From f21a030cf80c54fc0d7979f1de9578fe09b6fe7e Mon Sep 17 00:00:00 2001 From: Josh Date: Wed, 27 Jul 2016 10:37:36 -0500 Subject: [PATCH 01/79] Added the ability to view the library as a list (#394) * Added in the ability to view the library as a list * reverted LibraryAdapter and renamed libraryToggleViewEvent to LibraryToggleViewEvent for consistency * removed LibraryToggleViewEvent and directly subscribed to option change * fixed the toggleView subscription * Made the library list item layout more compliant with material design * Changed unread text style and removed background --- app/build.gradle | 1 + .../data/preference/PreferenceKeys.kt | 2 + .../data/preference/PreferencesHelper.kt | 2 + .../ui/library/LibraryCategoryAdapter.kt | 16 ++++-- .../ui/library/LibraryCategoryFragment.kt | 55 +++++++++++++++++++ .../tachiyomi/ui/library/LibraryFragment.kt | 42 ++++++++++++++ .../tachiyomi/ui/library/LibraryGridHolder.kt | 49 +++++++++++++++++ .../tachiyomi/ui/library/LibraryHolder.kt | 33 ++--------- .../tachiyomi/ui/library/LibraryListHolder.kt | 53 ++++++++++++++++++ .../tachiyomi/ui/library/LibraryPresenter.kt | 26 +++++++++ app/src/main/res/layout/fragment_library.xml | 10 ++-- .../res/layout/fragment_library_category.xml | 25 +++++++-- app/src/main/res/layout/item_library_list.xml | 35 ++++++++++++ app/src/main/res/menu/library.xml | 5 ++ app/src/main/res/values/keys.xml | 1 + app/src/main/res/xml/pref_general.xml | 2 +- 16 files changed, 313 insertions(+), 44 deletions(-) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryGridHolder.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListHolder.kt create mode 100644 app/src/main/res/layout/item_library_list.xml diff --git a/app/build.gradle b/app/build.gradle index 6c06d311f..23a51cbc4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -164,6 +164,7 @@ dependencies { compile 'net.xpece.android:support-preference:0.8.1' compile 'me.zhanghai.android.systemuihelper:library:1.0.0' compile 'org.adw.library:discrete-seekbar:1.0.1' + compile 'de.hdodenhof:circleimageview:2.1.0' // Tests testCompile 'junit:junit:4.12' diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt index 28b6fdc09..667a1eb90 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt @@ -92,4 +92,6 @@ class PreferenceKeys(context: Context) { fun syncPassword(syncId: Int) = "pref_mangasync_password_$syncId" + val libraryAsList = context.getString(R.string.pref_display_library_as_list) + } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt index 93a0c8bfd..6ebbdbcea 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt @@ -126,6 +126,8 @@ class PreferencesHelper(context: Context) { fun libraryUpdateRestriction() = prefs.getStringSet(keys.libraryUpdateRestriction, emptySet()) + fun libraryAsList() = rxPrefs.getBoolean(keys.libraryAsList, false) + fun filterDownloaded() = rxPrefs.getBoolean(keys.filterDownloaded, false) fun filterUnread() = rxPrefs.getBoolean(keys.filterUnread, false) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt index c16e86af3..5320e5be9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt @@ -84,11 +84,19 @@ class LibraryCategoryAdapter(val fragment: LibraryCategoryFragment) : * @return a new view holder for a manga. */ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LibraryHolder { - val view = parent.inflate(R.layout.item_catalogue_grid).apply { - card.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, coverHeight) - gradient.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, coverHeight / 2, Gravity.BOTTOM) + //depending on preferences, display a list or display a grid + if(parent.id == R.id.library_list) { + val view = parent.inflate(R.layout.item_library_list) + return LibraryListHolder(view, this, fragment) + + } else { + val view = parent.inflate(R.layout.item_catalogue_grid).apply { + card.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, coverHeight) + gradient.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, coverHeight / 2, Gravity.BOTTOM) + } + return LibraryGridHolder(view, this, fragment) } - return LibraryHolder(view, this, fragment) + } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryFragment.kt index 7cf99f807..1085df828 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryFragment.kt @@ -7,17 +7,22 @@ import android.support.v7.widget.RecyclerView import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.view.animation.AnimationUtils import com.f2prateek.rx.preferences.Preference import eu.davidea.flexibleadapter.FlexibleAdapter import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.library.LibraryUpdateService +import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder import eu.kanade.tachiyomi.ui.base.fragment.BaseFragment import eu.kanade.tachiyomi.ui.manga.MangaActivity import eu.kanade.tachiyomi.util.toast +import kotlinx.android.synthetic.main.fragment_catalogue.* +import kotlinx.android.synthetic.main.fragment_library.* import kotlinx.android.synthetic.main.fragment_library_category.* import rx.Subscription +import uy.kohesive.injekt.injectLazy /** * Fragment containing the library manga for a certain category. @@ -25,6 +30,11 @@ import rx.Subscription */ class LibraryCategoryFragment : BaseFragment(), FlexibleViewHolder.OnListItemClickListener { + /** + * Preferences. + */ + val preferences: PreferencesHelper by injectLazy() + /** * Adapter to hold the manga in this category. */ @@ -46,11 +56,21 @@ class LibraryCategoryFragment : BaseFragment(), FlexibleViewHolder.OnListItemCli */ private var numColumnsSubscription: Subscription? = null + /** + * subscription to view toggle + */ + private var toggleViewSubscription: Subscription? = null + /** * Subscription of the library search. */ private var searchSubscription: Subscription? = null + /** + * display mode + */ + private var displayAsList: Boolean = false; + companion object { /** * Key to save and restore [position] from a [Bundle]. @@ -66,19 +86,29 @@ class LibraryCategoryFragment : BaseFragment(), FlexibleViewHolder.OnListItemCli fun newInstance(position: Int): LibraryCategoryFragment { val fragment = LibraryCategoryFragment() fragment.position = position + return fragment } } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedState: Bundle?): View? { return inflater.inflate(R.layout.fragment_library_category, container, false) + + } override fun onViewCreated(view: View, savedState: Bundle?) { adapter = LibraryCategoryAdapter(this) + + //set up grid recycler.setHasFixedSize(true) recycler.adapter = adapter + //set up list + library_list.setHasFixedSize(true) + library_list.adapter = adapter + library_list.layoutManager = LinearLayoutManager(activity) + if (libraryFragment.actionMode != null) { setSelectionMode(FlexibleAdapter.MODE_MULTI) } @@ -94,6 +124,17 @@ class LibraryCategoryFragment : BaseFragment(), FlexibleViewHolder.OnListItemCli adapter.updateDataSet() } + toggleViewSubscription = preferences.libraryAsList().asObservable().subscribe {onViewModeChange(it)} + + if(libraryPresenter.displayAsList != displayAsList) { + library_switcher.showNext() + displayAsList = libraryPresenter.displayAsList + } + + + library_switcher.inAnimation = AnimationUtils.loadAnimation(activity, android.R.anim.fade_in) + library_switcher.outAnimation = AnimationUtils.loadAnimation(activity, android.R.anim.fade_out) + if (savedState != null) { position = savedState.getInt(POSITION_KEY) adapter.onRestoreInstanceState(savedState) @@ -129,13 +170,17 @@ class LibraryCategoryFragment : BaseFragment(), FlexibleViewHolder.OnListItemCli override fun onDestroyView() { numColumnsSubscription?.unsubscribe() searchSubscription?.unsubscribe() + toggleViewSubscription?.unsubscribe() super.onDestroyView() } override fun onResume() { super.onResume() + + libraryMangaSubscription = libraryPresenter.libraryMangaSubject .subscribe { onNextLibraryManga(it) } + } override fun onPause() { @@ -211,6 +256,7 @@ class LibraryCategoryFragment : BaseFragment(), FlexibleViewHolder.OnListItemCli startActivity(intent) } + /** * Toggles the selection for a manga. * @@ -262,6 +308,15 @@ class LibraryCategoryFragment : BaseFragment(), FlexibleViewHolder.OnListItemCli } } + fun onViewModeChange(isList: Boolean) { + //do nothing if the display does not need to change + if(isList == displayAsList) return + + //else change view and display mode + library_switcher.showNext() + displayAsList = isList + } + /** * Property to get the library fragment. */ diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt index 5f14d4eec..9e455a029 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt @@ -14,6 +14,7 @@ import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.library.LibraryUpdateService +import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment import eu.kanade.tachiyomi.ui.category.CategoryActivity @@ -22,6 +23,7 @@ import eu.kanade.tachiyomi.util.toast import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.fragment_library.* import nucleus.factory.RequiresPresenter +import uy.kohesive.injekt.injectLazy import java.io.IOException /** @@ -37,6 +39,11 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback lateinit var adapter: LibraryAdapter private set + /** + * Preferences. + */ + val preferences: PreferencesHelper by injectLazy() + /** * TabLayout of the categories. */ @@ -53,6 +60,11 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback */ private var query: String? = null + /** + * Display mode of the library (list or grid mode). + */ + private var displayMode: MenuItem? = null + /** * Action mode for manga selection. */ @@ -178,6 +190,18 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback return true } }) + + //set the icon for the display mode button + displayMode = menu.findItem(R.id.action_library_display_mode).apply { + val icon = if (preferences.libraryAsList().getOrDefault()) + R.drawable.ic_view_module_white_24dp + else + R.drawable.ic_view_list_white_24dp + + setIcon(icon) + } + + } override fun onOptionsItemSelected(item: MenuItem): Boolean { @@ -208,6 +232,7 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback // Apply filter onFilterCheckboxChanged() } + R.id.action_library_display_mode -> swapDisplayMode() R.id.action_update_library -> { LibraryUpdateService.start(activity, true) } @@ -231,6 +256,23 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback activity.supportInvalidateOptionsMenu() } + /** + * swap display mode + */ + private fun swapDisplayMode() { + + presenter.swapDisplayMode() + val isListMode = presenter.displayAsList + val icon = if (isListMode) + R.drawable.ic_view_module_white_24dp + else + R.drawable.ic_view_list_white_24dp + + displayMode?.setIcon(icon) + + } + + /** * Updates the query. * diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryGridHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryGridHolder.kt new file mode 100644 index 000000000..91424f3bd --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryGridHolder.kt @@ -0,0 +1,49 @@ +package eu.kanade.tachiyomi.ui.library + +import android.view.View +import com.bumptech.glide.Glide +import com.bumptech.glide.load.engine.DiskCacheStrategy +import eu.kanade.tachiyomi.data.database.models.Manga +import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder +import kotlinx.android.synthetic.main.item_catalogue_grid.view.* + +/** + * Class used to hold the displayed data of a manga in the library, like the cover or the title. + * All the elements from the layout file "item_catalogue_grid" are available in this class. + * + * @param view the inflated view for this holder. + * @param adapter the adapter handling this holder. + * @param listener a listener to react to single tap and long tap events. + * @constructor creates a new library holder. + */ +class LibraryGridHolder(private val view: View, + private val adapter: LibraryCategoryAdapter, + listener: FlexibleViewHolder.OnListItemClickListener) +: LibraryHolder(view, adapter, listener) { + + /** + * Method called from [LibraryCategoryAdapter.onBindViewHolder]. It updates the data for this + * holder with the given manga. + * + * @param manga the manga to bind. + */ + override fun onSetValues(manga: Manga) { + // Update the title of the manga. + view.title.text = manga.title + + // Update the unread count and its visibility. + with(view.unread_text) { + visibility = if (manga.unread > 0) View.VISIBLE else View.GONE + text = manga.unread.toString() + } + + // Update the cover. + Glide.clear(view.thumbnail) + Glide.with(view.context) + .load(manga) + .diskCacheStrategy(DiskCacheStrategy.RESULT) + .centerCrop() + .into(view.thumbnail) + } + +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.kt index 9cf28ae46..07f5add56 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.kt @@ -8,18 +8,14 @@ import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder import kotlinx.android.synthetic.main.item_catalogue_grid.view.* /** - * Class used to hold the displayed data of a manga in the library, like the cover or the title. - * All the elements from the layout file "item_catalogue_grid" are available in this class. - * + * Generic class used to hold the displayed data of a manga in the library. * @param view the inflated view for this holder. * @param adapter the adapter handling this holder. - * @param listener a listener to react to single tap and long tap events. - * @constructor creates a new library holder. + * @param listener a listener to react to the single tap and long tap events. */ -class LibraryHolder(private val view: View, - private val adapter: LibraryCategoryAdapter, - listener: FlexibleViewHolder.OnListItemClickListener) -: FlexibleViewHolder(view, adapter, listener) { + +abstract class LibraryHolder(private val view: View, adapter: LibraryCategoryAdapter, listener: FlexibleViewHolder.OnListItemClickListener) + : FlexibleViewHolder(view, adapter, listener) { /** * Method called from [LibraryCategoryAdapter.onBindViewHolder]. It updates the data for this @@ -27,23 +23,6 @@ class LibraryHolder(private val view: View, * * @param manga the manga to bind. */ - fun onSetValues(manga: Manga) { - // Update the title of the manga. - view.title.text = manga.title - - // Update the unread count and its visibility. - with(view.unread_text) { - visibility = if (manga.unread > 0) View.VISIBLE else View.GONE - text = manga.unread.toString() - } - - // Update the cover. - Glide.clear(view.thumbnail) - Glide.with(view.context) - .load(manga) - .diskCacheStrategy(DiskCacheStrategy.RESULT) - .centerCrop() - .into(view.thumbnail) - } + abstract fun onSetValues(manga: Manga) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListHolder.kt new file mode 100644 index 000000000..78d9accee --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListHolder.kt @@ -0,0 +1,53 @@ +package eu.kanade.tachiyomi.ui.library + +import android.view.View +import com.bumptech.glide.Glide +import com.bumptech.glide.load.engine.DiskCacheStrategy +import eu.kanade.tachiyomi.data.database.models.Manga +import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder +import kotlinx.android.synthetic.main.item_library_list.view.* + +/** + * Class used to hold the displayed data of a manga in the library, like the cover or the title. + * All the elements from the layout file "item_library_list" are available in this class. + * + * @param view the inflated view for this holder. + * @param adapter the adapter handling this holder. + * @param listener a listener to react to single tap and long tap events. + * @constructor creates a new library holder. + */ + +class LibraryListHolder(private val view: View, + private val adapter: LibraryCategoryAdapter, + listener: FlexibleViewHolder.OnListItemClickListener) +: LibraryHolder(view, adapter, listener) { + + /** + * Method called from [LibraryCategoryAdapter.onBindViewHolder]. It updates the data for this + * holder with the given manga. + * + * @param manga the manga to bind. + */ + override fun onSetValues(manga: Manga) { + // Update the title of the manga. + view.title.text = manga.title + + // Update the unread count and its visibility. + with(view.unread_text) { + visibility = if (manga.unread > 0) View.VISIBLE else View.GONE + text = manga.unread.toString() + } + + + + // Update the cover. + Glide.clear(view.thumbnail) + Glide.with(view.context) + .load(manga) + .diskCacheStrategy(DiskCacheStrategy.RESULT) + .centerCrop() + .dontAnimate() + .into(view.thumbnail) + } + +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt index 83c8682c8..26650cd04 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt @@ -71,6 +71,12 @@ class LibraryPresenter : BasePresenter() { */ val downloadManager: DownloadManager by injectLazy() + /** + * display the library as a list? + */ + var displayAsList: Boolean = false + private set + companion object { /** * Id of the restartable that listens for library updates. @@ -89,6 +95,18 @@ class LibraryPresenter : BasePresenter() { start(GET_LIBRARY) } + + add(preferences.libraryAsList().asObservable().subscribe{setDisplayMode(it)}) + + } + + /** + * Sets the display mode + * + * @param asList display as list or not + */ + fun setDisplayMode(asList: Boolean) { + displayAsList = asList } /** @@ -285,4 +303,12 @@ class LibraryPresenter : BasePresenter() { return false } + /** + * Changes the active display mode. + */ + fun swapDisplayMode() { + var currentMode: Boolean = displayAsList + preferences.libraryAsList().set(!displayAsList) + } + } diff --git a/app/src/main/res/layout/fragment_library.xml b/app/src/main/res/layout/fragment_library.xml index f603e3d3e..2edae65d2 100644 --- a/app/src/main/res/layout/fragment_library.xml +++ b/app/src/main/res/layout/fragment_library.xml @@ -3,11 +3,9 @@ android:layout_height="match_parent" android:orientation="vertical"> - - - + diff --git a/app/src/main/res/layout/fragment_library_category.xml b/app/src/main/res/layout/fragment_library_category.xml index 46801572a..77fd57c33 100644 --- a/app/src/main/res/layout/fragment_library_category.xml +++ b/app/src/main/res/layout/fragment_library_category.xml @@ -9,14 +9,27 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - + android:layout_height="0dp" + android:layout_weight="1"> + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_library_list.xml b/app/src/main/res/layout/item_library_list.xml new file mode 100644 index 000000000..7142650f4 --- /dev/null +++ b/app/src/main/res/layout/item_library_list.xml @@ -0,0 +1,35 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/library.xml b/app/src/main/res/menu/library.xml index bb669a95d..0af3d8b00 100644 --- a/app/src/main/res/menu/library.xml +++ b/app/src/main/res/menu/library.xml @@ -29,6 +29,11 @@ app:showAsAction="collapseActionView|ifRoom" app:actionViewClass="android.support.v7.widget.SearchView" /> + + pref_category_about_key pref_category_sources_key + pref_display_library_as_list pref_library_columns_dialog_key pref_library_columns_portrait_key pref_library_columns_landscape_key diff --git a/app/src/main/res/xml/pref_general.xml b/app/src/main/res/xml/pref_general.xml index 77e5fda3e..350b551a8 100644 --- a/app/src/main/res/xml/pref_general.xml +++ b/app/src/main/res/xml/pref_general.xml @@ -13,7 +13,7 @@ android:key="@string/pref_theme_key" android:summary="%s" android:title="@string/pref_theme"/> - + Date: Thu, 28 Jul 2016 01:01:56 +0200 Subject: [PATCH 02/79] Minor Improvements (#405) --- .../tachiyomi/ui/category/CategoryActivity.kt | 4 +- .../ui/library/LibraryCategoryFragment.kt | 12 ++--- .../tachiyomi/ui/library/LibraryFragment.kt | 6 +-- .../tachiyomi/ui/library/LibraryHolder.kt | 3 -- .../tachiyomi/ui/library/LibraryListHolder.kt | 16 +++--- .../tachiyomi/ui/library/LibraryPresenter.kt | 7 ++- .../res/layout/fragment_library_category.xml | 1 + app/src/main/res/layout/item_library_list.xml | 49 +++++++++++-------- app/src/main/res/menu/library.xml | 28 +++++------ 9 files changed, 67 insertions(+), 59 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryActivity.kt index 8a01bcacf..44d295b76 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryActivity.kt @@ -55,9 +55,9 @@ class CategoryActivity : BaseRxActivity(), ActionMode.Callbac } } - override fun onCreate(savedInstanceState: Bundle?) { + override fun onCreate(savedState: Bundle?) { setAppTheme() - super.onCreate(savedInstanceState) + super.onCreate(savedState) // Inflate activity_edit_categories.xml. setContentView(R.layout.activity_edit_categories) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryFragment.kt index 1085df828..814dec25a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryFragment.kt @@ -18,8 +18,6 @@ import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder import eu.kanade.tachiyomi.ui.base.fragment.BaseFragment import eu.kanade.tachiyomi.ui.manga.MangaActivity import eu.kanade.tachiyomi.util.toast -import kotlinx.android.synthetic.main.fragment_catalogue.* -import kotlinx.android.synthetic.main.fragment_library.* import kotlinx.android.synthetic.main.fragment_library_category.* import rx.Subscription import uy.kohesive.injekt.injectLazy @@ -69,7 +67,7 @@ class LibraryCategoryFragment : BaseFragment(), FlexibleViewHolder.OnListItemCli /** * display mode */ - private var displayAsList: Boolean = false; + private var displayAsList: Boolean = false companion object { /** @@ -109,6 +107,8 @@ class LibraryCategoryFragment : BaseFragment(), FlexibleViewHolder.OnListItemCli library_list.adapter = adapter library_list.layoutManager = LinearLayoutManager(activity) + + if (libraryFragment.actionMode != null) { setSelectionMode(FlexibleAdapter.MODE_MULTI) } @@ -119,7 +119,7 @@ class LibraryCategoryFragment : BaseFragment(), FlexibleViewHolder.OnListItemCli // Set again the adapter to recalculate the covers height .subscribe { recycler.adapter = adapter } - searchSubscription = libraryPresenter.searchSubject.subscribe { text -> + searchSubscription = libraryPresenter.searchSubject?.subscribe { text -> adapter.searchText = text adapter.updateDataSet() } @@ -179,7 +179,7 @@ class LibraryCategoryFragment : BaseFragment(), FlexibleViewHolder.OnListItemCli libraryMangaSubscription = libraryPresenter.libraryMangaSubject - .subscribe { onNextLibraryManga(it) } + ?.subscribe { onNextLibraryManga(it) } } @@ -247,7 +247,7 @@ class LibraryCategoryFragment : BaseFragment(), FlexibleViewHolder.OnListItemCli * * @param manga the manga to open. */ - protected fun openManga(manga: Manga) { + private fun openManga(manga: Manga) { // Notify the presenter a manga is being opened. libraryPresenter.onOpenManga() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt index 9e455a029..0916dfd2e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt @@ -138,7 +138,7 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback if (savedState != null) { activeCategory = savedState.getInt(CATEGORY_KEY) query = savedState.getString(QUERY_KEY) - presenter.searchSubject.onNext(query) + presenter.searchSubject?.onNext(query) } else { activeCategory = presenter.preferences.lastUsedCategory().getOrDefault() } @@ -283,7 +283,7 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback // Notify the subject the query has changed. if (isResumed) { - presenter.searchSubject.onNext(query) + presenter.searchSubject?.onNext(query) } } @@ -311,7 +311,7 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback view_pager.post { if (isAdded) tabs.setScrollPosition(view_pager.currentItem, 0f, true) } // Send the manga map to child fragments after the adapter is updated. - presenter.libraryMangaSubject.onNext(LibraryMangaEvent(mangaMap)) + presenter.libraryMangaSubject?.onNext(LibraryMangaEvent(mangaMap)) } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.kt index 07f5add56..a9f2cfb23 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.kt @@ -1,11 +1,8 @@ package eu.kanade.tachiyomi.ui.library import android.view.View -import com.bumptech.glide.Glide -import com.bumptech.glide.load.engine.DiskCacheStrategy import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder -import kotlinx.android.synthetic.main.item_catalogue_grid.view.* /** * Generic class used to hold the displayed data of a manga in the library. diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListHolder.kt index 78d9accee..2e561d38c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListHolder.kt @@ -30,24 +30,28 @@ class LibraryListHolder(private val view: View, */ override fun onSetValues(manga: Manga) { // Update the title of the manga. - view.title.text = manga.title + itemView.title.text = manga.title // Update the unread count and its visibility. - with(view.unread_text) { + with(itemView.unread_text) { visibility = if (manga.unread > 0) View.VISIBLE else View.GONE text = manga.unread.toString() } - + // Create thumbnail onclick to simulate long click + itemView.thumbnail.setOnClickListener { + // Simulate long click on this view to enter selection mode + onLongClick(itemView) + } // Update the cover. - Glide.clear(view.thumbnail) - Glide.with(view.context) + Glide.clear(itemView.thumbnail) + Glide.with(itemView.context) .load(manga) .diskCacheStrategy(DiskCacheStrategy.RESULT) .centerCrop() .dontAnimate() - .into(view.thumbnail) + .into(itemView.thumbnail) } } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt index 26650cd04..39efd36d6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt @@ -39,12 +39,12 @@ class LibraryPresenter : BasePresenter() { /** * Search query of the library. */ - val searchSubject = BehaviorSubject.create() + val searchSubject: BehaviorSubject? = BehaviorSubject.create() /** * Subject to notify the library's viewpager for updates. */ - val libraryMangaSubject = BehaviorSubject.create() + val libraryMangaSubject: BehaviorSubject? = BehaviorSubject.create() /** * Database. @@ -247,7 +247,7 @@ class LibraryPresenter : BasePresenter() { * * @param mangas the list of manga. */ - fun getCommonCategories(mangas: List) = mangas.toSet() + fun getCommonCategories(mangas: List): Collection = mangas.toSet() .map { db.getCategoriesForManga(it).executeAsBlocking() } .reduce { set1: Iterable, set2 -> set1.intersect(set2) } @@ -307,7 +307,6 @@ class LibraryPresenter : BasePresenter() { * Changes the active display mode. */ fun swapDisplayMode() { - var currentMode: Boolean = displayAsList preferences.libraryAsList().set(!displayAsList) } diff --git a/app/src/main/res/layout/fragment_library_category.xml b/app/src/main/res/layout/fragment_library_category.xml index 77fd57c33..bc31c39c8 100644 --- a/app/src/main/res/layout/fragment_library_category.xml +++ b/app/src/main/res/layout/fragment_library_category.xml @@ -27,6 +27,7 @@ android:id="@+id/library_list" android:layout_width="match_parent" android:layout_height="match_parent" + android:paddingTop="@dimen/material_component_lists_padding_above_list" tools:listitem="@layout/item_library_list"/> diff --git a/app/src/main/res/layout/item_library_list.xml b/app/src/main/res/layout/item_library_list.xml index 7142650f4..cad62cfd0 100644 --- a/app/src/main/res/layout/item_library_list.xml +++ b/app/src/main/res/layout/item_library_list.xml @@ -2,34 +2,41 @@ + android:layout_width="@dimen/material_component_text_fields_icon_height" + android:layout_height="@dimen/material_component_text_fields_icon_height" + android:layout_gravity="center_vertical" + android:paddingEnd="0dp" + android:paddingLeft="@dimen/material_component_lists_icon_left_padding" + android:paddingRight="0dp" + android:paddingStart="@dimen/material_component_lists_icon_left_padding" + android:src="@drawable/icon"/> + android:id="@+id/title" + style="@style/TextAppearance.Regular.SubHeading" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:paddingEnd="0dp" + android:paddingLeft="@dimen/material_component_lists_text_left_padding" + android:paddingRight="0dp" + android:paddingStart="@dimen/material_component_lists_text_left_padding"/> + android:id="@+id/unread_text" + style="@style/TextAppearance.Regular.Caption.Hint" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical|end" + android:paddingEnd="@dimen/material_component_lists_right_padding" + android:paddingLeft="0dp" + android:paddingRight="@dimen/material_component_lists_right_padding" + android:paddingStart="0dp" + android:visibility="gone"/> \ No newline at end of file diff --git a/app/src/main/res/menu/library.xml b/app/src/main/res/menu/library.xml index 0af3d8b00..27fabce9b 100644 --- a/app/src/main/res/menu/library.xml +++ b/app/src/main/res/menu/library.xml @@ -2,10 +2,17 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" tools:context=".MainActivity"> + + + android:id="@+id/action_update_library" + android:icon="@drawable/ic_refresh_white_24dp" + android:title="@string/action_update_library" + app:showAsAction="ifRoom" /> - - + android:title="@string/action_display_mode" + app:showAsAction="never"/> Date: Sat, 30 Jul 2016 15:51:49 +0200 Subject: [PATCH 03/79] Replace page fragments with views --- .../{PagerReaderFragment.kt => PageView.kt} | 170 +++++++----------- .../ui/reader/viewer/pager/PagerReader.kt | 13 +- .../reader/viewer/pager/PagerReaderAdapter.kt | 61 ++----- .../tachiyomi/widget/ViewPagerAdapter.kt | 30 ++++ app/src/main/res/layout/item_pager_reader.xml | 4 +- 5 files changed, 116 insertions(+), 162 deletions(-) rename app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/{PagerReaderFragment.kt => PageView.kt} (53%) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/widget/ViewPagerAdapter.kt diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReaderFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PageView.kt similarity index 53% rename from app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReaderFragment.kt rename to app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PageView.kt index 0c172a086..523fbba68 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReaderFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PageView.kt @@ -1,23 +1,23 @@ package eu.kanade.tachiyomi.ui.reader.viewer.pager -import android.graphics.PointF -import android.os.Bundle -import android.support.v4.content.ContextCompat -import android.view.LayoutInflater +import android.content.Context +import android.util.AttributeSet import android.view.MotionEvent import android.view.View -import android.view.ViewGroup +import android.widget.FrameLayout import com.davemorrissey.labs.subscaleview.ImageSource import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.source.model.Page -import eu.kanade.tachiyomi.ui.base.fragment.BaseFragment import eu.kanade.tachiyomi.ui.reader.ReaderActivity import eu.kanade.tachiyomi.ui.reader.viewer.base.PageDecodeErrorLayout +import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerReader.Companion.ALIGN_CENTER +import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerReader.Companion.ALIGN_LEFT +import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerReader.Companion.ALIGN_RIGHT import eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal.RightToLeftReader import eu.kanade.tachiyomi.ui.reader.viewer.pager.vertical.VerticalReader -import kotlinx.android.synthetic.main.chapter_image.* -import kotlinx.android.synthetic.main.item_pager_reader.* +import kotlinx.android.synthetic.main.chapter_image.view.* +import kotlinx.android.synthetic.main.item_pager_reader.view.* import rx.Observable import rx.Subscription import rx.android.schedulers.AndroidSchedulers @@ -25,41 +25,15 @@ import rx.subjects.PublishSubject import rx.subjects.SerializedSubject import java.io.File import java.util.concurrent.TimeUnit -import java.util.concurrent.atomic.AtomicInteger -/** - * Fragment for a single page of the ViewPager reader. - * All the elements from the layout file "item_pager_reader" are available in this class. - */ -class PagerReaderFragment : BaseFragment() { - - companion object { - /** - * Creates a new instance of this fragment. - * - * @return a new instance of [PagerReaderFragment]. - */ - fun newInstance(): PagerReaderFragment { - return PagerReaderFragment() - } - } +class PageView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) +: FrameLayout(context, attrs) { /** * Page of a chapter. */ var page: Page? = null - set(value) { - field = value - // Observe status if the view is initialized - if (view != null) { - observeStatus() - } - } - - /** - * Position of the fragment in the adapter. - */ - var position = -1 + private set /** * Subscription for progress changes of the page. @@ -71,47 +45,34 @@ class PagerReaderFragment : BaseFragment() { */ private var statusSubscription: Subscription? = null - /** - * Text color for black theme. - */ - private val whiteColor by lazy { ContextCompat.getColor(context, R.color.textColorSecondaryDark) } + fun initialize(reader: PagerReader, page: Page?) { + val activity = reader.activity as ReaderActivity - /** - * Text color for white theme. - */ - private val blackColor by lazy { ContextCompat.getColor(context, R.color.textColorSecondaryLight) } - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedState: Bundle?): View? { - return inflater.inflate(R.layout.item_pager_reader, container, false) - } - - override fun onViewCreated(view: View, savedState: Bundle?) { - if (readerActivity.readerTheme == ReaderActivity.BLACK_THEME) { - progress_text.setTextColor(whiteColor) - } else { - progress_text.setTextColor(blackColor) + when (activity.readerTheme) { + ReaderActivity.BLACK_THEME -> progress_text.setTextColor(reader.whiteColor) + ReaderActivity.WHITE_THEME -> progress_text.setTextColor(reader.blackColor) } - if (pagerReader is RightToLeftReader) { - view.rotation = -180f + if (reader is RightToLeftReader) { + rotation = -180f } with(image_view) { - setMaxBitmapDimensions(readerActivity.maxBitmapSize) - setDoubleTapZoomStyle(SubsamplingScaleImageView.ZOOM_FOCUS_FIXED) - setPanLimit(SubsamplingScaleImageView.PAN_LIMIT_INSIDE) - setMinimumScaleType(pagerReader.scaleType) + setMaxBitmapDimensions((reader.activity as ReaderActivity).maxBitmapSize) + setDoubleTapZoomStyle(com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView.ZOOM_FOCUS_FIXED) + setPanLimit(com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView.PAN_LIMIT_INSIDE) + setMinimumScaleType(reader.scaleType) setMinimumDpi(50) - setRegionDecoderClass(pagerReader.regionDecoderClass) - setBitmapDecoderClass(pagerReader.bitmapDecoderClass) - setVerticalScrollingParent(pagerReader is VerticalReader) - setOnTouchListener { v, motionEvent -> pagerReader.gestureDetector.onTouchEvent(motionEvent) } + setRegionDecoderClass(reader.regionDecoderClass) + setBitmapDecoderClass(reader.bitmapDecoderClass) + setVerticalScrollingParent(reader is VerticalReader) + setOnTouchListener { v, motionEvent -> reader.gestureDetector.onTouchEvent(motionEvent) } setOnImageEventListener(object : SubsamplingScaleImageView.DefaultOnImageEventListener() { override fun onReady() { - when (pagerReader.zoomType) { - PagerReader.ALIGN_LEFT -> setScaleAndCenter(scale, PointF(0f, 0f)) - PagerReader.ALIGN_RIGHT -> setScaleAndCenter(scale, PointF(sWidth.toFloat(), 0f)) - PagerReader.ALIGN_CENTER -> { + when (reader.zoomType) { + ALIGN_LEFT -> setScaleAndCenter(scale, android.graphics.PointF(0f, 0f)) + ALIGN_RIGHT -> setScaleAndCenter(scale, android.graphics.PointF(sWidth.toFloat(), 0f)) + ALIGN_CENTER -> { val newCenter = center newCenter.y = 0f setScaleAndCenter(scale, newCenter) @@ -120,27 +81,34 @@ class PagerReaderFragment : BaseFragment() { } override fun onImageLoadError(e: Exception) { - onImageDecodeError() + onImageDecodeError(activity) } }) } retry_button.setOnTouchListener { v, event -> if (event.action == MotionEvent.ACTION_UP) { - readerActivity.presenter.retryPage(page) + activity.presenter.retryPage(page) } true } - observeStatus() + if (page != null) { + this.page = page + observeStatus() + } } - override fun onDestroyView() { + fun cleanup() { unsubscribeProgress() unsubscribeStatus() image_view.setOnTouchListener(null) image_view.setOnImageEventListener(null) - super.onDestroyView() + } + + override fun onDetachedFromWindow() { + cleanup() + super.onDetachedFromWindow() } /** @@ -149,33 +117,31 @@ class PagerReaderFragment : BaseFragment() { * @see processStatus */ private fun observeStatus() { - page?.let { page -> - val statusSubject = SerializedSubject(PublishSubject.create()) - page.setStatusSubject(statusSubject) + statusSubscription?.unsubscribe() + val page = page ?: return - statusSubscription?.unsubscribe() - statusSubscription = statusSubject.startWith(page.status) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { processStatus(it) } - } + val statusSubject = SerializedSubject(PublishSubject.create()) + page.setStatusSubject(statusSubject) + + statusSubscription = statusSubject.startWith(page.status) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { processStatus(it) } } /** * Observes the progress of the page and updates view. */ private fun observeProgress() { - val currentValue = AtomicInteger(-1) - progressSubscription?.unsubscribe() + val page = page ?: return + progressSubscription = Observable.interval(100, TimeUnit.MILLISECONDS) + .map { page.progress } + .distinctUntilChanged() .onBackpressureLatest() .observeOn(AndroidSchedulers.mainThread()) - .subscribe { - // Refresh UI only if progress change - if (page?.progress != currentValue.get()) { - currentValue.set(page?.progress ?: 0) - progress_text.text = getString(R.string.download_progress, currentValue.get()) - } + .subscribe { progress -> + progress_text.text = context.getString(R.string.download_progress, progress) } } @@ -269,27 +235,13 @@ class PagerReaderFragment : BaseFragment() { /** * Called when an image fails to decode. */ - private fun onImageDecodeError() { - val view = view as? ViewGroup ?: return - + private fun onImageDecodeError(activity: ReaderActivity) { page?.let { page -> - val errorLayout = PageDecodeErrorLayout(context, page, readerActivity.readerTheme, - { readerActivity.presenter.retryPage(page) }) + val errorLayout = PageDecodeErrorLayout(context, page, activity.readerTheme, + { activity.presenter.retryPage(page) }) - view.addView(errorLayout) + addView(errorLayout) } } - /** - * Property to get the reader activity. - */ - private val readerActivity: ReaderActivity - get() = activity as ReaderActivity - - /** - * Property to get the pager reader. - */ - private val pagerReader: PagerReader - get() = parentFragment as PagerReader - -} +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReader.kt index f1c0e32a5..fafd4f3a7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReader.kt @@ -1,5 +1,6 @@ package eu.kanade.tachiyomi.ui.reader.viewer.pager +import android.support.v4.content.ContextCompat import android.view.GestureDetector import android.view.MotionEvent import android.view.ViewGroup @@ -90,13 +91,23 @@ abstract class PagerReader : BaseReader() { var zoomType = 1 private set + /** + * Text color for black theme. + */ + val whiteColor by lazy { ContextCompat.getColor(context, R.color.textColorSecondaryDark) } + + /** + * Text color for white theme. + */ + val blackColor by lazy { ContextCompat.getColor(context, R.color.textColorSecondaryLight) } + /** * Initializes the pager. * * @param pager the pager to initialize. */ protected fun initializePager(pager: Pager) { - adapter = PagerReaderAdapter(childFragmentManager) + adapter = PagerReaderAdapter(this) this.pager = pager.apply { setLayoutParams(ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReaderAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReaderAdapter.kt index 9c2990b51..fe3fadfc0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReaderAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReaderAdapter.kt @@ -1,19 +1,16 @@ package eu.kanade.tachiyomi.ui.reader.viewer.pager -import android.support.v4.app.Fragment -import android.support.v4.app.FragmentManager -import android.support.v4.app.FragmentStatePagerAdapter -import android.support.v4.view.PagerAdapter +import android.view.View import android.view.ViewGroup - +import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.source.model.Page +import eu.kanade.tachiyomi.util.inflate +import eu.kanade.tachiyomi.widget.ViewPagerAdapter /** * Adapter of pages for a ViewPager. - * - * @param fm the fragment manager. */ -class PagerReaderAdapter(fm: FragmentManager) : FragmentStatePagerAdapter(fm) { +class PagerReaderAdapter(private val reader: PagerReader) : ViewPagerAdapter() { /** * Pages stored in the adapter. @@ -24,6 +21,12 @@ class PagerReaderAdapter(fm: FragmentManager) : FragmentStatePagerAdapter(fm) { notifyDataSetChanged() } + override fun createView(container: ViewGroup, position: Int): View { + val view = container.inflate(R.layout.item_pager_reader) as PageView + view.initialize(reader, pages?.getOrNull(position)) + return view + } + /** * Returns the number of pages. * @@ -33,46 +36,4 @@ class PagerReaderAdapter(fm: FragmentManager) : FragmentStatePagerAdapter(fm) { return pages?.size ?: 0 } - /** - * Creates a new fragment for the given position when it's called. - * - * @param position the position to instantiate. - * @return a fragment for the given position. - */ - override fun getItem(position: Int): Fragment { - return PagerReaderFragment.newInstance() - } - - /** - * Instantiates a fragment in the given position. - * - * @param container the parent view. - * @param position the position to instantiate. - * @return an instance of a fragment for the given position. - */ - override fun instantiateItem(container: ViewGroup, position: Int): Any { - val f = super.instantiateItem(container, position) as PagerReaderFragment - f.page = pages!![position] - f.position = position - return f - } - - /** - * Returns the position of a given item. - * - * @param obj the item to find its position. - * @return the position for the item. - */ - override fun getItemPosition(obj: Any): Int { - val f = obj as PagerReaderFragment - val position = f.position - if (position >= 0 && position < count) { - if (pages!![position] === f.page) { - return PagerAdapter.POSITION_UNCHANGED - } else { - return PagerAdapter.POSITION_NONE - } - } - return super.getItemPosition(obj) - } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/ViewPagerAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/ViewPagerAdapter.kt new file mode 100644 index 000000000..c2c21a66b --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/ViewPagerAdapter.kt @@ -0,0 +1,30 @@ +package eu.kanade.tachiyomi.widget + +import android.support.v4.view.PagerAdapter +import android.view.View +import android.view.ViewGroup + +abstract class ViewPagerAdapter : PagerAdapter() { + + protected abstract fun createView(container: ViewGroup, position: Int): View + + protected open fun destroyView(container: ViewGroup, position: Int, view: View) { + } + + override fun instantiateItem(container: ViewGroup, position: Int): Any { + val view = createView(container, position) + container.addView(view) + return view + } + + override fun destroyItem(container: ViewGroup, position: Int, obj: Any) { + val view = obj as View + destroyView(container, position, view) + container.removeView(view) + } + + override fun isViewFromObject(view: View, obj: Any): Boolean { + return view === obj + } + +} \ No newline at end of file diff --git a/app/src/main/res/layout/item_pager_reader.xml b/app/src/main/res/layout/item_pager_reader.xml index 4fefdc9b1..3b5f3d8c9 100644 --- a/app/src/main/res/layout/item_pager_reader.xml +++ b/app/src/main/res/layout/item_pager_reader.xml @@ -1,6 +1,6 @@ - @@ -40,4 +40,4 @@ android:layout_gravity="center" android:visibility="gone"/> - \ No newline at end of file + \ No newline at end of file From f768393a4b415178267f3cac9c54dafe49f4afb2 Mon Sep 17 00:00:00 2001 From: len Date: Sat, 30 Jul 2016 16:04:43 +0200 Subject: [PATCH 04/79] Bump dependencies, set target sdk 24 --- app/build.gradle | 20 +++++++++---------- .../widget/preference/MangaSyncLoginDialog.kt | 10 +++++----- build.gradle | 2 +- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 23a51cbc4..7a5d166aa 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -29,14 +29,14 @@ def includeUpdater() { } android { - compileSdkVersion 23 - buildToolsVersion "23.0.3" + compileSdkVersion 24 + buildToolsVersion "24.0.1" publishNonDefault true defaultConfig { applicationId "eu.kanade.tachiyomi" minSdkVersion 16 - targetSdkVersion 23 + targetSdkVersion 24 testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" versionCode 10 versionName "0.2.3" @@ -87,7 +87,7 @@ dependencies { compile 'com.github.inorichi:ReactiveNetwork:69092ed' // Android support library - final support_library_version = '23.4.0' + final support_library_version = '24.1.1' compile "com.android.support:support-v4:$support_library_version" compile "com.android.support:appcompat-v7:$support_library_version" compile "com.android.support:cardview-v7:$support_library_version" @@ -98,11 +98,11 @@ dependencies { // ReactiveX compile 'io.reactivex:rxandroid:1.2.1' - compile 'io.reactivex:rxjava:1.1.6' + compile 'io.reactivex:rxjava:1.1.8' compile 'com.f2prateek.rx.preferences:rx-preferences:1.0.2' // Network client - compile "com.squareup.okhttp3:okhttp:3.3.1" + compile "com.squareup.okhttp3:okhttp:3.4.1" // REST final retrofit_version = '2.1.0' @@ -111,7 +111,7 @@ dependencies { compile "com.squareup.retrofit2:adapter-rxjava:$retrofit_version" // IO - compile 'com.squareup.okio:okio:1.8.0' + compile 'com.squareup.okio:okio:1.9.0' // JSON compile 'com.google.code.gson:gson:2.7' @@ -133,7 +133,7 @@ dependencies { compile 'com.github.gabrielemariotti.changeloglib:changelog:2.1.0' // Database - compile "com.pushtorefresh.storio:sqlite:1.9.0" + compile "com.pushtorefresh.storio:sqlite:1.10.0" // Model View Presenter final nucleus_version = '3.0.0' @@ -160,7 +160,7 @@ dependencies { compile 'eu.davidea:flexible-adapter:4.2.0' compile 'com.nononsenseapps:filepicker:2.5.2' compile 'com.github.amulyakhare:TextDrawable:558677e' - compile 'com.afollestad.material-dialogs:core:0.8.6.1' + compile 'com.afollestad.material-dialogs:core:0.8.6.2' compile 'net.xpece.android:support-preference:0.8.1' compile 'me.zhanghai.android.systemuihelper:library:1.0.0' compile 'org.adw.library:discrete-seekbar:1.0.1' @@ -170,7 +170,7 @@ dependencies { testCompile 'junit:junit:4.12' testCompile 'org.assertj:assertj-core:1.7.1' testCompile 'org.mockito:mockito-core:1.10.19' - testCompile 'org.robolectric:robolectric:3.1' + testCompile 'org.robolectric:robolectric:3.1.2' compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" } diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/MangaSyncLoginDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/MangaSyncLoginDialog.kt index 63f20de60..49b46da4b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/MangaSyncLoginDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/MangaSyncLoginDialog.kt @@ -55,14 +55,14 @@ class MangaSyncLoginDialog : LoginDialogPreference() { requestSubscription = sync.login(user, pass) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .subscribe({ error -> - sync.logout() - login.progress = -1 - login.setText(R.string.unknown_error) - }, { + .subscribe({ sync.saveCredentials(user, pass) dialog.dismiss() context.toast(R.string.login_success) + }, { error -> + sync.logout() + login.progress = -1 + login.setText(R.string.unknown_error) }) } diff --git a/build.gradle b/build.gradle index 7075d509e..00ced40a7 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ buildscript { } dependencies { classpath 'com.android.tools.build:gradle:2.1.2' - classpath 'com.github.ben-manes:gradle-versions-plugin:0.12.0' + classpath 'com.github.ben-manes:gradle-versions-plugin:0.13.0' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } From df2b1dbeb13d8ccd411aa19bde5baeebbb1bafcd Mon Sep 17 00:00:00 2001 From: len Date: Sat, 30 Jul 2016 16:18:51 +0200 Subject: [PATCH 05/79] Update travis --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 863820399..587041f38 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,8 +5,8 @@ android: - tools # The BuildTools version used by your project - - build-tools-23.0.3 - - android-23 + - build-tools-24.0.1 + - android-24 - extra-android-m2repository - extra-google-m2repository - extra-android-support From 328f9a70d3b449c10aef6a9f56c2c445af6ba8e9 Mon Sep 17 00:00:00 2001 From: len Date: Sat, 30 Jul 2016 16:25:23 +0200 Subject: [PATCH 06/79] Fix robolectric tests --- .../kanade/tachiyomi/data/library/LibraryUpdateServiceTest.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/test/java/eu/kanade/tachiyomi/data/library/LibraryUpdateServiceTest.kt b/app/src/test/java/eu/kanade/tachiyomi/data/library/LibraryUpdateServiceTest.kt index 139abad41..875735fc4 100644 --- a/app/src/test/java/eu/kanade/tachiyomi/data/library/LibraryUpdateServiceTest.kt +++ b/app/src/test/java/eu/kanade/tachiyomi/data/library/LibraryUpdateServiceTest.kt @@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.data.library import android.app.Application import android.content.Context +import android.os.Build import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.CustomRobolectricGradleTestRunner import eu.kanade.tachiyomi.data.database.models.Chapter @@ -25,7 +26,7 @@ import uy.kohesive.injekt.api.InjektRegistrar import uy.kohesive.injekt.api.addSingleton import java.util.* -@Config(constants = BuildConfig::class) +@Config(constants = BuildConfig::class, sdk = intArrayOf(Build.VERSION_CODES.LOLLIPOP)) @RunWith(CustomRobolectricGradleTestRunner::class) class LibraryUpdateServiceTest { From a5d4f632819d57a9a4adfa18d6ffe35869a16f3b Mon Sep 17 00:00:00 2001 From: len Date: Sat, 30 Jul 2016 16:40:35 +0200 Subject: [PATCH 07/79] Set jdk 8 in travis --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 587041f38..10e3f13e9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,6 +12,9 @@ android: - extra-android-support - extra-google-google_play_services +jdk: + - oraclejdk8 + before_script: - chmod +x gradlew #Build, and run tests From 31b1b83606236817de86040196717e34ab3f303b Mon Sep 17 00:00:00 2001 From: len Date: Sat, 30 Jul 2016 17:43:16 +0200 Subject: [PATCH 08/79] Fix #408 --- .../tachiyomi/ui/reader/ReaderActivity.kt | 54 +++++++++++-------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt index bf90a1081..a87a89596 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt @@ -149,6 +149,13 @@ class ReaderActivity : BaseRxActivity() { super.onSaveInstanceState(outState) } + override fun onWindowFocusChanged(hasFocus: Boolean) { + super.onWindowFocusChanged(hasFocus) + if (hasFocus) { + setMenuVisibility(menuVisible, animate = false) + } + } + override fun onBackPressed() { val chapterToUpdate = presenter.getMangaSyncChapterToUpdate() @@ -463,37 +470,42 @@ class ReaderActivity : BaseRxActivity() { } } - private fun setMenuVisibility(visible: Boolean) { + private fun setMenuVisibility(visible: Boolean, animate: Boolean = true) { menuVisible = visible if (visible) { systemUi?.show() reader_menu.visibility = View.VISIBLE - val toolbarAnimation = AnimationUtils.loadAnimation(this, R.anim.enter_from_top) - toolbarAnimation.setAnimationListener(object : SimpleAnimationListener() { - override fun onAnimationStart(animation: Animation) { - // Fix status bar being translucent the first time it's opened. - if (Build.VERSION.SDK_INT >= 21) { - window.addFlags(FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) + if (animate) { + val toolbarAnimation = AnimationUtils.loadAnimation(this, R.anim.enter_from_top) + toolbarAnimation.setAnimationListener(object : SimpleAnimationListener() { + override fun onAnimationStart(animation: Animation) { + // Fix status bar being translucent the first time it's opened. + if (Build.VERSION.SDK_INT >= 21) { + window.addFlags(FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) + } } - } - }) - toolbar.startAnimation(toolbarAnimation) + }) + toolbar.startAnimation(toolbarAnimation) - val bottomMenuAnimation = AnimationUtils.loadAnimation(this, R.anim.enter_from_bottom) - reader_menu_bottom.startAnimation(bottomMenuAnimation) + val bottomMenuAnimation = AnimationUtils.loadAnimation(this, R.anim.enter_from_bottom) + reader_menu_bottom.startAnimation(bottomMenuAnimation) + } } else { systemUi?.hide() - val toolbarAnimation = AnimationUtils.loadAnimation(this, R.anim.exit_to_top) - toolbarAnimation.setAnimationListener(object : SimpleAnimationListener() { - override fun onAnimationEnd(animation: Animation) { - reader_menu.visibility = View.GONE - } - }) - toolbar.startAnimation(toolbarAnimation) - val bottomMenuAnimation = AnimationUtils.loadAnimation(this, R.anim.exit_to_bottom) - reader_menu_bottom.startAnimation(bottomMenuAnimation) + if (animate) { + val toolbarAnimation = AnimationUtils.loadAnimation(this, R.anim.exit_to_top) + toolbarAnimation.setAnimationListener(object : SimpleAnimationListener() { + override fun onAnimationEnd(animation: Animation) { + reader_menu.visibility = View.GONE + } + }) + toolbar.startAnimation(toolbarAnimation) + + val bottomMenuAnimation = AnimationUtils.loadAnimation(this, R.anim.exit_to_bottom) + reader_menu_bottom.startAnimation(bottomMenuAnimation) + } } } From fbd2235a516dced18dae3542639eec77a2a8ab55 Mon Sep 17 00:00:00 2001 From: len Date: Sat, 30 Jul 2016 20:21:01 +0200 Subject: [PATCH 09/79] Recycle view holders in library. Format fixes --- .../ui/library/LibraryCategoryAdapter.kt | 3 +-- .../ui/library/LibraryCategoryFragment.kt | 26 +++++++++---------- .../tachiyomi/ui/library/LibraryFragment.kt | 10 +++++-- .../tachiyomi/ui/library/LibraryListHolder.kt | 4 +-- .../tachiyomi/ui/library/LibraryPresenter.kt | 8 +++--- 5 files changed, 26 insertions(+), 25 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt index 5320e5be9..d814bbfe1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt @@ -85,10 +85,9 @@ class LibraryCategoryAdapter(val fragment: LibraryCategoryFragment) : */ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LibraryHolder { //depending on preferences, display a list or display a grid - if(parent.id == R.id.library_list) { + if (parent.id == R.id.library_list) { val view = parent.inflate(R.layout.item_library_list) return LibraryListHolder(view, this, fragment) - } else { val view = parent.inflate(R.layout.item_catalogue_grid).apply { card.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, coverHeight) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryFragment.kt index 814dec25a..826059805 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryFragment.kt @@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.ui.library import android.content.res.Configuration import android.os.Bundle +import android.support.v7.widget.GridLayoutManager import android.support.v7.widget.LinearLayoutManager import android.support.v7.widget.RecyclerView import android.view.LayoutInflater @@ -91,8 +92,6 @@ class LibraryCategoryFragment : BaseFragment(), FlexibleViewHolder.OnListItemCli override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedState: Bundle?): View? { return inflater.inflate(R.layout.fragment_library_category, container, false) - - } override fun onViewCreated(view: View, savedState: Bundle?) { @@ -100,14 +99,16 @@ class LibraryCategoryFragment : BaseFragment(), FlexibleViewHolder.OnListItemCli //set up grid recycler.setHasFixedSize(true) + (recycler.layoutManager as GridLayoutManager).recycleChildrenOnDetach = true + recycler.recycledViewPool = libraryFragment.pool recycler.adapter = adapter //set up list library_list.setHasFixedSize(true) - library_list.adapter = adapter library_list.layoutManager = LinearLayoutManager(activity) - - + library_list.recycledViewPool = libraryFragment.pool + (library_list.layoutManager as LinearLayoutManager).recycleChildrenOnDetach = true + library_list.adapter = adapter if (libraryFragment.actionMode != null) { setSelectionMode(FlexibleAdapter.MODE_MULTI) @@ -119,19 +120,19 @@ class LibraryCategoryFragment : BaseFragment(), FlexibleViewHolder.OnListItemCli // Set again the adapter to recalculate the covers height .subscribe { recycler.adapter = adapter } - searchSubscription = libraryPresenter.searchSubject?.subscribe { text -> + searchSubscription = libraryPresenter.searchSubject.subscribe { text -> adapter.searchText = text adapter.updateDataSet() } - toggleViewSubscription = preferences.libraryAsList().asObservable().subscribe {onViewModeChange(it)} + toggleViewSubscription = preferences.libraryAsList().asObservable() + .subscribe { onViewModeChange(it) } - if(libraryPresenter.displayAsList != displayAsList) { + if (libraryPresenter.displayAsList != displayAsList) { library_switcher.showNext() displayAsList = libraryPresenter.displayAsList } - library_switcher.inAnimation = AnimationUtils.loadAnimation(activity, android.R.anim.fade_in) library_switcher.outAnimation = AnimationUtils.loadAnimation(activity, android.R.anim.fade_out) @@ -176,11 +177,8 @@ class LibraryCategoryFragment : BaseFragment(), FlexibleViewHolder.OnListItemCli override fun onResume() { super.onResume() - - libraryMangaSubscription = libraryPresenter.libraryMangaSubject - ?.subscribe { onNextLibraryManga(it) } - + .subscribe { onNextLibraryManga(it) } } override fun onPause() { @@ -310,7 +308,7 @@ class LibraryCategoryFragment : BaseFragment(), FlexibleViewHolder.OnListItemCli fun onViewModeChange(isList: Boolean) { //do nothing if the display does not need to change - if(isList == displayAsList) return + if (isList == displayAsList) return //else change view and display mode library_switcher.showNext() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt index 0916dfd2e..bd5784c41 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt @@ -6,6 +6,7 @@ import android.os.Bundle import android.support.design.widget.TabLayout import android.support.v4.view.ViewPager import android.support.v7.view.ActionMode +import android.support.v7.widget.RecyclerView import android.support.v7.widget.SearchView import android.view.* import com.afollestad.materialdialogs.MaterialDialog @@ -86,6 +87,11 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback */ var isFilterUnread = false + /** + * A pool to share view holders between all the registered categories (fragments). + */ + val pool = RecyclerView.RecycledViewPool() + companion object { /** * Key to change the cover of a manga in [onActivityResult]. @@ -138,7 +144,7 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback if (savedState != null) { activeCategory = savedState.getInt(CATEGORY_KEY) query = savedState.getString(QUERY_KEY) - presenter.searchSubject?.onNext(query) + presenter.searchSubject.onNext(query) } else { activeCategory = presenter.preferences.lastUsedCategory().getOrDefault() } @@ -311,7 +317,7 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback view_pager.post { if (isAdded) tabs.setScrollPosition(view_pager.currentItem, 0f, true) } // Send the manga map to child fragments after the adapter is updated. - presenter.libraryMangaSubject?.onNext(LibraryMangaEvent(mangaMap)) + presenter.libraryMangaSubject.onNext(LibraryMangaEvent(mangaMap)) } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListHolder.kt index 2e561d38c..d0928262b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListHolder.kt @@ -18,8 +18,8 @@ import kotlinx.android.synthetic.main.item_library_list.view.* */ class LibraryListHolder(private val view: View, - private val adapter: LibraryCategoryAdapter, - listener: FlexibleViewHolder.OnListItemClickListener) + private val adapter: LibraryCategoryAdapter, + listener: FlexibleViewHolder.OnListItemClickListener) : LibraryHolder(view, adapter, listener) { /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt index 39efd36d6..5f44089f8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt @@ -39,12 +39,12 @@ class LibraryPresenter : BasePresenter() { /** * Search query of the library. */ - val searchSubject: BehaviorSubject? = BehaviorSubject.create() + val searchSubject: BehaviorSubject = BehaviorSubject.create() /** * Subject to notify the library's viewpager for updates. */ - val libraryMangaSubject: BehaviorSubject? = BehaviorSubject.create() + val libraryMangaSubject: BehaviorSubject = BehaviorSubject.create() /** * Database. @@ -95,9 +95,7 @@ class LibraryPresenter : BasePresenter() { start(GET_LIBRARY) } - - add(preferences.libraryAsList().asObservable().subscribe{setDisplayMode(it)}) - + add(preferences.libraryAsList().asObservable().subscribe { setDisplayMode(it) }) } /** From e95fcf61720906a88d784eba9a4d30d7f50a26b2 Mon Sep 17 00:00:00 2001 From: len Date: Sat, 30 Jul 2016 23:54:32 +0200 Subject: [PATCH 10/79] Dynamic recyclerview inflation for the library view and better swap handling --- .../tachiyomi/ui/library/LibraryAdapter.kt | 1 - .../ui/library/LibraryCategoryAdapter.kt | 17 ++-- .../ui/library/LibraryCategoryFragment.kt | 93 ++++--------------- .../tachiyomi/ui/library/LibraryFragment.kt | 73 +++++++++------ .../tachiyomi/ui/library/LibraryHolder.kt | 6 +- .../tachiyomi/ui/library/LibraryPresenter.kt | 17 +--- .../res/layout/fragment_library_category.xml | 30 +----- .../main/res/layout/library_grid_recycler.xml | 10 ++ .../main/res/layout/library_list_recycler.xml | 10 ++ 9 files changed, 99 insertions(+), 158 deletions(-) create mode 100644 app/src/main/res/layout/library_grid_recycler.xml create mode 100644 app/src/main/res/layout/library_list_recycler.xml diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryAdapter.kt index dcfc8539e..8cf6540d4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryAdapter.kt @@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.ui.library import android.support.v4.app.Fragment import android.support.v4.app.FragmentManager - import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.ui.base.adapter.SmartFragmentStatePagerAdapter diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt index d814bbfe1..eb3c528da 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt @@ -8,7 +8,7 @@ import eu.davidea.flexibleadapter.FlexibleAdapter import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.util.inflate -import kotlinx.android.synthetic.main.fragment_library_category.* +import eu.kanade.tachiyomi.widget.AutofitRecyclerView import kotlinx.android.synthetic.main.item_catalogue_grid.view.* import java.util.* @@ -85,15 +85,16 @@ class LibraryCategoryAdapter(val fragment: LibraryCategoryFragment) : */ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LibraryHolder { //depending on preferences, display a list or display a grid - if (parent.id == R.id.library_list) { - val view = parent.inflate(R.layout.item_library_list) - return LibraryListHolder(view, this, fragment) - } else { + if (parent is AutofitRecyclerView) { val view = parent.inflate(R.layout.item_catalogue_grid).apply { + val coverHeight = parent.itemWidth / 3 * 4 card.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, coverHeight) gradient.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, coverHeight / 2, Gravity.BOTTOM) } return LibraryGridHolder(view, this, fragment) + } else { + val view = parent.inflate(R.layout.item_library_list) + return LibraryListHolder(view, this, fragment) } } @@ -112,10 +113,4 @@ class LibraryCategoryAdapter(val fragment: LibraryCategoryFragment) : holder.itemView.isActivated = isSelected(position) } - /** - * Property to return the height for the covers based on the width to keep an aspect ratio. - */ - val coverHeight: Int - get() = fragment.recycler.itemWidth / 3 * 4 - } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryFragment.kt index 826059805..084e1b135 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryFragment.kt @@ -1,24 +1,23 @@ package eu.kanade.tachiyomi.ui.library -import android.content.res.Configuration import android.os.Bundle -import android.support.v7.widget.GridLayoutManager import android.support.v7.widget.LinearLayoutManager import android.support.v7.widget.RecyclerView import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.view.animation.AnimationUtils -import com.f2prateek.rx.preferences.Preference import eu.davidea.flexibleadapter.FlexibleAdapter import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.library.LibraryUpdateService import eu.kanade.tachiyomi.data.preference.PreferencesHelper +import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder import eu.kanade.tachiyomi.ui.base.fragment.BaseFragment import eu.kanade.tachiyomi.ui.manga.MangaActivity +import eu.kanade.tachiyomi.util.inflate import eu.kanade.tachiyomi.util.toast +import eu.kanade.tachiyomi.widget.AutofitRecyclerView import kotlinx.android.synthetic.main.fragment_library_category.* import rx.Subscription import uy.kohesive.injekt.injectLazy @@ -50,26 +49,11 @@ class LibraryCategoryFragment : BaseFragment(), FlexibleViewHolder.OnListItemCli */ private var libraryMangaSubscription: Subscription? = null - /** - * Subscription of the number of manga per row. - */ - private var numColumnsSubscription: Subscription? = null - - /** - * subscription to view toggle - */ - private var toggleViewSubscription: Subscription? = null - /** * Subscription of the library search. */ private var searchSubscription: Subscription? = null - /** - * display mode - */ - private var displayAsList: Boolean = false - companion object { /** * Key to save and restore [position] from a [Bundle]. @@ -97,45 +81,31 @@ class LibraryCategoryFragment : BaseFragment(), FlexibleViewHolder.OnListItemCli override fun onViewCreated(view: View, savedState: Bundle?) { adapter = LibraryCategoryAdapter(this) - //set up grid - recycler.setHasFixedSize(true) - (recycler.layoutManager as GridLayoutManager).recycleChildrenOnDetach = true - recycler.recycledViewPool = libraryFragment.pool - recycler.adapter = adapter + val recycler = if (preferences.libraryAsList().getOrDefault()) { + (swipe_refresh.inflate(R.layout.library_grid_recycler) as AutofitRecyclerView).apply { + spanCount = libraryFragment.mangaPerRow + } + } else { + (swipe_refresh.inflate(R.layout.library_list_recycler) as RecyclerView).apply { + layoutManager = LinearLayoutManager(context) + } + } - //set up list - library_list.setHasFixedSize(true) - library_list.layoutManager = LinearLayoutManager(activity) - library_list.recycledViewPool = libraryFragment.pool - (library_list.layoutManager as LinearLayoutManager).recycleChildrenOnDetach = true - library_list.adapter = adapter + (recycler.layoutManager as LinearLayoutManager).recycleChildrenOnDetach = true + recycler.recycledViewPool = libraryFragment.pool + recycler.setHasFixedSize(true) + recycler.adapter = adapter + swipe_refresh.addView(recycler) if (libraryFragment.actionMode != null) { setSelectionMode(FlexibleAdapter.MODE_MULTI) } - numColumnsSubscription = getColumnsPreferenceForCurrentOrientation().asObservable() - .doOnNext { recycler.spanCount = it } - .skip(1) - // Set again the adapter to recalculate the covers height - .subscribe { recycler.adapter = adapter } - searchSubscription = libraryPresenter.searchSubject.subscribe { text -> adapter.searchText = text adapter.updateDataSet() } - toggleViewSubscription = preferences.libraryAsList().asObservable() - .subscribe { onViewModeChange(it) } - - if (libraryPresenter.displayAsList != displayAsList) { - library_switcher.showNext() - displayAsList = libraryPresenter.displayAsList - } - - library_switcher.inAnimation = AnimationUtils.loadAnimation(activity, android.R.anim.fade_in) - library_switcher.outAnimation = AnimationUtils.loadAnimation(activity, android.R.anim.fade_out) - if (savedState != null) { position = savedState.getInt(POSITION_KEY) adapter.onRestoreInstanceState(savedState) @@ -157,9 +127,9 @@ class LibraryCategoryFragment : BaseFragment(), FlexibleViewHolder.OnListItemCli // Double the distance required to trigger sync swipe_refresh.setDistanceToTriggerSync((2 * 64 * resources.displayMetrics.density).toInt()) swipe_refresh.setOnRefreshListener { - if (!LibraryUpdateService.isRunning(activity)) { + if (!LibraryUpdateService.isRunning(context)) { libraryPresenter.categories.getOrNull(position)?.let { - LibraryUpdateService.start(activity, true, it) + LibraryUpdateService.start(context, true, it) context.toast(R.string.updating_category) } } @@ -169,9 +139,7 @@ class LibraryCategoryFragment : BaseFragment(), FlexibleViewHolder.OnListItemCli } override fun onDestroyView() { - numColumnsSubscription?.unsubscribe() searchSubscription?.unsubscribe() - toggleViewSubscription?.unsubscribe() super.onDestroyView() } @@ -250,7 +218,7 @@ class LibraryCategoryFragment : BaseFragment(), FlexibleViewHolder.OnListItemCli libraryPresenter.onOpenManga() // Create a new activity with the manga. - val intent = MangaActivity.newIntent(activity, manga) + val intent = MangaActivity.newIntent(context, manga) startActivity(intent) } @@ -282,18 +250,6 @@ class LibraryCategoryFragment : BaseFragment(), FlexibleViewHolder.OnListItemCli } } - /** - * Returns a preference for the number of manga per row based on the current orientation. - * - * @return the preference. - */ - fun getColumnsPreferenceForCurrentOrientation(): Preference { - return if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) - libraryPresenter.preferences.portraitColumns() - else - libraryPresenter.preferences.landscapeColumns() - } - /** * Sets the mode for the adapter. * @@ -306,15 +262,6 @@ class LibraryCategoryFragment : BaseFragment(), FlexibleViewHolder.OnListItemCli } } - fun onViewModeChange(isList: Boolean) { - //do nothing if the display does not need to change - if (isList == displayAsList) return - - //else change view and display mode - library_switcher.showNext() - displayAsList = isList - } - /** * Property to get the library fragment. */ diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt index bd5784c41..0c3f997f7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt @@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.ui.library import android.app.Activity import android.content.Intent +import android.content.res.Configuration import android.os.Bundle import android.support.design.widget.TabLayout import android.support.v4.view.ViewPager @@ -10,6 +11,7 @@ import android.support.v7.widget.RecyclerView import android.support.v7.widget.SearchView import android.view.* import com.afollestad.materialdialogs.MaterialDialog +import com.f2prateek.rx.preferences.Preference import eu.davidea.flexibleadapter.FlexibleAdapter import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Category @@ -24,6 +26,7 @@ import eu.kanade.tachiyomi.util.toast import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.fragment_library.* import nucleus.factory.RequiresPresenter +import rx.Subscription import uy.kohesive.injekt.injectLazy import java.io.IOException @@ -61,11 +64,6 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback */ private var query: String? = null - /** - * Display mode of the library (list or grid mode). - */ - private var displayMode: MenuItem? = null - /** * Action mode for manga selection. */ @@ -87,10 +85,18 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback */ var isFilterUnread = false + /** + * Number of manga per row in grid mode. + */ + var mangaPerRow = 0 + private set + /** * A pool to share view holders between all the registered categories (fragments). */ - val pool = RecyclerView.RecycledViewPool() + var pool = RecyclerView.RecycledViewPool() + + private var numColumnsSubscription: Subscription? = null companion object { /** @@ -148,6 +154,12 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback } else { activeCategory = presenter.preferences.lastUsedCategory().getOrDefault() } + + numColumnsSubscription = getColumnsPreferenceForCurrentOrientation().asObservable() + .doOnNext { mangaPerRow = it } + .skip(1) + // Set again the adapter to recalculate the covers height + .subscribe { reattachAdapter() } } override fun onResume() { @@ -156,6 +168,7 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback } override fun onDestroyView() { + numColumnsSubscription?.unsubscribe() tabs.setupWithViewPager(null) tabs.visibility = View.GONE super.onDestroyView() @@ -197,17 +210,6 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback } }) - //set the icon for the display mode button - displayMode = menu.findItem(R.id.action_library_display_mode).apply { - val icon = if (preferences.libraryAsList().getOrDefault()) - R.drawable.ic_view_module_white_24dp - else - R.drawable.ic_view_list_white_24dp - - setIcon(icon) - } - - } override fun onOptionsItemSelected(item: MenuItem): Boolean { @@ -257,27 +259,40 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback */ private fun onFilterCheckboxChanged() { presenter.updateLibrary() - adapter.notifyDataSetChanged() adapter.refreshRegisteredAdapters() activity.supportInvalidateOptionsMenu() } /** - * swap display mode + * Swap display mode */ private fun swapDisplayMode() { - presenter.swapDisplayMode() - val isListMode = presenter.displayAsList - val icon = if (isListMode) - R.drawable.ic_view_module_white_24dp - else - R.drawable.ic_view_list_white_24dp - - displayMode?.setIcon(icon) - + reattachAdapter() } + /** + * Reattaches the adapter to the view pager to recreate fragments + */ + private fun reattachAdapter() { + pool.clear() + pool = RecyclerView.RecycledViewPool() + val position = view_pager.currentItem + view_pager.adapter = adapter + view_pager.currentItem = position + } + + /** + * Returns a preference for the number of manga per row based on the current orientation. + * + * @return the preference. + */ + private fun getColumnsPreferenceForCurrentOrientation(): Preference { + return if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) + preferences.portraitColumns() + else + preferences.landscapeColumns() + } /** * Updates the query. @@ -289,7 +304,7 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback // Notify the subject the query has changed. if (isResumed) { - presenter.searchSubject?.onNext(query) + presenter.searchSubject.onNext(query) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.kt index a9f2cfb23..efdd42200 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.kt @@ -11,8 +11,10 @@ import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder * @param listener a listener to react to the single tap and long tap events. */ -abstract class LibraryHolder(private val view: View, adapter: LibraryCategoryAdapter, listener: FlexibleViewHolder.OnListItemClickListener) - : FlexibleViewHolder(view, adapter, listener) { +abstract class LibraryHolder(private val view: View, + adapter: LibraryCategoryAdapter, + listener: FlexibleViewHolder.OnListItemClickListener) +: FlexibleViewHolder(view, adapter, listener) { /** * Method called from [LibraryCategoryAdapter.onBindViewHolder]. It updates the data for this diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt index 5f44089f8..2173be875 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt @@ -71,12 +71,6 @@ class LibraryPresenter : BasePresenter() { */ val downloadManager: DownloadManager by injectLazy() - /** - * display the library as a list? - */ - var displayAsList: Boolean = false - private set - companion object { /** * Id of the restartable that listens for library updates. @@ -95,16 +89,6 @@ class LibraryPresenter : BasePresenter() { start(GET_LIBRARY) } - add(preferences.libraryAsList().asObservable().subscribe { setDisplayMode(it) }) - } - - /** - * Sets the display mode - * - * @param asList display as list or not - */ - fun setDisplayMode(asList: Boolean) { - displayAsList = asList } /** @@ -305,6 +289,7 @@ class LibraryPresenter : BasePresenter() { * Changes the active display mode. */ fun swapDisplayMode() { + val displayAsList = preferences.libraryAsList().getOrDefault() preferences.libraryAsList().set(!displayAsList) } diff --git a/app/src/main/res/layout/fragment_library_category.xml b/app/src/main/res/layout/fragment_library_category.xml index bc31c39c8..a0614ea62 100644 --- a/app/src/main/res/layout/fragment_library_category.xml +++ b/app/src/main/res/layout/fragment_library_category.xml @@ -1,36 +1,14 @@ - + - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/library_grid_recycler.xml b/app/src/main/res/layout/library_grid_recycler.xml new file mode 100644 index 000000000..fa4563430 --- /dev/null +++ b/app/src/main/res/layout/library_grid_recycler.xml @@ -0,0 +1,10 @@ + + diff --git a/app/src/main/res/layout/library_list_recycler.xml b/app/src/main/res/layout/library_list_recycler.xml new file mode 100644 index 000000000..d3dfbc044 --- /dev/null +++ b/app/src/main/res/layout/library_list_recycler.xml @@ -0,0 +1,10 @@ + + + From be521804c8a907dc018a5ef69c87e7c665ab4373 Mon Sep 17 00:00:00 2001 From: len Date: Sun, 31 Jul 2016 00:05:05 +0200 Subject: [PATCH 11/79] Fix inverted if condition --- .../tachiyomi/ui/library/LibraryCategoryFragment.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryFragment.kt index 084e1b135..7b4916a8d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryFragment.kt @@ -82,13 +82,13 @@ class LibraryCategoryFragment : BaseFragment(), FlexibleViewHolder.OnListItemCli adapter = LibraryCategoryAdapter(this) val recycler = if (preferences.libraryAsList().getOrDefault()) { - (swipe_refresh.inflate(R.layout.library_grid_recycler) as AutofitRecyclerView).apply { - spanCount = libraryFragment.mangaPerRow - } - } else { (swipe_refresh.inflate(R.layout.library_list_recycler) as RecyclerView).apply { layoutManager = LinearLayoutManager(context) } + } else { + (swipe_refresh.inflate(R.layout.library_grid_recycler) as AutofitRecyclerView).apply { + spanCount = libraryFragment.mangaPerRow + } } (recycler.layoutManager as LinearLayoutManager).recycleChildrenOnDetach = true From b6a06189fb7f260ecd2a47da62b99442027bd257 Mon Sep 17 00:00:00 2001 From: len Date: Sun, 31 Jul 2016 01:01:25 +0200 Subject: [PATCH 12/79] Fix text overlapping, make icons a bit bigger --- app/src/main/res/layout/item_library_list.xml | 54 ++++++++++++------- .../main/res/layout/library_list_recycler.xml | 1 - 2 files changed, 34 insertions(+), 21 deletions(-) diff --git a/app/src/main/res/layout/item_library_list.xml b/app/src/main/res/layout/item_library_list.xml index cad62cfd0..d846ab9d0 100644 --- a/app/src/main/res/layout/item_library_list.xml +++ b/app/src/main/res/layout/item_library_list.xml @@ -1,42 +1,56 @@ + tools:src="@drawable/icon"/> - - - + android:paddingEnd="@dimen/material_component_lists_right_padding"> + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/library_list_recycler.xml b/app/src/main/res/layout/library_list_recycler.xml index d3dfbc044..8f5dc91b3 100644 --- a/app/src/main/res/layout/library_list_recycler.xml +++ b/app/src/main/res/layout/library_list_recycler.xml @@ -5,6 +5,5 @@ android:id="@+id/library_list" android:layout_width="match_parent" android:layout_height="match_parent" - android:paddingTop="@dimen/material_component_lists_padding_above_list" tools:listitem="@layout/item_library_list" /> From 4200409f79f4df2647a3da6c1b53a85168dca3a5 Mon Sep 17 00:00:00 2001 From: len Date: Sun, 31 Jul 2016 14:07:12 +0200 Subject: [PATCH 13/79] Fix crashes introduced yesterday --- .../ui/library/LibraryCategoryFragment.kt | 4 +++- .../tachiyomi/ui/library/LibraryFragment.kt | 21 +++++++++++-------- .../tachiyomi/widget/AutofitRecyclerView.kt | 4 ++-- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryFragment.kt index 7b4916a8d..57c100dc0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryFragment.kt @@ -91,7 +91,9 @@ class LibraryCategoryFragment : BaseFragment(), FlexibleViewHolder.OnListItemCli } } - (recycler.layoutManager as LinearLayoutManager).recycleChildrenOnDetach = true + // This crashes when opening a manga after changing categories, but then viewholders aren't + // recycled between pages. It may be fixed if this fragment is replaced with a custom view. + //(recycler.layoutManager as LinearLayoutManager).recycleChildrenOnDetach = true recycler.recycledViewPool = libraryFragment.pool recycler.setHasFixedSize(true) recycler.adapter = adapter diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt index 0c3f997f7..fe78be102 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt @@ -94,7 +94,10 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback /** * A pool to share view holders between all the registered categories (fragments). */ - var pool = RecyclerView.RecycledViewPool() + var pool = RecyclerView.RecycledViewPool().apply { setMaxRecycledViews(0, 20) } + private set(value) { + field = value.apply { setMaxRecycledViews(0, 20) } + } private var numColumnsSubscription: Subscription? = null @@ -127,8 +130,8 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback override fun onCreate(savedState: Bundle?) { super.onCreate(savedState) setHasOptionsMenu(true) - isFilterDownloaded = presenter.preferences.filterDownloaded().get() as Boolean - isFilterUnread = presenter.preferences.filterUnread().get() as Boolean + isFilterDownloaded = preferences.filterDownloaded().get() as Boolean + isFilterUnread = preferences.filterUnread().get() as Boolean } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedState: Bundle?): View? { @@ -142,7 +145,7 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback view_pager.adapter = adapter view_pager.addOnPageChangeListener(object : ViewPager.SimpleOnPageChangeListener() { override fun onPageSelected(position: Int) { - presenter.preferences.lastUsedCategory().set(position) + preferences.lastUsedCategory().set(position) } }) tabs.setupWithViewPager(view_pager) @@ -152,7 +155,7 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback query = savedState.getString(QUERY_KEY) presenter.searchSubject.onNext(query) } else { - activeCategory = presenter.preferences.lastUsedCategory().getOrDefault() + activeCategory = preferences.lastUsedCategory().getOrDefault() } numColumnsSubscription = getColumnsPreferenceForCurrentOrientation().asObservable() @@ -218,7 +221,7 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback // Change unread filter status. isFilterUnread = !isFilterUnread // Update settings. - presenter.preferences.filterUnread().set(isFilterUnread) + preferences.filterUnread().set(isFilterUnread) // Apply filter. onFilterCheckboxChanged() } @@ -226,7 +229,7 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback // Change downloaded filter status. isFilterDownloaded = !isFilterDownloaded // Update settings. - presenter.preferences.filterDownloaded().set(isFilterDownloaded) + preferences.filterDownloaded().set(isFilterDownloaded) // Apply filter. onFilterCheckboxChanged() } @@ -235,8 +238,8 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback isFilterUnread = false isFilterDownloaded = false // Update settings. - presenter.preferences.filterUnread().set(isFilterUnread) - presenter.preferences.filterDownloaded().set(isFilterDownloaded) + preferences.filterUnread().set(isFilterUnread) + preferences.filterDownloaded().set(isFilterDownloaded) // Apply filter onFilterCheckboxChanged() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/AutofitRecyclerView.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/AutofitRecyclerView.kt index 11a8e1140..62cc4b04c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/AutofitRecyclerView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/AutofitRecyclerView.kt @@ -37,8 +37,8 @@ class AutofitRecyclerView @JvmOverloads constructor(context: Context, attrs: Att override fun onMeasure(widthSpec: Int, heightSpec: Int) { super.onMeasure(widthSpec, heightSpec) if (spanCount == 0 && columnWidth > 0) { - val spanCount = Math.max(1, measuredWidth / columnWidth) - manager.spanCount = spanCount + val count = Math.max(1, measuredWidth / columnWidth) + spanCount = count } } From 97454ca162dbe38b9b1e0fd988f0660055daa104 Mon Sep 17 00:00:00 2001 From: len Date: Mon, 1 Aug 2016 00:09:34 +0200 Subject: [PATCH 14/79] Disable shared holders for now --- .../ui/library/LibraryCategoryFragment.kt | 2 +- .../kanade/tachiyomi/ui/library/LibraryFragment.kt | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryFragment.kt index 57c100dc0..1b28363f2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryFragment.kt @@ -94,7 +94,7 @@ class LibraryCategoryFragment : BaseFragment(), FlexibleViewHolder.OnListItemCli // This crashes when opening a manga after changing categories, but then viewholders aren't // recycled between pages. It may be fixed if this fragment is replaced with a custom view. //(recycler.layoutManager as LinearLayoutManager).recycleChildrenOnDetach = true - recycler.recycledViewPool = libraryFragment.pool + //recycler.recycledViewPool = libraryFragment.pool recycler.setHasFixedSize(true) recycler.adapter = adapter swipe_refresh.addView(recycler) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt index fe78be102..ac0b77b6d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt @@ -7,7 +7,6 @@ import android.os.Bundle import android.support.design.widget.TabLayout import android.support.v4.view.ViewPager import android.support.v7.view.ActionMode -import android.support.v7.widget.RecyclerView import android.support.v7.widget.SearchView import android.view.* import com.afollestad.materialdialogs.MaterialDialog @@ -94,10 +93,11 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback /** * A pool to share view holders between all the registered categories (fragments). */ - var pool = RecyclerView.RecycledViewPool().apply { setMaxRecycledViews(0, 20) } - private set(value) { - field = value.apply { setMaxRecycledViews(0, 20) } - } + // TODO find out why this breaks sometimes +// var pool = RecyclerView.RecycledViewPool().apply { setMaxRecycledViews(0, 20) } +// private set(value) { +// field = value.apply { setMaxRecycledViews(0, 20) } +// } private var numColumnsSubscription: Subscription? = null @@ -278,8 +278,8 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback * Reattaches the adapter to the view pager to recreate fragments */ private fun reattachAdapter() { - pool.clear() - pool = RecyclerView.RecycledViewPool() +// pool.clear() +// pool = RecyclerView.RecycledViewPool() val position = view_pager.currentItem view_pager.adapter = adapter view_pager.currentItem = position From dfb2487640c889bc46a022dad1f2e35ee2a629bc Mon Sep 17 00:00:00 2001 From: len Date: Mon, 22 Aug 2016 12:54:16 +0200 Subject: [PATCH 15/79] Library views recycling --- .../ui/base/adapter/FlexibleViewHolder.kt | 2 +- .../tachiyomi/ui/library/LibraryAdapter.kt | 69 ++--- .../ui/library/LibraryCategoryAdapter.kt | 14 +- .../ui/library/LibraryCategoryFragment.kt | 260 +++++++++--------- .../tachiyomi/ui/library/LibraryFragment.kt | 94 +++---- .../tachiyomi/ui/library/LibraryPresenter.kt | 26 +- .../ui/library/LibrarySelectionEvent.kt | 10 + .../widget/RecyclerViewPagerAdapter.kt | 42 +++ .../main/res/layout/item_library_category.xml | 14 + 9 files changed, 295 insertions(+), 236 deletions(-) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySelectionEvent.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/widget/RecyclerViewPagerAdapter.kt create mode 100644 app/src/main/res/layout/item_library_category.xml diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/adapter/FlexibleViewHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/adapter/FlexibleViewHolder.kt index ed5e39e65..2fe0ddfd6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/adapter/FlexibleViewHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/adapter/FlexibleViewHolder.kt @@ -27,7 +27,7 @@ abstract class FlexibleViewHolder(view: View, return true } - protected fun toggleActivation() { + fun toggleActivation() { itemView.isActivated = adapter.isSelected(adapterPosition) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryAdapter.kt index 8cf6540d4..8519a3279 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryAdapter.kt @@ -1,22 +1,23 @@ package eu.kanade.tachiyomi.ui.library -import android.support.v4.app.Fragment -import android.support.v4.app.FragmentManager +import android.view.View +import android.view.ViewGroup +import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Category -import eu.kanade.tachiyomi.ui.base.adapter.SmartFragmentStatePagerAdapter +import eu.kanade.tachiyomi.util.inflate +import eu.kanade.tachiyomi.widget.RecyclerViewPagerAdapter /** * This adapter stores the categories from the library, used with a ViewPager. * - * @param fm the fragment manager. * @constructor creates an instance of the adapter. */ -class LibraryAdapter(fm: FragmentManager) : SmartFragmentStatePagerAdapter(fm) { +class LibraryAdapter(private val fragment: LibraryFragment) : RecyclerViewPagerAdapter() { /** * The categories to bind in the adapter. */ - var categories: List? = null + var categories: List = emptyList() // This setter helps to not refresh the adapter if the reference to the list doesn't change. set(value) { if (field !== value) { @@ -26,13 +27,34 @@ class LibraryAdapter(fm: FragmentManager) : SmartFragmentStatePagerAdapter(fm) { } /** - * Creates a new fragment for the given position when it's called. + * Creates a new view for this adapter. * - * @param position the position to instantiate. - * @return a fragment for the given position. + * @return a new view. */ - override fun getItem(position: Int): Fragment { - return LibraryCategoryFragment.newInstance(position) + override fun createView(container: ViewGroup): View { + val view = container.inflate(R.layout.item_library_category) as LibraryCategoryFragment + view.onCreate(fragment) + return view + } + + /** + * Binds a view with a position. + * + * @param view the view to bind. + * @param position the position in the adapter. + */ + override fun bindView(view: View, position: Int) { + (view as LibraryCategoryFragment).onBind(categories[position]) + } + + /** + * Recycles a view. + * + * @param view the view to recycle. + * @param position the position in the adapter. + */ + override fun recycleView(view: View, position: Int) { + (view as LibraryCategoryFragment).onRecycle() } /** @@ -41,7 +63,7 @@ class LibraryAdapter(fm: FragmentManager) : SmartFragmentStatePagerAdapter(fm) { * @return the number of categories or 0 if the list is null. */ override fun getCount(): Int { - return categories?.size ?: 0 + return categories.size } /** @@ -51,28 +73,7 @@ class LibraryAdapter(fm: FragmentManager) : SmartFragmentStatePagerAdapter(fm) { * @return the title to display. */ override fun getPageTitle(position: Int): CharSequence { - return categories!![position].name - } - - /** - * Method to enable or disable the action mode (multiple selection) for all the instantiated - * fragments. - * - * @param mode the mode to set. - */ - fun setSelectionMode(mode: Int) { - for (fragment in getRegisteredFragments()) { - (fragment as LibraryCategoryFragment).setSelectionMode(mode) - } - } - - /** - * Notifies the adapters in all the registered fragments to refresh their content. - */ - fun refreshRegisteredAdapters() { - for (fragment in getRegisteredFragments()) { - (fragment as LibraryCategoryFragment).adapter.notifyDataSetChanged() - } + return categories[position].name } } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt index eb3c528da..18e31a65b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt @@ -84,7 +84,7 @@ class LibraryCategoryAdapter(val fragment: LibraryCategoryFragment) : * @return a new view holder for a manga. */ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LibraryHolder { - //depending on preferences, display a list or display a grid + // Depending on preferences, display a list or display a grid if (parent is AutofitRecyclerView) { val view = parent.inflate(R.layout.item_catalogue_grid).apply { val coverHeight = parent.itemWidth / 3 * 4 @@ -96,7 +96,6 @@ class LibraryCategoryAdapter(val fragment: LibraryCategoryFragment) : val view = parent.inflate(R.layout.item_library_list) return LibraryListHolder(view, this, fragment) } - } /** @@ -109,8 +108,17 @@ class LibraryCategoryAdapter(val fragment: LibraryCategoryFragment) : val manga = getItem(position) holder.onSetValues(manga) - //When user scrolls this bind the correct selection status + // When user scrolls this bind the correct selection status holder.itemView.isActivated = isSelected(position) } + /** + * Returns the position in the adapter for the given manga. + * + * @param manga the manga to find. + */ + fun indexOf(manga: Manga): Int { + return mangas.orEmpty().indexOfFirst { it.id == manga.id } + } + } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryFragment.kt index 1b28363f2..6f2778398 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryFragment.kt @@ -1,24 +1,23 @@ package eu.kanade.tachiyomi.ui.library -import android.os.Bundle +import android.content.Context import android.support.v7.widget.LinearLayoutManager import android.support.v7.widget.RecyclerView -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup +import android.util.AttributeSet +import android.widget.FrameLayout import eu.davidea.flexibleadapter.FlexibleAdapter import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.library.LibraryUpdateService import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder -import eu.kanade.tachiyomi.ui.base.fragment.BaseFragment import eu.kanade.tachiyomi.ui.manga.MangaActivity import eu.kanade.tachiyomi.util.inflate import eu.kanade.tachiyomi.util.toast import eu.kanade.tachiyomi.widget.AutofitRecyclerView -import kotlinx.android.synthetic.main.fragment_library_category.* +import kotlinx.android.synthetic.main.item_library_category.view.* import rx.Subscription import uy.kohesive.injekt.injectLazy @@ -26,23 +25,33 @@ import uy.kohesive.injekt.injectLazy * Fragment containing the library manga for a certain category. * Uses R.layout.fragment_library_category. */ -class LibraryCategoryFragment : BaseFragment(), FlexibleViewHolder.OnListItemClickListener { +class LibraryCategoryFragment @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) +: FrameLayout(context, attrs), FlexibleViewHolder.OnListItemClickListener { /** * Preferences. */ - val preferences: PreferencesHelper by injectLazy() + private val preferences: PreferencesHelper by injectLazy() + + /** + * The fragment containing this view. + */ + private lateinit var fragment: LibraryFragment + + /** + * Category for this view. + */ + private lateinit var category: Category + + /** + * Recycler view of the list of manga. + */ + private lateinit var recycler: RecyclerView /** * Adapter to hold the manga in this category. */ - lateinit var adapter: LibraryCategoryAdapter - private set - - /** - * Position in the adapter from [LibraryAdapter]. - */ - private var position: Int = 0 + private lateinit var adapter: LibraryCategoryAdapter /** * Subscription for the library manga. @@ -54,69 +63,30 @@ class LibraryCategoryFragment : BaseFragment(), FlexibleViewHolder.OnListItemCli */ private var searchSubscription: Subscription? = null - companion object { - /** - * Key to save and restore [position] from a [Bundle]. - */ - const val POSITION_KEY = "position_key" + /** + * Subscription of the library selections. + */ + private var selectionSubscription: Subscription? = null - /** - * Creates a new instance of this class. - * - * @param position the position in the adapter from [LibraryAdapter]. - * @return a new instance of [LibraryCategoryFragment]. - */ - fun newInstance(position: Int): LibraryCategoryFragment { - val fragment = LibraryCategoryFragment() - fragment.position = position + fun onCreate(fragment: LibraryFragment) { + this.fragment = fragment - return fragment - } - } - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_library_category, container, false) - } - - override fun onViewCreated(view: View, savedState: Bundle?) { - adapter = LibraryCategoryAdapter(this) - - val recycler = if (preferences.libraryAsList().getOrDefault()) { + recycler = if (preferences.libraryAsList().getOrDefault()) { (swipe_refresh.inflate(R.layout.library_list_recycler) as RecyclerView).apply { layoutManager = LinearLayoutManager(context) } } else { (swipe_refresh.inflate(R.layout.library_grid_recycler) as AutofitRecyclerView).apply { - spanCount = libraryFragment.mangaPerRow + spanCount = fragment.mangaPerRow } } - // This crashes when opening a manga after changing categories, but then viewholders aren't - // recycled between pages. It may be fixed if this fragment is replaced with a custom view. - //(recycler.layoutManager as LinearLayoutManager).recycleChildrenOnDetach = true - //recycler.recycledViewPool = libraryFragment.pool + adapter = LibraryCategoryAdapter(this) + recycler.setHasFixedSize(true) recycler.adapter = adapter swipe_refresh.addView(recycler) - if (libraryFragment.actionMode != null) { - setSelectionMode(FlexibleAdapter.MODE_MULTI) - } - - searchSubscription = libraryPresenter.searchSubject.subscribe { text -> - adapter.searchText = text - adapter.updateDataSet() - } - - if (savedState != null) { - position = savedState.getInt(POSITION_KEY) - adapter.onRestoreInstanceState(savedState) - - if (adapter.mode == FlexibleAdapter.MODE_SINGLE) { - adapter.clearSelection() - } - } - recycler.addOnScrollListener(object : RecyclerView.OnScrollListener() { override fun onScrollStateChanged(recycler: RecyclerView, newState: Int) { // Disable swipe refresh when view is not at the top @@ -130,36 +100,47 @@ class LibraryCategoryFragment : BaseFragment(), FlexibleViewHolder.OnListItemCli swipe_refresh.setDistanceToTriggerSync((2 * 64 * resources.displayMetrics.density).toInt()) swipe_refresh.setOnRefreshListener { if (!LibraryUpdateService.isRunning(context)) { - libraryPresenter.categories.getOrNull(position)?.let { - LibraryUpdateService.start(context, true, it) - context.toast(R.string.updating_category) - } + LibraryUpdateService.start(context, true, category) + context.toast(R.string.updating_category) } // It can be a very long operation, so we disable swipe refresh and show a toast. swipe_refresh.isRefreshing = false } } - override fun onDestroyView() { - searchSubscription?.unsubscribe() - super.onDestroyView() - } + fun onBind(category: Category) { + this.category = category - override fun onResume() { - super.onResume() - libraryMangaSubscription = libraryPresenter.libraryMangaSubject + val presenter = fragment.presenter + + searchSubscription = presenter.searchSubject.subscribe { text -> + adapter.searchText = text + adapter.updateDataSet() + } + + adapter.mode = if (presenter.selectedMangas.isNotEmpty()) { + FlexibleAdapter.MODE_MULTI + } else { + FlexibleAdapter.MODE_SINGLE + } + + libraryMangaSubscription = presenter.libraryMangaSubject .subscribe { onNextLibraryManga(it) } + + selectionSubscription = presenter.selectionSubject + .subscribe { onSelectionChanged(it) } } - override fun onPause() { + fun onRecycle() { + adapter.setItems(emptyList()) + adapter.clearSelection() + } + + override fun onDetachedFromWindow() { + searchSubscription?.unsubscribe() libraryMangaSubscription?.unsubscribe() - super.onPause() - } - - override fun onSaveInstanceState(outState: Bundle) { - outState.putInt(POSITION_KEY, position) - adapter.onSaveInstanceState(outState) - super.onSaveInstanceState(outState) + selectionSubscription?.unsubscribe() + super.onDetachedFromWindow() } /** @@ -169,17 +150,61 @@ class LibraryCategoryFragment : BaseFragment(), FlexibleViewHolder.OnListItemCli * @param event the event received. */ fun onNextLibraryManga(event: LibraryMangaEvent) { - // Get the categories from the parent fragment. - val categories = libraryFragment.adapter.categories ?: return - - // When a category is deleted, the index can be greater than the number of categories. - if (position >= categories.size) return - // Get the manga list for this category. - val mangaForCategory = event.getMangaForCategory(categories[position]) ?: emptyList() + val mangaForCategory = event.getMangaForCategory(category).orEmpty() // Update the category with its manga. adapter.setItems(mangaForCategory) + + if (adapter.mode == FlexibleAdapter.MODE_MULTI) { + fragment.presenter.selectedMangas.forEach { manga -> + val position = adapter.indexOf(manga) + if (position != -1 && !adapter.isSelected(position)) { + adapter.toggleSelection(position) + (recycler.findViewHolderForItemId(manga.id!!) as? LibraryHolder)?.toggleActivation() + } + } + } + } + + /** + * Subscribe to [LibrarySelectionEvent]. When an event is received, it updates the selection + * depending on the type of event received. + * + * @param event the selection event received. + */ + private fun onSelectionChanged(event: LibrarySelectionEvent) { + when (event) { + is LibrarySelectionEvent.Selected -> { + if (adapter.mode != FlexibleAdapter.MODE_MULTI) { + adapter.mode = FlexibleAdapter.MODE_MULTI + } + findAndToggleSelection(event.manga) + } + is LibrarySelectionEvent.Unselected -> { + findAndToggleSelection(event.manga) + if (fragment.presenter.selectedMangas.isEmpty()) { + adapter.mode = FlexibleAdapter.MODE_SINGLE + } + } + is LibrarySelectionEvent.Cleared -> { + adapter.mode = FlexibleAdapter.MODE_SINGLE + adapter.clearSelection() + } + } + } + + /** + * Toggles the selection for the given manga and updates the view if needed. + * + * @param manga the manga to toggle. + */ + private fun findAndToggleSelection(manga: Manga) { + val position = adapter.indexOf(manga) + if (position != -1) { + adapter.toggleSelection(position) + (recycler.findViewHolderForItemId(manga.id!!) as? LibraryHolder)?.toggleActivation() + } } /** @@ -191,7 +216,7 @@ class LibraryCategoryFragment : BaseFragment(), FlexibleViewHolder.OnListItemCli override fun onListItemClick(position: Int): Boolean { // If the action mode is created and the position is valid, toggle the selection. val item = adapter.getItem(position) ?: return false - if (libraryFragment.actionMode != null) { + if (adapter.mode == FlexibleAdapter.MODE_MULTI) { toggleSelection(position) return true } else { @@ -206,7 +231,7 @@ class LibraryCategoryFragment : BaseFragment(), FlexibleViewHolder.OnListItemCli * @param position the position of the element clicked. */ override fun onListItemLongClick(position: Int) { - libraryFragment.createActionModeIfNeeded() + fragment.createActionModeIfNeeded() toggleSelection(position) } @@ -217,63 +242,24 @@ class LibraryCategoryFragment : BaseFragment(), FlexibleViewHolder.OnListItemCli */ private fun openManga(manga: Manga) { // Notify the presenter a manga is being opened. - libraryPresenter.onOpenManga() + fragment.presenter.onOpenManga() // Create a new activity with the manga. val intent = MangaActivity.newIntent(context, manga) - startActivity(intent) + fragment.startActivity(intent) } /** - * Toggles the selection for a manga. + * Tells the presenter to toggle the selection for the given position. * * @param position the position to toggle. */ private fun toggleSelection(position: Int) { - val library = libraryFragment + val manga = adapter.getItem(position) ?: return - // Toggle the selection. - adapter.toggleSelection(position, false) - - // Notify the selection to the presenter. - library.presenter.setSelection(adapter.getItem(position), adapter.isSelected(position)) - - // Get the selected count. - val count = library.presenter.selectedMangas.size - if (count == 0) { - // Destroy action mode if there are no items selected. - library.destroyActionModeIfNeeded() - } else { - // Update action mode with the new selection. - library.setContextTitle(count) - library.setVisibilityOfCoverEdit(count) - library.invalidateActionMode() - } + fragment.presenter.setSelection(manga, !adapter.isSelected(position)) + fragment.invalidateActionMode() } - /** - * Sets the mode for the adapter. - * - * @param mode the mode to set. It should be MODE_SINGLE or MODE_MULTI. - */ - fun setSelectionMode(mode: Int) { - adapter.mode = mode - if (mode == FlexibleAdapter.MODE_SINGLE) { - adapter.clearSelection() - } - } - - /** - * Property to get the library fragment. - */ - private val libraryFragment: LibraryFragment - get() = parentFragment as LibraryFragment - - /** - * Property to get the library presenter. - */ - private val libraryPresenter: LibraryPresenter - get() = libraryFragment.presenter - } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt index ac0b77b6d..62d441e88 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt @@ -11,7 +11,6 @@ import android.support.v7.widget.SearchView import android.view.* import com.afollestad.materialdialogs.MaterialDialog import com.f2prateek.rx.preferences.Preference -import eu.davidea.flexibleadapter.FlexibleAdapter import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.data.database.models.Manga @@ -26,6 +25,7 @@ import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.fragment_library.* import nucleus.factory.RequiresPresenter import rx.Subscription +import timber.log.Timber import uy.kohesive.injekt.injectLazy import java.io.IOException @@ -66,8 +66,7 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback /** * Action mode for manga selection. */ - var actionMode: ActionMode? = null - private set + private var actionMode: ActionMode? = null /** * Selected manga for editing its cover. @@ -91,14 +90,8 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback private set /** - * A pool to share view holders between all the registered categories (fragments). + * Subscription for the number of manga per row. */ - // TODO find out why this breaks sometimes -// var pool = RecyclerView.RecycledViewPool().apply { setMaxRecycledViews(0, 20) } -// private set(value) { -// field = value.apply { setMaxRecycledViews(0, 20) } -// } - private var numColumnsSubscription: Subscription? = null companion object { @@ -141,7 +134,7 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback override fun onViewCreated(view: View, savedState: Bundle?) { setToolbarTitle(getString(R.string.label_library)) - adapter = LibraryAdapter(childFragmentManager) + adapter = LibraryAdapter(this) view_pager.adapter = adapter view_pager.addOnPageChangeListener(object : ViewPager.SimpleOnPageChangeListener() { override fun onPageSelected(position: Int) { @@ -154,6 +147,9 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback activeCategory = savedState.getInt(CATEGORY_KEY) query = savedState.getString(QUERY_KEY) presenter.searchSubject.onNext(query) + if (presenter.selectedMangas.isNotEmpty()) { + createActionModeIfNeeded() + } } else { activeCategory = preferences.lastUsedCategory().getOrDefault() } @@ -261,8 +257,7 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback * Applies filter change */ private fun onFilterCheckboxChanged() { - presenter.updateLibrary() - adapter.refreshRegisteredAdapters() + presenter.resubscribeLibrary() activity.supportInvalidateOptionsMenu() } @@ -278,11 +273,11 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback * Reattaches the adapter to the view pager to recreate fragments */ private fun reattachAdapter() { -// pool.clear() -// pool = RecyclerView.RecycledViewPool() val position = view_pager.currentItem + adapter.recycle = false view_pager.adapter = adapter view_pager.currentItem = position + adapter.recycle = true } /** @@ -323,7 +318,7 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback R.string.information_empty_library, R.drawable.ic_book_black_128dp) // Get the current active category. - val activeCat = if (adapter.categories != null) view_pager.currentItem else activeCategory + val activeCat = if (adapter.categories.isNotEmpty()) view_pager.currentItem else activeCategory // Set the categories adapter.categories = categories @@ -339,31 +334,42 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback } /** - * Sets the title of the action mode. - * - * @param count the number of items selected. + * Creates the action mode if it's not created already. */ - fun setContextTitle(count: Int) { - actionMode?.title = getString(R.string.label_selected, count) + fun createActionModeIfNeeded() { + if (actionMode == null) { + actionMode = activity.startSupportActionMode(this) + } } /** - * Sets the visibility of the edit cover item. - * - * @param count the number of items selected. + * Destroys the action mode. */ - fun setVisibilityOfCoverEdit(count: Int) { - // If count = 1 display edit button - actionMode?.menu?.findItem(R.id.action_edit_cover)?.isVisible = count == 1 + fun destroyActionModeIfNeeded() { + actionMode?.finish() + } + + /** + * Invalidates the action mode, forcing it to refresh its content. + */ + fun invalidateActionMode() { + actionMode?.invalidate() } override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean { mode.menuInflater.inflate(R.menu.library_selection, menu) - adapter.setSelectionMode(FlexibleAdapter.MODE_MULTI) return true } override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean { + val count = presenter.selectedMangas.size + if (count == 0) { + // Destroy action mode if there are no items selected. + destroyActionModeIfNeeded() + } else { + mode.title = getString(R.string.label_selected, count) + menu.findItem(R.id.action_edit_cover)?.isVisible = count == 1 + } return false } @@ -381,18 +387,10 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback } override fun onDestroyActionMode(mode: ActionMode) { - adapter.setSelectionMode(FlexibleAdapter.MODE_SINGLE) - presenter.selectedMangas.clear() + presenter.clearSelections() actionMode = null } - /** - * Destroys the action mode. - */ - fun destroyActionModeIfNeeded() { - actionMode?.finish() - } - /** * Changes the cover for the selected manga. * @@ -422,14 +420,14 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback context.contentResolver.openInputStream(data.data).use { // Update cover to selected file, show error if something went wrong if (presenter.editCoverWithStream(it, manga)) { - adapter.refreshRegisteredAdapters() + // TODO refresh cover } else { context.toast(R.string.notification_manga_update_failed) } } - } catch (e: IOException) { + } catch (error: IOException) { context.toast(R.string.notification_manga_update_failed) - e.printStackTrace() + Timber.e(error, error.message) } } @@ -476,20 +474,4 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback .show() } - /** - * Creates the action mode if it's not created already. - */ - fun createActionModeIfNeeded() { - if (actionMode == null) { - actionMode = activity.startSupportActionMode(this) - } - } - - /** - * Invalidates the action mode, forcing it to refresh its content. - */ - fun invalidateActionMode() { - actionMode?.invalidate() - } - } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt index 2173be875..51a81cc9e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt @@ -16,6 +16,7 @@ import rx.Observable import rx.android.schedulers.AndroidSchedulers import rx.schedulers.Schedulers import rx.subjects.BehaviorSubject +import rx.subjects.PublishSubject import uy.kohesive.injekt.injectLazy import java.io.IOException import java.io.InputStream @@ -29,22 +30,27 @@ class LibraryPresenter : BasePresenter() { /** * Categories of the library. */ - lateinit var categories: List + var categories: List = emptyList() /** * Currently selected manga. */ - var selectedMangas = mutableListOf() + val selectedMangas = mutableListOf() /** * Search query of the library. */ - val searchSubject: BehaviorSubject = BehaviorSubject.create() + val searchSubject: BehaviorSubject = BehaviorSubject.create() /** * Subject to notify the library's viewpager for updates. */ - val libraryMangaSubject: BehaviorSubject = BehaviorSubject.create() + val libraryMangaSubject: BehaviorSubject = BehaviorSubject.create() + + /** + * Subject to notify the UI of selection updates. + */ + val selectionSubject: PublishSubject = PublishSubject.create() /** * Database. @@ -149,7 +155,7 @@ class LibraryPresenter : BasePresenter() { /** * Resubscribes to library. */ - fun updateLibrary() { + fun resubscribeLibrary() { start(GET_LIBRARY) } @@ -219,11 +225,21 @@ class LibraryPresenter : BasePresenter() { fun setSelection(manga: Manga, selected: Boolean) { if (selected) { selectedMangas.add(manga) + selectionSubject.onNext(LibrarySelectionEvent.Selected(manga)) } else { selectedMangas.remove(manga) + selectionSubject.onNext(LibrarySelectionEvent.Unselected(manga)) } } + /** + * Clears all the manga selections and notifies the UI. + */ + fun clearSelections() { + selectedMangas.clear() + selectionSubject.onNext(LibrarySelectionEvent.Cleared()) + } + /** * Returns the common categories for the given list of manga. * diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySelectionEvent.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySelectionEvent.kt new file mode 100644 index 000000000..e490e4364 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySelectionEvent.kt @@ -0,0 +1,10 @@ +package eu.kanade.tachiyomi.ui.library + +import eu.kanade.tachiyomi.data.database.models.Manga + +sealed class LibrarySelectionEvent { + + class Selected(val manga: Manga) : LibrarySelectionEvent() + class Unselected(val manga: Manga) : LibrarySelectionEvent() + class Cleared() : LibrarySelectionEvent() +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/RecyclerViewPagerAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/RecyclerViewPagerAdapter.kt new file mode 100644 index 000000000..6d0044fe8 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/RecyclerViewPagerAdapter.kt @@ -0,0 +1,42 @@ +package eu.kanade.tachiyomi.widget + +import android.support.v4.view.PagerAdapter +import android.view.View +import android.view.ViewGroup +import java.util.* + +abstract class RecyclerViewPagerAdapter : PagerAdapter() { + + private val pool = Stack() + + var recycle = true + set(value) { + if (!value) pool.clear() + field = value + } + + protected abstract fun createView(container: ViewGroup): View + + protected abstract fun bindView(view: View, position: Int) + + protected open fun recycleView(view: View, position: Int) {} + + override fun instantiateItem(container: ViewGroup, position: Int): Any { + val view = if (pool.isNotEmpty()) pool.pop() else createView(container) + bindView(view, position) + container.addView(view) + return view + } + + override fun destroyItem(container: ViewGroup, position: Int, obj: Any) { + val view = obj as View + recycleView(view, position) + container.removeView(view) + if (recycle) pool.push(view) + } + + override fun isViewFromObject(view: View, obj: Any): Boolean { + return view === obj + } + +} \ No newline at end of file diff --git a/app/src/main/res/layout/item_library_category.xml b/app/src/main/res/layout/item_library_category.xml new file mode 100644 index 000000000..8c0e1c974 --- /dev/null +++ b/app/src/main/res/layout/item_library_category.xml @@ -0,0 +1,14 @@ + + + + + + + + \ No newline at end of file From 60b30360377bab1891d3e15f386038d45ae96dff Mon Sep 17 00:00:00 2001 From: len Date: Mon, 22 Aug 2016 12:55:31 +0200 Subject: [PATCH 16/79] Rename fragment to view --- .../java/eu/kanade/tachiyomi/ui/library/LibraryAdapter.kt | 6 +++--- .../kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt | 2 +- .../{LibraryCategoryFragment.kt => LibraryCategoryView.kt} | 2 +- app/src/main/res/layout/item_library_category.xml | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) rename app/src/main/java/eu/kanade/tachiyomi/ui/library/{LibraryCategoryFragment.kt => LibraryCategoryView.kt} (98%) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryAdapter.kt index 8519a3279..7b98e5b99 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryAdapter.kt @@ -32,7 +32,7 @@ class LibraryAdapter(private val fragment: LibraryFragment) : RecyclerViewPagerA * @return a new view. */ override fun createView(container: ViewGroup): View { - val view = container.inflate(R.layout.item_library_category) as LibraryCategoryFragment + val view = container.inflate(R.layout.item_library_category) as LibraryCategoryView view.onCreate(fragment) return view } @@ -44,7 +44,7 @@ class LibraryAdapter(private val fragment: LibraryFragment) : RecyclerViewPagerA * @param position the position in the adapter. */ override fun bindView(view: View, position: Int) { - (view as LibraryCategoryFragment).onBind(categories[position]) + (view as LibraryCategoryView).onBind(categories[position]) } /** @@ -54,7 +54,7 @@ class LibraryAdapter(private val fragment: LibraryFragment) : RecyclerViewPagerA * @param position the position in the adapter. */ override fun recycleView(view: View, position: Int) { - (view as LibraryCategoryFragment).onRecycle() + (view as LibraryCategoryView).onRecycle() } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt index 18e31a65b..1a67ee3a3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt @@ -17,7 +17,7 @@ import java.util.* * * @param fragment the fragment containing this adapter. */ -class LibraryCategoryAdapter(val fragment: LibraryCategoryFragment) : +class LibraryCategoryAdapter(val fragment: LibraryCategoryView) : FlexibleAdapter() { /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryView.kt similarity index 98% rename from app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryFragment.kt rename to app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryView.kt index 6f2778398..fd042ef45 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryView.kt @@ -25,7 +25,7 @@ import uy.kohesive.injekt.injectLazy * Fragment containing the library manga for a certain category. * Uses R.layout.fragment_library_category. */ -class LibraryCategoryFragment @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) +class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : FrameLayout(context, attrs), FlexibleViewHolder.OnListItemClickListener { /** diff --git a/app/src/main/res/layout/item_library_category.xml b/app/src/main/res/layout/item_library_category.xml index 8c0e1c974..df6b7ce5a 100644 --- a/app/src/main/res/layout/item_library_category.xml +++ b/app/src/main/res/layout/item_library_category.xml @@ -1,5 +1,5 @@ - @@ -11,4 +11,4 @@ - \ No newline at end of file + \ No newline at end of file From 4171e87b4bb94b374c30655ae0ef8239ec4da469 Mon Sep 17 00:00:00 2001 From: Gilfar Date: Sun, 28 Aug 2016 11:38:37 +0200 Subject: [PATCH 17/79] update Mangasee chapter selector (#429) --- .../eu/kanade/tachiyomi/data/source/online/english/Mangasee.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangasee.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangasee.kt index 697f1b165..7cc85e813 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangasee.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangasee.kt @@ -77,7 +77,7 @@ class Mangasee(context: Context, override val id: Int) : ParsedOnlineSource(cont else -> Manga.UNKNOWN } - override fun chapterListSelector() = "div.row > div > div.row:has(a.chapter_link[alt])" + override fun chapterListSelector() = "div.row > div > div.row > div > div.row:has(a.chapter_link[alt])" override fun chapterFromElement(element: Element, chapter: Chapter) { val urlElement = element.select("a").first() From 2fb3b505355c8635d0a64c6fcfb28f884169a0e5 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Sun, 28 Aug 2016 22:59:00 +0200 Subject: [PATCH 18/79] Add genre filter for catalogue (#428) * Add genre filter for catalogue * Implement genre filter for batoto * hardcode filters for sources * swtich filter id to string * reset filters when switching sources * Add filter support to mangafox * Catalogue changes * Indefinite snackbar on error, use plain subscriptions in catalogue presenter --- .../eu/kanade/tachiyomi/data/source/Source.kt | 1 - .../data/source/online/OnlineSource.kt | 22 +- .../data/source/online/ParsedOnlineSource.kt | 3 +- .../data/source/online/YamlOnlineSource.kt | 11 +- .../data/source/online/english/Batoto.kt | 74 +++++- .../data/source/online/english/Kissmanga.kt | 77 +++++- .../data/source/online/english/Mangafox.kt | 43 +++- .../data/source/online/english/Mangahere.kt | 2 +- .../data/source/online/english/Mangasee.kt | 2 +- .../source/online/english/Readmangatoday.kt | 10 +- .../data/source/online/german/WieManga.kt | 2 +- .../data/source/online/russian/Mangachan.kt | 2 +- .../data/source/online/russian/Mintmanga.kt | 2 +- .../data/source/online/russian/Readmanga.kt | 2 +- .../ui/catalogue/CatalogueFragment.kt | 96 +++++--- .../tachiyomi/ui/catalogue/CataloguePager.kt | 41 ++++ .../ui/catalogue/CataloguePresenter.kt | 232 +++++++++--------- .../java/eu/kanade/tachiyomi/util/RxPager.kt | 21 -- .../main/res/layout/fragment_catalogue.xml | 85 ++++--- app/src/main/res/menu/catalogue_list.xml | 6 + app/src/main/res/values/strings.xml | 1 + 21 files changed, 484 insertions(+), 251 deletions(-) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePager.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/util/RxPager.kt diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/Source.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/Source.kt index d7fd5c5c1..ba196a51f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/Source.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/Source.kt @@ -47,5 +47,4 @@ interface Source { * @param page the page. */ fun fetchImage(page: Page): Observable - } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/OnlineSource.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/OnlineSource.kt index fa9759b82..8daa87703 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/OnlineSource.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/OnlineSource.kt @@ -58,6 +58,11 @@ abstract class OnlineSource(context: Context) : Source { */ val headers by lazy { headersBuilder().build() } + /** + * Genre filters. + */ + val filters by lazy { getFilterList() } + /** * Default network client for doing requests. */ @@ -126,11 +131,11 @@ abstract class OnlineSource(context: Context) : Source { * the current page and the next page url. * @param query the search query. */ - open fun fetchSearchManga(page: MangasPage, query: String): Observable = client - .newCall(searchMangaRequest(page, query)) + open fun fetchSearchManga(page: MangasPage, query: String, filters: List): Observable = client + .newCall(searchMangaRequest(page, query, filters)) .asObservable() .map { response -> - searchMangaParse(response, page, query) + searchMangaParse(response, page, query, filters) page } @@ -141,9 +146,9 @@ abstract class OnlineSource(context: Context) : Source { * @param page the page object. * @param query the search query. */ - open protected fun searchMangaRequest(page: MangasPage, query: String): Request { + open protected fun searchMangaRequest(page: MangasPage, query: String, filters: List): Request { if (page.page == 1) { - page.url = searchMangaInitialUrl(query) + page.url = searchMangaInitialUrl(query, filters) } return GET(page.url, headers) } @@ -153,7 +158,7 @@ abstract class OnlineSource(context: Context) : Source { * * @param query the search query. */ - abstract protected fun searchMangaInitialUrl(query: String): String + abstract protected fun searchMangaInitialUrl(query: String, filters: List): String /** * Parse the response from the site. It should add a list of manga and the absolute url to the @@ -163,7 +168,7 @@ abstract class OnlineSource(context: Context) : Source { * @param page the page object to be filled. * @param query the search query. */ - abstract protected fun searchMangaParse(response: Response, page: MangasPage, query: String) + abstract protected fun searchMangaParse(response: Response, page: MangasPage, query: String, filters: List) /** * Returns an observable with the updated details for a manga. Normally it's not needed to @@ -428,4 +433,7 @@ abstract class OnlineSource(context: Context) : Source { } + data class Filter(val id: String, val name: String) + + open fun getFilterList(): List = emptyList() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/ParsedOnlineSource.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/ParsedOnlineSource.kt index 2c2b8de14..639438d44 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/ParsedOnlineSource.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/ParsedOnlineSource.kt @@ -64,7 +64,7 @@ abstract class ParsedOnlineSource(context: Context) : OnlineSource(context) { * @param page the page object to be filled. * @param query the search query. */ - override fun searchMangaParse(response: Response, page: MangasPage, query: String) { + override fun searchMangaParse(response: Response, page: MangasPage, query: String, filters: List) { val document = response.asJsoup() for (element in document.select(searchMangaSelector())) { Manga.create(id).apply { @@ -179,5 +179,4 @@ abstract class ParsedOnlineSource(context: Context) : OnlineSource(context) { * @param document the parsed document. */ abstract protected fun imageUrlParse(document: Document): String - } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/YamlOnlineSource.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/YamlOnlineSource.kt index 21f8c5919..70ba85b5f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/YamlOnlineSource.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/YamlOnlineSource.kt @@ -5,6 +5,7 @@ import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.network.GET import eu.kanade.tachiyomi.data.network.POST +import eu.kanade.tachiyomi.data.source.Source import eu.kanade.tachiyomi.data.source.getLanguages import eu.kanade.tachiyomi.data.source.model.MangasPage import eu.kanade.tachiyomi.data.source.model.Page @@ -14,6 +15,7 @@ import okhttp3.Request import okhttp3.Response import org.jsoup.Jsoup import org.jsoup.nodes.Element +import rx.Observable import java.text.SimpleDateFormat import java.util.* @@ -68,9 +70,9 @@ class YamlOnlineSource(context: Context, mappings: Map<*, *>) : OnlineSource(con } } - override fun searchMangaRequest(page: MangasPage, query: String): Request { + override fun searchMangaRequest(page: MangasPage, query: String, filters: List): Request { if (page.page == 1) { - page.url = searchMangaInitialUrl(query) + page.url = searchMangaInitialUrl(query, filters) } return when (map.search.method?.toLowerCase()) { "post" -> POST(page.url, headers, map.search.createForm()) @@ -78,9 +80,9 @@ class YamlOnlineSource(context: Context, mappings: Map<*, *>) : OnlineSource(con } } - override fun searchMangaInitialUrl(query: String) = map.search.url.replace("\$query", query) + override fun searchMangaInitialUrl(query: String, filters: List) = map.search.url.replace("\$query", query) - override fun searchMangaParse(response: Response, page: MangasPage, query: String) { + override fun searchMangaParse(response: Response, page: MangasPage, query: String, filters: List) { val document = response.asJsoup() for (element in document.select(map.search.manga_css)) { Manga.create(id).apply { @@ -184,5 +186,4 @@ class YamlOnlineSource(context: Context, mappings: Map<*, *>) : OnlineSource(con throw Exception("image_regex and image_css are null") } } - } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Batoto.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Batoto.kt index 0fe674241..40a296880 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Batoto.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Batoto.kt @@ -84,9 +84,21 @@ class Batoto(context: Context, override val id: Int) : ParsedOnlineSource(contex override fun popularMangaNextPageSelector() = "#show_more_row" - override fun searchMangaInitialUrl(query: String) = "$baseUrl/search_ajax?name=${Uri.encode(query)}&p=1" + override fun searchMangaInitialUrl(query: String, filters: List) = "$baseUrl/search_ajax?name=${Uri.encode(query)}&order_cond=views&order=desc&p=1&genre_cond=and&genres=${getFilterParams(filters)}" - override fun searchMangaParse(response: Response, page: MangasPage, query: String) { + private fun getFilterParams(filters: List): String = filters + .map { + ";i" + it.id + }.joinToString() + + override fun searchMangaRequest(page: MangasPage, query: String, filters: List): Request { + if (page.page == 1) { + page.url = searchMangaInitialUrl(query, filters) + } + return GET(page.url, headers) + } + + override fun searchMangaParse(response: Response, page: MangasPage, query: String, filters: List) { val document = response.asJsoup() for (element in document.select(searchMangaSelector())) { Manga.create(id).apply { @@ -96,7 +108,7 @@ class Batoto(context: Context, override val id: Int) : ParsedOnlineSource(contex } page.nextPageUrl = document.select(searchMangaNextPageSelector()).first()?.let { - "$baseUrl/search_ajax?name=${Uri.encode(query)}&p=${page.page + 1}" + "$baseUrl/search_ajax?name=${Uri.encode(query)}&p=${page.page + 1}&order_cond=views&order=desc&genre_cond=and&genres=" + getFilterParams(filters) } } @@ -211,7 +223,7 @@ class Batoto(context: Context, override val id: Int) : ParsedOnlineSource(contex val start = pageUrl.indexOf("#") + 1 val end = pageUrl.indexOf("_", start) val id = pageUrl.substring(start, end) - return GET("$baseUrl/areader?id=$id&p=${pageUrl.substring(end+1)}", pageHeaders) + return GET("$baseUrl/areader?id=$id&p=${pageUrl.substring(end + 1)}", pageHeaders) } override fun imageUrlParse(document: Document): String { @@ -219,10 +231,10 @@ class Batoto(context: Context, override val id: Int) : ParsedOnlineSource(contex } override fun login(username: String, password: String) = - client.newCall(GET("$baseUrl/forums/index.php?app=core&module=global§ion=login", headers)) - .asObservable() - .flatMap { doLogin(it, username, password) } - .map { isAuthenticationSuccessful(it) } + client.newCall(GET("$baseUrl/forums/index.php?app=core&module=global§ion=login", headers)) + .asObservable() + .flatMap { doLogin(it, username, password) } + .map { isAuthenticationSuccessful(it) } private fun doLogin(response: Response, username: String, password: String): Observable { val doc = response.asJsoup() @@ -242,7 +254,7 @@ class Batoto(context: Context, override val id: Int) : ParsedOnlineSource(contex } override fun isAuthenticationSuccessful(response: Response) = - response.priorResponse() != null && response.priorResponse().code() == 302 + response.priorResponse() != null && response.priorResponse().code() == 302 override fun isLogged(): Boolean { return network.cookies.get(URI(baseUrl)).any { it.name() == "pass_hash" } @@ -264,4 +276,48 @@ class Batoto(context: Context, override val id: Int) : ParsedOnlineSource(contex } } + // [...document.querySelectorAll("#advanced_options div.genre_buttons")].map((el,i) => { + // const onClick=el.getAttribute('onclick');const id=onClick.substr(14,onClick.length-16);return `Filter("${id}", "${el.textContent.trim()}")` + // }).join(',\n') + // on https://bato.to/search + override fun getFilterList(): List = listOf( + Filter("40", "4-Koma"), + Filter("1", "Action"), + Filter("2", "Adventure"), + Filter("39", "Award Winning"), + Filter("3", "Comedy"), + Filter("41", "Cooking"), + Filter("9", "Doujinshi"), + Filter("10", "Drama"), + Filter("12", "Ecchi"), + Filter("13", "Fantasy"), + Filter("15", "Gender Bender"), + Filter("17", "Harem"), + Filter("20", "Historical"), + Filter("22", "Horror"), + Filter("34", "Josei"), + Filter("27", "Martial Arts"), + Filter("30", "Mecha"), + Filter("42", "Medical"), + Filter("37", "Music"), + Filter("4", "Mystery"), + Filter("38", "Oneshot"), + Filter("5", "Psychological"), + Filter("6", "Romance"), + Filter("7", "School Life"), + Filter("8", "Sci-fi"), + Filter("32", "Seinen"), + Filter("35", "Shoujo"), + Filter("16", "Shoujo Ai"), + Filter("33", "Shounen"), + Filter("19", "Shounen Ai"), + Filter("21", "Slice of Life"), + Filter("23", "Smut"), + Filter("25", "Sports"), + Filter("26", "Supernatural"), + Filter("28", "Tragedy"), + Filter("36", "Webtoon"), + Filter("29", "Yaoi"), + Filter("31", "Yuri") + ) } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Kissmanga.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Kissmanga.kt index 163712425..565cbefb0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Kissmanga.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Kissmanga.kt @@ -42,22 +42,34 @@ class Kissmanga(context: Context, override val id: Int) : ParsedOnlineSource(con override fun popularMangaNextPageSelector() = "li > a:contains(› Next)" - override fun searchMangaRequest(page: MangasPage, query: String): Request { + override fun searchMangaRequest(page: MangasPage, query: String, filters: List): Request { if (page.page == 1) { - page.url = searchMangaInitialUrl(query) + page.url = searchMangaInitialUrl(query, filters) } val form = FormBody.Builder().apply { add("authorArtist", "") add("mangaName", query) add("status", "") - add("genres", "") - }.build() + } - return POST(page.url, headers, form) + val filterIndexes = filters.map { it.id.toInt() } + val maxFilterIndex = filterIndexes.max() + + if (maxFilterIndex !== null) { + for (i in 0..maxFilterIndex) { + form.add("genres", if (filterIndexes.contains(i)) { + "1" + } else { + "0" + }) + } + } + + return POST(page.url, headers, form.build()) } - override fun searchMangaInitialUrl(query: String) = "$baseUrl/AdvanceSearch" + override fun searchMangaInitialUrl(query: String, filters: List) = "$baseUrl/AdvanceSearch" override fun searchMangaSelector() = popularMangaSelector() @@ -73,7 +85,7 @@ class Kissmanga(context: Context, override val id: Int) : ParsedOnlineSource(con manga.author = infoElement.select("p:has(span:contains(Author:)) > a").first()?.text() manga.genre = infoElement.select("p:has(span:contains(Genres:)) > *:gt(0)").text() manga.description = infoElement.select("p:has(span:contains(Summary:)) ~ p").text() - manga.status = infoElement.select("p:has(span:contains(Status:))").first()?.text().orEmpty().let { parseStatus(it)} + manga.status = infoElement.select("p:has(span:contains(Status:))").first()?.text().orEmpty().let { parseStatus(it) } manga.thumbnail_url = document.select(".rightBox:eq(0) img").first()?.attr("src") } @@ -109,10 +121,59 @@ class Kissmanga(context: Context, override val id: Int) : ParsedOnlineSource(con } // Not used - override fun pageListParse(document: Document, pages: MutableList) {} + override fun pageListParse(document: Document, pages: MutableList) { + } override fun imageUrlRequest(page: Page) = GET(page.url) override fun imageUrlParse(document: Document) = "" + // $("select[name=\"genres\"]").map((i,el) => `Filter("${i}", "${$(el).next().text().trim()}")`).get().join(',\n') + // on http://kissmanga.com/AdvanceSearch + override fun getFilterList(): List = listOf( + Filter("0", "Action"), + Filter("1", "Adult"), + Filter("2", "Adventure"), + Filter("3", "Comedy"), + Filter("4", "Comic"), + Filter("5", "Cooking"), + Filter("6", "Doujinshi"), + Filter("7", "Drama"), + Filter("8", "Ecchi"), + Filter("9", "Fantasy"), + Filter("10", "Gender Bender"), + Filter("11", "Harem"), + Filter("12", "Historical"), + Filter("13", "Horror"), + Filter("14", "Josei"), + Filter("15", "Lolicon"), + Filter("16", "Manga"), + Filter("17", "Manhua"), + Filter("18", "Manhwa"), + Filter("19", "Martial Arts"), + Filter("20", "Mature"), + Filter("21", "Mecha"), + Filter("22", "Medical"), + Filter("23", "Music"), + Filter("24", "Mystery"), + Filter("25", "One shot"), + Filter("26", "Psychological"), + Filter("27", "Romance"), + Filter("28", "School Life"), + Filter("29", "Sci-fi"), + Filter("30", "Seinen"), + Filter("31", "Shotacon"), + Filter("32", "Shoujo"), + Filter("33", "Shoujo Ai"), + Filter("34", "Shounen"), + Filter("35", "Shounen Ai"), + Filter("36", "Slice of Life"), + Filter("37", "Smut"), + Filter("38", "Sports"), + Filter("39", "Supernatural"), + Filter("40", "Tragedy"), + Filter("41", "Webtoon"), + Filter("42", "Yaoi"), + Filter("43", "Yuri") + ) } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangafox.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangafox.kt index 17d9f1045..db79dc1e8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangafox.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangafox.kt @@ -36,8 +36,8 @@ class Mangafox(context: Context, override val id: Int) : ParsedOnlineSource(cont override fun popularMangaNextPageSelector() = "a:has(span.next)" - override fun searchMangaInitialUrl(query: String) = - "$baseUrl/search.php?name_method=cw&advopts=1&order=za&sort=views&name=$query&page=1" + override fun searchMangaInitialUrl(query: String, filters: List) = + "$baseUrl/search.php?name_method=cw&advopts=1&order=za&sort=views&name=$query&page=1&${filters.map { it.id + "=1" }.joinToString("&")}" override fun searchMangaSelector() = "table#listing > tbody > tr:gt(0)" @@ -118,4 +118,43 @@ class Mangafox(context: Context, override val id: Int) : ParsedOnlineSource(cont override fun imageUrlParse(document: Document) = document.getElementById("image").attr("src") + // $('select.genres').map((i,el)=>`Filter("${$(el).attr('name')}", "${$(el).next().text().trim()}")`).get().join(',\n') + // on http://kissmanga.com/AdvanceSearch + override fun getFilterList(): List = listOf( + Filter("genres[Action]", "Action"), + Filter("genres[Adult]", "Adult"), + Filter("genres[Adventure]", "Adventure"), + Filter("genres[Comedy]", "Comedy"), + Filter("genres[Doujinshi]", "Doujinshi"), + Filter("genres[Drama]", "Drama"), + Filter("genres[Ecchi]", "Ecchi"), + Filter("genres[Fantasy]", "Fantasy"), + Filter("genres[Gender Bender]", "Gender Bender"), + Filter("genres[Harem]", "Harem"), + Filter("genres[Historical]", "Historical"), + Filter("genres[Horror]", "Horror"), + Filter("genres[Josei]", "Josei"), + Filter("genres[Martial Arts]", "Martial Arts"), + Filter("genres[Mature]", "Mature"), + Filter("genres[Mecha]", "Mecha"), + Filter("genres[Mystery]", "Mystery"), + Filter("genres[One Shot]", "One Shot"), + Filter("genres[Psychological]", "Psychological"), + Filter("genres[Romance]", "Romance"), + Filter("genres[School Life]", "School Life"), + Filter("genres[Sci-fi]", "Sci-fi"), + Filter("genres[Seinen]", "Seinen"), + Filter("genres[Shoujo]", "Shoujo"), + Filter("genres[Shoujo Ai]", "Shoujo Ai"), + Filter("genres[Shounen]", "Shounen"), + Filter("genres[Shounen Ai]", "Shounen Ai"), + Filter("genres[Slice of Life]", "Slice of Life"), + Filter("genres[Smut]", "Smut"), + Filter("genres[Sports]", "Sports"), + Filter("genres[Supernatural]", "Supernatural"), + Filter("genres[Tragedy]", "Tragedy"), + Filter("genres[Webtoons]", "Webtoons"), + Filter("genres[Yaoi]", "Yaoi"), + Filter("genres[Yuri]", "Yuri") + ) } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangahere.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangahere.kt index dd63a5d13..7da6487a7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangahere.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangahere.kt @@ -34,7 +34,7 @@ class Mangahere(context: Context, override val id: Int) : ParsedOnlineSource(con override fun popularMangaNextPageSelector() = "div.next-page > a.next" - override fun searchMangaInitialUrl(query: String) = + override fun searchMangaInitialUrl(query: String, filters: List) = "$baseUrl/search.php?name=$query&page=1&sort=views&order=za" override fun searchMangaSelector() = "div.result_search > dl:has(dt)" diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangasee.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangasee.kt index 7cc85e813..3874f2aff 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangasee.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangasee.kt @@ -47,7 +47,7 @@ class Mangasee(context: Context, override val id: Int) : ParsedOnlineSource(cont override fun popularMangaNextPageSelector() = "ul.pagination > li > a:contains(Next)" - override fun searchMangaInitialUrl(query: String) = + override fun searchMangaInitialUrl(query: String, filters: List) = "$baseUrl/advanced-search/result.php?sortBy=alphabet&direction=ASC&textOnly=no&resPerPage=20&page=1&seriesName=$query" override fun searchMangaSelector() = "div.row > div > div > div > h1" diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Readmangatoday.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Readmangatoday.kt index 7c89ada96..7cd682792 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Readmangatoday.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Readmangatoday.kt @@ -6,8 +6,10 @@ import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.network.POST import eu.kanade.tachiyomi.data.source.EN import eu.kanade.tachiyomi.data.source.Language +import eu.kanade.tachiyomi.data.source.Source import eu.kanade.tachiyomi.data.source.model.MangasPage import eu.kanade.tachiyomi.data.source.model.Page +import eu.kanade.tachiyomi.data.source.online.OnlineSource import eu.kanade.tachiyomi.data.source.online.ParsedOnlineSource import okhttp3.OkHttpClient import okhttp3.Request @@ -38,16 +40,16 @@ class Readmangatoday(context: Context, override val id: Int) : ParsedOnlineSourc override fun popularMangaNextPageSelector() = "div.hot-manga > ul.pagination > li > a:contains(»)" - override fun searchMangaInitialUrl(query: String) = + override fun searchMangaInitialUrl(query: String, filters: List) = "$baseUrl/search" - override fun searchMangaRequest(page: MangasPage, query: String): Request { + override fun searchMangaRequest(page: MangasPage, query: String, filters: List): Request { if (page.page == 1) { - page.url = searchMangaInitialUrl(query) + page.url = searchMangaInitialUrl(query, filters) } - var builder = okhttp3.FormBody.Builder() + val builder = okhttp3.FormBody.Builder() builder.add("query", query) return POST(page.url, headers, builder.build()) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/german/WieManga.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/german/WieManga.kt index d58e5af55..f3f48ab17 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/german/WieManga.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/german/WieManga.kt @@ -36,7 +36,7 @@ class WieManga(context: Context, override val id: Int) : ParsedOnlineSource(cont override fun popularMangaNextPageSelector() = null - override fun searchMangaInitialUrl(query: String) = "$baseUrl/search/?wd=$query" + override fun searchMangaInitialUrl(query: String, filters: List) = "$baseUrl/search/?wd=$query" override fun searchMangaSelector() = ".searchresult td > div" diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Mangachan.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Mangachan.kt index b93cc0867..48e5cb924 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Mangachan.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Mangachan.kt @@ -23,7 +23,7 @@ class Mangachan(context: Context, override val id: Int) : ParsedOnlineSource(con override fun popularMangaInitialUrl() = "$baseUrl/mostfavorites" - override fun searchMangaInitialUrl(query: String) = "$baseUrl/?do=search&subaction=search&story=$query" + override fun searchMangaInitialUrl(query: String, filters: List) = "$baseUrl/?do=search&subaction=search&story=$query" override fun popularMangaSelector() = "div.content_row" diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Mintmanga.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Mintmanga.kt index 25abf0082..ff73f608f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Mintmanga.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Mintmanga.kt @@ -24,7 +24,7 @@ class Mintmanga(context: Context, override val id: Int) : ParsedOnlineSource(con override fun popularMangaInitialUrl() = "$baseUrl/list?sortType=rate" - override fun searchMangaInitialUrl(query: String) = "$baseUrl/search?q=$query" + override fun searchMangaInitialUrl(query: String, filters: List) = "$baseUrl/search?q=$query" override fun popularMangaSelector() = "div.desc" diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Readmanga.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Readmanga.kt index 7bb6cc50f..b645db2d7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Readmanga.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Readmanga.kt @@ -24,7 +24,7 @@ class Readmanga(context: Context, override val id: Int) : ParsedOnlineSource(con override fun popularMangaInitialUrl() = "$baseUrl/list?sortType=rate" - override fun searchMangaInitialUrl(query: String) = "$baseUrl/search?q=$query" + override fun searchMangaInitialUrl(query: String, filters: List) = "$baseUrl/search?q=$query" override fun popularMangaSelector() = "div.desc" diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueFragment.kt index ae62b22b5..e50bb26f4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueFragment.kt @@ -2,13 +2,13 @@ package eu.kanade.tachiyomi.ui.catalogue import android.content.res.Configuration import android.os.Bundle +import android.support.design.widget.Snackbar import android.support.v7.widget.GridLayoutManager import android.support.v7.widget.LinearLayoutManager import android.support.v7.widget.SearchView import android.support.v7.widget.Toolbar import android.view.* import android.view.animation.AnimationUtils -import android.widget.AdapterView import android.widget.ArrayAdapter import android.widget.ProgressBar import android.widget.Spinner @@ -25,6 +25,7 @@ import eu.kanade.tachiyomi.util.snack import eu.kanade.tachiyomi.util.toast import eu.kanade.tachiyomi.widget.DividerItemDecoration import eu.kanade.tachiyomi.widget.EndlessScrollListener +import eu.kanade.tachiyomi.widget.IgnoreFirstSpinnerListener import kotlinx.android.synthetic.main.fragment_catalogue.* import kotlinx.android.synthetic.main.toolbar.* import nucleus.factory.RequiresPresenter @@ -64,7 +65,7 @@ class CatalogueFragment : BaseRxFragment(), FlexibleViewHold /** * Query of the search box. */ - private val query: String? + private val query: String get() = presenter.query /** @@ -92,11 +93,6 @@ class CatalogueFragment : BaseRxFragment(), FlexibleViewHold */ private var numColumnsSubscription: Subscription? = null - /** - * Display mode of the catalogue (list or grid mode). - */ - private var displayMode: MenuItem? = null - /** * Search item. */ @@ -144,7 +140,8 @@ class CatalogueFragment : BaseRxFragment(), FlexibleViewHold catalogue_list.adapter = adapter catalogue_list.layoutManager = llm catalogue_list.addOnScrollListener(listScrollListener) - catalogue_list.addItemDecoration(DividerItemDecoration(context.theme.getResourceDrawable(R.attr.divider_drawable))) + catalogue_list.addItemDecoration( + DividerItemDecoration(context.theme.getResourceDrawable(R.attr.divider_drawable))) if (presenter.isListMode) { switcher.showNext() @@ -166,28 +163,25 @@ class CatalogueFragment : BaseRxFragment(), FlexibleViewHold android.R.layout.simple_spinner_item, presenter.sources) spinnerAdapter.setDropDownViewResource(R.layout.spinner_item) - val onItemSelected = object : AdapterView.OnItemSelectedListener { - override fun onItemSelected(parent: AdapterView<*>, view: View?, position: Int, id: Long) { - val source = spinnerAdapter.getItem(position) - if (!presenter.isValidSource(source)) { - spinner.setSelection(selectedIndex) - context.toast(R.string.source_requires_login) - } else if (source != presenter.source) { - selectedIndex = position - showProgressBar() - glm.scrollToPositionWithOffset(0, 0) - llm.scrollToPositionWithOffset(0, 0) - presenter.setActiveSource(source) - } - } - - override fun onNothingSelected(parent: AdapterView<*>) { + val onItemSelected = IgnoreFirstSpinnerListener { position -> + val source = spinnerAdapter.getItem(position) + if (!presenter.isValidSource(source)) { + spinner.setSelection(selectedIndex) + context.toast(R.string.source_requires_login) + } else if (source != presenter.source) { + selectedIndex = position + showProgressBar() + glm.scrollToPositionWithOffset(0, 0) + llm.scrollToPositionWithOffset(0, 0) + presenter.setActiveSource(source) + activity.invalidateOptionsMenu() } } + selectedIndex = presenter.sources.indexOf(presenter.source) + spinner = Spinner(themedContext).apply { adapter = spinnerAdapter - selectedIndex = presenter.sources.indexOf(presenter.source) setSelection(selectedIndex) onItemSelectedListener = onItemSelected } @@ -205,7 +199,7 @@ class CatalogueFragment : BaseRxFragment(), FlexibleViewHold searchItem = menu.findItem(R.id.action_search).apply { val searchView = actionView as SearchView - if (!query.isNullOrEmpty()) { + if (!query.isBlank()) { expandActionView() searchView.setQuery(query, true) searchView.clearFocus() @@ -223,20 +217,31 @@ class CatalogueFragment : BaseRxFragment(), FlexibleViewHold }) } + // Setup filters button + menu.findItem(R.id.action_set_filter).apply { + if (presenter.source.filters.isEmpty()) { + isEnabled = false + icon.alpha = 128 + } else { + isEnabled = true + icon.alpha = 255 + } + } + // Show next display mode - displayMode = menu.findItem(R.id.action_display_mode).apply { + menu.findItem(R.id.action_display_mode).apply { val icon = if (presenter.isListMode) R.drawable.ic_view_module_white_24dp else R.drawable.ic_view_list_white_24dp setIcon(icon) } - } override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { R.id.action_display_mode -> swapDisplayMode() + R.id.action_set_filter -> showFiltersDialog() else -> return super.onOptionsItemSelected(item) } return true @@ -312,7 +317,7 @@ class CatalogueFragment : BaseRxFragment(), FlexibleViewHold */ fun onAddPage(page: Int, mangas: List) { hideProgressBar() - if (page == 0) { + if (page == 1) { adapter.clear() gridScrollListener.resetScroll() listScrollListener.resetScroll() @@ -329,10 +334,10 @@ class CatalogueFragment : BaseRxFragment(), FlexibleViewHold hideProgressBar() Timber.e(error, error.message) - catalogue_view.snack(error.message ?: "") { + catalogue_view.snack(error.message ?: "", Snackbar.LENGTH_INDEFINITE) { setAction(R.string.action_retry) { showProgressBar() - presenter.retryPage() + presenter.requestNext() } } } @@ -352,11 +357,7 @@ class CatalogueFragment : BaseRxFragment(), FlexibleViewHold fun swapDisplayMode() { presenter.swapDisplayMode() val isListMode = presenter.isListMode - val icon = if (isListMode) - R.drawable.ic_view_module_white_24dp - else - R.drawable.ic_view_list_white_24dp - displayMode?.setIcon(icon) + activity.invalidateOptionsMenu() switcher.showNext() if (!isListMode) { // Initialize mangas if going to grid view @@ -444,4 +445,27 @@ class CatalogueFragment : BaseRxFragment(), FlexibleViewHold }.show() } + /** + * Show the filter dialog for the source. + */ + private fun showFiltersDialog() { + val allFilters = presenter.source.filters + val selectedFilters = presenter.filters + .map { filter -> allFilters.indexOf(filter) } + .toTypedArray() + + MaterialDialog.Builder(context) + .title(R.string.action_set_filter) + .items(allFilters.map { it.name }) + .itemsCallbackMultiChoice(selectedFilters) { dialog, positions, text -> + val newFilters = positions.map { allFilters[it] } + showProgressBar() + presenter.setSourceFilter(newFilters) + true + } + .positiveText(android.R.string.ok) + .negativeText(android.R.string.cancel) + .show() + } + } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePager.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePager.kt new file mode 100644 index 000000000..a10243a85 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePager.kt @@ -0,0 +1,41 @@ +package eu.kanade.tachiyomi.ui.catalogue + +import eu.kanade.tachiyomi.data.source.model.MangasPage +import eu.kanade.tachiyomi.data.source.online.OnlineSource +import eu.kanade.tachiyomi.data.source.online.OnlineSource.Filter +import rx.Observable +import rx.subjects.PublishSubject + +class CataloguePager(val source: OnlineSource, val query: String, val filters: List) { + + private var lastPage: MangasPage? = null + + private val results = PublishSubject.create() + + fun results(): Observable { + return results.asObservable() + } + + fun requestNext(transformer: (Observable) -> Observable): Observable { + val lastPage = lastPage + + val page = if (lastPage == null) + MangasPage(1) + else + MangasPage(lastPage.page + 1).apply { url = lastPage.nextPageUrl!! } + + val observable = if (query.isBlank() && filters.isEmpty()) + source.fetchPopularManga(page) + else + source.fetchSearchManga(page, query, filters) + + return transformer(observable) + .doOnNext { results.onNext(it) } + .doOnNext { this@CataloguePager.lastPage = it } + } + + fun hasNextPage(): Boolean { + return lastPage == null || lastPage?.nextPageUrl != null + } + +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePresenter.kt index 6d1b9426a..e4818f12a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePresenter.kt @@ -12,9 +12,10 @@ import eu.kanade.tachiyomi.data.source.SourceManager import eu.kanade.tachiyomi.data.source.model.MangasPage import eu.kanade.tachiyomi.data.source.online.LoginSource import eu.kanade.tachiyomi.data.source.online.OnlineSource +import eu.kanade.tachiyomi.data.source.online.OnlineSource.Filter import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter -import eu.kanade.tachiyomi.util.RxPager import rx.Observable +import rx.Subscription import rx.android.schedulers.AndroidSchedulers import rx.schedulers.Schedulers import rx.subjects.PublishSubject @@ -64,14 +65,14 @@ class CataloguePresenter : BasePresenter() { private set /** - * Pager containing a list of manga results. + * Active filters. */ - private var pager = RxPager() + var filters: List = emptyList() /** - * Last fetched page from network. + * Pager containing a list of manga results. */ - private var lastMangasPage: MangasPage? = null + private lateinit var pager: CataloguePager /** * Subject that initializes a list of manga. @@ -84,27 +85,20 @@ class CataloguePresenter : BasePresenter() { var isListMode: Boolean = false private set - companion object { - /** - * Id of the restartable that delivers a list of manga. - */ - const val PAGER = 1 + /** + * Subscription for the pager. + */ + private var pagerSubscription: Subscription? = null - /** - * Id of the restartable that requests a page of manga from network. - */ - const val REQUEST_PAGE = 2 + /** + * Subscription for one request from the pager. + */ + private var pageSubscription: Subscription? = null - /** - * Id of the restartable that initializes the details of manga. - */ - const val GET_MANGA_DETAILS = 3 - - /** - * Key to save and restore [query] from a [Bundle]. - */ - const val QUERY_KEY = "query_key" - } + /** + * Subscription to initialize manga details. + */ + private var initializerSubscription: Subscription? = null override fun onCreate(savedState: Bundle?) { super.onCreate(savedState) @@ -112,52 +106,68 @@ class CataloguePresenter : BasePresenter() { source = getLastUsedSource() if (savedState != null) { - query = savedState.getString(QUERY_KEY, "") + query = savedState.getString(CataloguePresenter::query.name, "") } - startableLatestCache(GET_MANGA_DETAILS, - { mangaDetailSubject.observeOn(Schedulers.io()) - .flatMap { Observable.from(it) } - .filter { !it.initialized } - .concatMap { getMangaDetailsObservable(it) } - .onBackpressureBuffer() - .observeOn(AndroidSchedulers.mainThread()) }, - { view, manga -> view.onMangaInitialized(manga) }, - { view, error -> Timber.e(error.message) }) - add(prefs.catalogueAsList().asObservable() .subscribe { setDisplayMode(it) }) - startableReplay(PAGER, - { pager.results() }, - { view, pair -> view.onAddPage(pair.first, pair.second) }) - - startableFirst(REQUEST_PAGE, - { pager.request { page -> getMangasPageObservable(page + 1) } }, - { view, next -> }, - { view, error -> view.onAddPageError(error) }) - - start(PAGER) - start(REQUEST_PAGE) + restartPager() } override fun onSave(state: Bundle) { - state.putString(QUERY_KEY, query) + state.putString(CataloguePresenter::query.name, query) super.onSave(state) } /** - * Sets the display mode. + * Restarts the pager for the active source with the provided query and filters. * - * @param asList whether the current mode is in list or not. + * @param query the query. + * @param filters the list of active filters (for search mode). */ - private fun setDisplayMode(asList: Boolean) { - isListMode = asList - if (asList) { - stop(GET_MANGA_DETAILS) - } else { - start(GET_MANGA_DETAILS) + fun restartPager(query: String = this.query, filters: List = this.filters) { + this.query = query + this.filters = filters + + if (!isListMode) { + subscribeToMangaInitializer() } + + // Create a new pager. + pager = CataloguePager(source, query, filters) + + // Prepare the pager. + pagerSubscription?.let { remove(it) } + pagerSubscription = pager.results() + .subscribeReplay({ view, page -> + view.onAddPage(page.page, page.mangas) + }, { view, error -> + Timber.e(error, error.message) + }) + + // Request first page. + requestNext() + } + + /** + * Requests the next page for the active pager. + */ + fun requestNext() { + if (!hasNextPage()) return + + pageSubscription?.let { remove(it) } + pageSubscription = pager.requestNext { getPageTransformer(it) } + .subscribeFirst({ view, page -> + // Nothing to do when onNext is emitted. + }, CatalogueFragment::onAddPageError) + } + + /** + * Returns true if the last fetched page has a next page. + */ + fun hasNextPage(): Boolean { + return pager.hasNextPage() } /** @@ -168,73 +178,64 @@ class CataloguePresenter : BasePresenter() { fun setActiveSource(source: OnlineSource) { prefs.lastUsedCatalogueSource().set(source.id) this.source = source - restartPager() + + restartPager(query = "", filters = emptyList()) } /** - * Restarts the request for the active source. + * Sets the display mode. * - * @param query the query, or null if searching popular manga. + * @param asList whether the current mode is in list or not. */ - fun restartPager(query: String = "") { - this.query = query - stop(REQUEST_PAGE) - lastMangasPage = null - - if (!isListMode) { - start(GET_MANGA_DETAILS) - } - start(PAGER) - start(REQUEST_PAGE) - } - - /** - * Requests the next page for the active pager. - */ - fun requestNext() { - if (hasNextPage()) { - start(REQUEST_PAGE) + private fun setDisplayMode(asList: Boolean) { + isListMode = asList + if (asList) { + initializerSubscription?.let { remove(it) } + } else { + subscribeToMangaInitializer() } } /** - * Returns true if the last fetched page has a next page. + * Subscribes to the initializer of manga details and updates the view if needed. */ - fun hasNextPage(): Boolean { - return lastMangasPage?.nextPageUrl != null - } - - /** - * Retries the current request that failed. - */ - fun retryPage() { - start(REQUEST_PAGE) - } - - /** - * Returns the observable of the network request for a page. - * - * @param page the page number to request. - * @return an observable of the network request. - */ - private fun getMangasPageObservable(page: Int): Observable> { - val nextMangasPage = MangasPage(page) - if (page != 1) { - nextMangasPage.url = lastMangasPage!!.nextPageUrl!! - } - - val observable = if (query.isEmpty()) - source.fetchPopularManga(nextMangasPage) - else - source.fetchSearchManga(nextMangasPage, query) - - return observable.subscribeOn(Schedulers.io()) - .doOnNext { lastMangasPage = it } - .flatMap { Observable.from(it.mangas) } - .map { networkToLocalManga(it) } - .toList() - .doOnNext { initializeMangas(it) } + private fun subscribeToMangaInitializer() { + initializerSubscription?.let { remove(it) } + initializerSubscription = mangaDetailSubject.observeOn(Schedulers.io()) + .flatMap { Observable.from(it) } + .filter { !it.initialized } + .concatMap { getMangaDetailsObservable(it) } + .onBackpressureBuffer() .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ manga -> + @Suppress("DEPRECATION") + view?.onMangaInitialized(manga) + }, { error -> + Timber.e(error, error.message) + }) + .apply { add(this) } + } + + /** + * Returns the function to apply to the observable of the list of manga from the source. + * + * @param observable the observable from the source. + * @return the function to apply. + */ + fun getPageTransformer(observable: Observable): Observable { + return observable.subscribeOn(Schedulers.io()) + .doOnNext { it.mangas.replace { networkToLocalManga(it) } } + .doOnNext { initializeMangas(it.mangas) } + .observeOn(AndroidSchedulers.mainThread()) + } + + /** + * Replaces an object in the list with another. + */ + fun MutableList.replace(block: (T) -> T) { + forEachIndexed { i, obj -> + set(i, block(obj)) + } } /** @@ -354,4 +355,13 @@ class CataloguePresenter : BasePresenter() { prefs.catalogueAsList().set(!isListMode) } + /** + * Set the active filters for the current source. + * + * @param selectedFilters a list of active filters. + */ + fun setSourceFilter(selectedFilters: List) { + restartPager(filters = selectedFilters) + } + } diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/RxPager.kt b/app/src/main/java/eu/kanade/tachiyomi/util/RxPager.kt deleted file mode 100644 index c46d68e1a..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/util/RxPager.kt +++ /dev/null @@ -1,21 +0,0 @@ -package eu.kanade.tachiyomi.util - -import android.util.Pair -import rx.Observable -import rx.subjects.PublishSubject - -class RxPager { - - private val results = PublishSubject.create>() - private var requestedCount: Int = 0 - - fun results(): Observable>> { - requestedCount = 0 - return results.map { Pair(requestedCount++, it) } - } - - fun request(networkObservable: (Int) -> Observable>) = - networkObservable(requestedCount).doOnNext { results.onNext(it) } - -} - diff --git a/app/src/main/res/layout/fragment_catalogue.xml b/app/src/main/res/layout/fragment_catalogue.xml index 1f5ec2e4e..d862a45b7 100644 --- a/app/src/main/res/layout/fragment_catalogue.xml +++ b/app/src/main/res/layout/fragment_catalogue.xml @@ -1,48 +1,55 @@ - + - + - - - + android:layout_gravity="center_vertical|center_horizontal" + android:visibility="gone"/> - + android:layout_height="0dp" + android:layout_weight="1"> + - + - + - + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/catalogue_list.xml b/app/src/main/res/menu/catalogue_list.xml index 369402eea..ff4373df4 100644 --- a/app/src/main/res/menu/catalogue_list.xml +++ b/app/src/main/res/menu/catalogue_list.xml @@ -9,6 +9,12 @@ app:showAsAction="collapseActionView|ifRoom" app:actionViewClass="android.support.v7.widget.SearchView"/> + + Resume Open in browser Change display mode + Set filter Cancel Sort Install From 50136c319f9445bf412b0a59dd499bd173a49561 Mon Sep 17 00:00:00 2001 From: Franklin L Date: Tue, 30 Aug 2016 01:28:10 -0700 Subject: [PATCH 19/79] MAL switched to SSL/HTTPS (#437) Changed the URL for myanimelist.net to use HTTPS, as API endpoints are using HTTPS/TLS as of August 25. --- .../kanade/tachiyomi/data/mangasync/myanimelist/MyAnimeList.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/mangasync/myanimelist/MyAnimeList.kt b/app/src/main/java/eu/kanade/tachiyomi/data/mangasync/myanimelist/MyAnimeList.kt index 3b589972e..66a475cf5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/mangasync/myanimelist/MyAnimeList.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/mangasync/myanimelist/MyAnimeList.kt @@ -26,7 +26,7 @@ class MyAnimeList(private val context: Context, id: Int) : MangaSyncService(cont private lateinit var headers: Headers companion object { - val BASE_URL = "http://myanimelist.net" + val BASE_URL = "https://myanimelist.net" private val ENTRY_TAG = "entry" private val CHAPTER_TAG = "chapter" From d58c517a6cd8853e99256164fceb54be6bae89e5 Mon Sep 17 00:00:00 2001 From: Taumer Date: Tue, 30 Aug 2016 14:22:55 +0300 Subject: [PATCH 20/79] Implement genre filter for Mintmanga --- .../data/source/online/russian/Mintmanga.kt | 56 ++++++++++++++++++- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Mintmanga.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Mintmanga.kt index ff73f608f..e6e821bbd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Mintmanga.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Mintmanga.kt @@ -24,7 +24,8 @@ class Mintmanga(context: Context, override val id: Int) : ParsedOnlineSource(con override fun popularMangaInitialUrl() = "$baseUrl/list?sortType=rate" - override fun searchMangaInitialUrl(query: String, filters: List) = "$baseUrl/search?q=$query" + override fun searchMangaInitialUrl(query: String, filters: List) = + "$baseUrl/search?q=$query&${filters.map { it.id + "=in" }.joinToString("&")}" override fun popularMangaSelector() = "div.desc" @@ -43,7 +44,8 @@ class Mintmanga(context: Context, override val id: Int) : ParsedOnlineSource(con popularMangaFromElement(element, manga) } - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() + // max 200 results + override fun searchMangaNextPageSelector() = null override fun mangaDetailsParse(document: Document, manga: Manga) { val infoElement = document.select("div.leftContent").first() @@ -99,4 +101,54 @@ class Mintmanga(context: Context, override val id: Int) : ParsedOnlineSource(con override fun pageListParse(document: Document, pages: MutableList) { } override fun imageUrlParse(document: Document) = "" + + /* [...document.querySelectorAll("tr.advanced_option:nth-child(1) > td:nth-child(3) span.js-link")].map((el,i) => { + * const onClick=el.getAttribute('onclick');const id=onClick.substr(31,onClick.length-33); + * return `Filter("${id}", "${el.textContent.trim()}")` }).join(',\n') + * on http://mintmanga.com/search + */ + override fun getFilterList(): List = listOf( + Filter("el_2220", "арт"), + Filter("el_1353", "бара"), + Filter("el_1346", "боевик"), + Filter("el_1334", "боевые искусства"), + Filter("el_1339", "вампиры"), + Filter("el_1333", "гарем"), + Filter("el_1347", "гендерная интрига"), + Filter("el_1337", "героическое фэнтези"), + Filter("el_1343", "детектив"), + Filter("el_1349", "дзёсэй"), + Filter("el_1332", "додзинси"), + Filter("el_1310", "драма"), + Filter("el_5229", "игра"), + Filter("el_1311", "история"), + Filter("el_1351", "киберпанк"), + Filter("el_1328", "комедия"), + Filter("el_1318", "меха"), + Filter("el_1324", "мистика"), + Filter("el_1325", "научная фантастика"), + Filter("el_1327", "повседневность"), + Filter("el_1342", "постапокалиптика"), + Filter("el_1322", "приключения"), + Filter("el_1335", "психология"), + Filter("el_1313", "романтика"), + Filter("el_1316", "самурайский боевик"), + Filter("el_1350", "сверхъестественное"), + Filter("el_1314", "сёдзё"), + Filter("el_1320", "сёдзё-ай"), + Filter("el_1326", "сёнэн"), + Filter("el_1330", "сёнэн-ай"), + Filter("el_1321", "спорт"), + Filter("el_1329", "сэйнэн"), + Filter("el_1344", "трагедия"), + Filter("el_1341", "триллер"), + Filter("el_1317", "ужасы"), + Filter("el_1331", "фантастика"), + Filter("el_1323", "фэнтези"), + Filter("el_1319", "школа"), + Filter("el_1340", "эротика"), + Filter("el_1354", "этти"), + Filter("el_1315", "юри"), + Filter("el_1336", "яой") + ) } \ No newline at end of file From 2a5edf4547c10fc6bb9c3c019e71bcc0bf5d7cea Mon Sep 17 00:00:00 2001 From: Taumer Date: Tue, 30 Aug 2016 14:23:47 +0300 Subject: [PATCH 21/79] Implement genre filter for Readmanga --- .../data/source/online/russian/Readmanga.kt | 59 ++++++++++++++++++- 1 file changed, 56 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Readmanga.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Readmanga.kt index b645db2d7..a874ba09f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Readmanga.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Readmanga.kt @@ -24,7 +24,8 @@ class Readmanga(context: Context, override val id: Int) : ParsedOnlineSource(con override fun popularMangaInitialUrl() = "$baseUrl/list?sortType=rate" - override fun searchMangaInitialUrl(query: String, filters: List) = "$baseUrl/search?q=$query" + override fun searchMangaInitialUrl(query: String, filters: List) = + "$baseUrl/search?q=$query&${filters.map { it.id + "=in" }.joinToString("&")}" override fun popularMangaSelector() = "div.desc" @@ -40,10 +41,14 @@ class Readmanga(context: Context, override val id: Int) : ParsedOnlineSource(con override fun searchMangaSelector() = popularMangaSelector() override fun searchMangaFromElement(element: Element, manga: Manga) { - popularMangaFromElement(element, manga) + element.select("h3 > a").first().let { + manga.setUrlWithoutDomain(it.attr("href")) + manga.title = it.attr("title") + } } - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() + // max 200 results + override fun searchMangaNextPageSelector() = null override fun mangaDetailsParse(document: Document, manga: Manga) { val infoElement = document.select("div.leftContent").first() @@ -99,4 +104,52 @@ class Readmanga(context: Context, override val id: Int) : ParsedOnlineSource(con override fun pageListParse(document: Document, pages: MutableList) { } override fun imageUrlParse(document: Document) = "" + + /* [...document.querySelectorAll("tr.advanced_option:nth-child(1) > td:nth-child(3) span.js-link")].map((el,i) => { + * const onClick=el.getAttribute('onclick');const id=onClick.substr(31,onClick.length-33); + * return `Filter("${id}", "${el.textContent.trim()}")` }).join(',\n') + * on http://readmanga.me/search + */ + override fun getFilterList(): List = listOf( + Filter("el_5685", "арт"), + Filter("el_2155", "боевик"), + Filter("el_2143", "боевые искусства"), + Filter("el_2148", "вампиры"), + Filter("el_2142", "гарем"), + Filter("el_2156", "гендерная интрига"), + Filter("el_2146", "героическое фэнтези"), + Filter("el_2152", "детектив"), + Filter("el_2158", "дзёсэй"), + Filter("el_2141", "додзинси"), + Filter("el_2118", "драма"), + Filter("el_2154", "игра"), + Filter("el_2119", "история"), + Filter("el_2137", "кодомо"), + Filter("el_2136", "комедия"), + Filter("el_2147", "махо-сёдзё"), + Filter("el_2126", "меха"), + Filter("el_2132", "мистика"), + Filter("el_2133", "научная фантастика"), + Filter("el_2135", "повседневность"), + Filter("el_2151", "постапокалиптика"), + Filter("el_2130", "приключения"), + Filter("el_2144", "психология"), + Filter("el_2121", "романтика"), + Filter("el_2124", "самурайский боевик"), + Filter("el_2159", "сверхъестественное"), + Filter("el_2122", "сёдзё"), + Filter("el_2128", "сёдзё-ай"), + Filter("el_2134", "сёнэн"), + Filter("el_2139", "сёнэн-ай"), + Filter("el_2129", "спорт"), + Filter("el_2138", "сэйнэн"), + Filter("el_2153", "трагедия"), + Filter("el_2150", "триллер"), + Filter("el_2125", "ужасы"), + Filter("el_2140", "фантастика"), + Filter("el_2131", "фэнтези"), + Filter("el_2127", "школа"), + Filter("el_2149", "этти"), + Filter("el_2123", "юри") + ) } \ No newline at end of file From b280d6a76bee807c6825ca3ef68c3987bb1636f0 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 31 Aug 2016 22:40:12 +0200 Subject: [PATCH 22/79] Add filter support to mangahere --- .../data/source/online/english/Mangahere.kt | 40 ++++++++++++++++++- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangahere.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangahere.kt index 7da6487a7..f75c8ac88 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangahere.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangahere.kt @@ -1,6 +1,7 @@ package eu.kanade.tachiyomi.data.source.online.english import android.content.Context +import android.util.Log import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.source.EN @@ -34,8 +35,7 @@ class Mangahere(context: Context, override val id: Int) : ParsedOnlineSource(con override fun popularMangaNextPageSelector() = "div.next-page > a.next" - override fun searchMangaInitialUrl(query: String, filters: List) = - "$baseUrl/search.php?name=$query&page=1&sort=views&order=za" + override fun searchMangaInitialUrl(query: String, filters: List) = "$baseUrl/search.php?name=$query&page=1&sort=views&order=za&${filters.map { it.id + "=1" }.joinToString("&")}&advopts=1" override fun searchMangaSelector() = "div.result_search > dl:has(dt)" @@ -110,4 +110,40 @@ class Mangahere(context: Context, override val id: Int) : ParsedOnlineSource(con override fun imageUrlParse(document: Document) = document.getElementById("image").attr("src") + // [...document.querySelectorAll("select[id^='genres'")].map((el,i) => `Filter("${el.getAttribute('name')}", "${el.nextSibling.nextSibling.textContent.trim()}")`).join(',\n') + // http://www.mangahere.co/advsearch.htm + override fun getFilterList(): List = listOf( + Filter("genres[Action]", "Action"), + Filter("genres[Adventure]", "Adventure"), + Filter("genres[Comedy]", "Comedy"), + Filter("genres[Doujinshi]", "Doujinshi"), + Filter("genres[Drama]", "Drama"), + Filter("genres[Ecchi]", "Ecchi"), + Filter("genres[Fantasy]", "Fantasy"), + Filter("genres[Gender Bender]", "Gender Bender"), + Filter("genres[Harem]", "Harem"), + Filter("genres[Historical]", "Historical"), + Filter("genres[Horror]", "Horror"), + Filter("genres[Josei]", "Josei"), + Filter("genres[Martial Arts]", "Martial Arts"), + Filter("genres[Mature]", "Mature"), + Filter("genres[Mecha]", "Mecha"), + Filter("genres[Mystery]", "Mystery"), + Filter("genres[One Shot]", "One Shot"), + Filter("genres[Psychological]", "Psychological"), + Filter("genres[Romance]", "Romance"), + Filter("genres[School Life]", "School Life"), + Filter("genres[Sci-fi]", "Sci-fi"), + Filter("genres[Seinen]", "Seinen"), + Filter("genres[Shoujo]", "Shoujo"), + Filter("genres[Shoujo Ai]", "Shoujo Ai"), + Filter("genres[Shounen]", "Shounen"), + Filter("genres[Shounen Ai]", "Shounen Ai"), + Filter("genres[Slice of Life]", "Slice of Life"), + Filter("genres[Sports]", "Sports"), + Filter("genres[Supernatural]", "Supernatural"), + Filter("genres[Tragedy]", "Tragedy"), + Filter("genres[Yaoi]", "Yaoi"), + Filter("genres[Yuri]", "Yuri") + ) } \ No newline at end of file From c9b62209c23ee26ae7dc1db60c6248bca905a92e Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 31 Aug 2016 23:12:25 +0200 Subject: [PATCH 23/79] Add filter support to mangasee --- .../data/source/online/english/Mangasee.kt | 42 ++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangasee.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangasee.kt index 3874f2aff..118cf212c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangasee.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangasee.kt @@ -48,7 +48,7 @@ class Mangasee(context: Context, override val id: Int) : ParsedOnlineSource(cont override fun popularMangaNextPageSelector() = "ul.pagination > li > a:contains(Next)" override fun searchMangaInitialUrl(query: String, filters: List) = - "$baseUrl/advanced-search/result.php?sortBy=alphabet&direction=ASC&textOnly=no&resPerPage=20&page=1&seriesName=$query" + "$baseUrl/advanced-search/result.php?sortBy=alphabet&direction=ASC&textOnly=no&resPerPage=20&page=1&seriesName=$query&${filters.map { it.id + "=Yes" }.joinToString("&")}" override fun searchMangaSelector() = "div.row > div > div > div > h1" @@ -122,4 +122,44 @@ class Mangasee(context: Context, override val id: Int) : ParsedOnlineSource(cont override fun imageUrlParse(document: Document) = document.select("div > a > img").attr("src") + // [...document.querySelectorAll("label.triStateCheckBox input")].map(el => `Filter("${el.getAttribute('name')}", "${el.nextSibling.textContent.trim()}")`).join(',\n') + // http://mangasee.co/advanced-search/ + override fun getFilterList(): List = listOf( + Filter("Action", "Action"), + Filter("Adult", "Adult"), + Filter("Adventure", "Adventure"), + Filter("Comedy", "Comedy"), + Filter("Doujinshi", "Doujinshi"), + Filter("Drama", "Drama"), + Filter("Ecchi", "Ecchi"), + Filter("Fantasy", "Fantasy"), + Filter("Gender_Bender", "Gender Bender"), + Filter("Harem", "Harem"), + Filter("Hentai", "Hentai"), + Filter("Historical", "Historical"), + Filter("Horror", "Horror"), + Filter("Josei", "Josei"), + Filter("Lolicon", "Lolicon"), + Filter("Martial_Arts", "Martial Arts"), + Filter("Mature", "Mature"), + Filter("Mecha", "Mecha"), + Filter("Mystery", "Mystery"), + Filter("Psychological", "Psychological"), + Filter("Romance", "Romance"), + Filter("School_Life", "School Life"), + Filter("Sci-fi", "Sci-fi"), + Filter("Seinen", "Seinen"), + Filter("Shotacon", "Shotacon"), + Filter("Shoujo", "Shoujo"), + Filter("Shoujo_Ai", "Shoujo Ai"), + Filter("Shounen", "Shounen"), + Filter("Shounen_Ai", "Shounen Ai"), + Filter("Slice_of_Life", "Slice of Life"), + Filter("Smut", "Smut"), + Filter("Sports", "Sports"), + Filter("Supernatural", "Supernatural"), + Filter("Tragedy", "Tragedy"), + Filter("Yaoi", "Yaoi"), + Filter("Yuri", "Yuri") + ) } \ No newline at end of file From 1698a85e99902fdfff23dc6d8ecf0e3ba00d6fd4 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 31 Aug 2016 23:11:34 +0200 Subject: [PATCH 24/79] Add filter support to readmangatoday --- .../source/online/english/Readmangatoday.kt | 59 ++++++++++++++++++- 1 file changed, 56 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Readmangatoday.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Readmangatoday.kt index 7cd682792..f32b90a84 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Readmangatoday.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Readmangatoday.kt @@ -11,6 +11,7 @@ import eu.kanade.tachiyomi.data.source.model.MangasPage import eu.kanade.tachiyomi.data.source.model.Page import eu.kanade.tachiyomi.data.source.online.OnlineSource import eu.kanade.tachiyomi.data.source.online.ParsedOnlineSource +import okhttp3.Headers import okhttp3.OkHttpClient import okhttp3.Request import org.jsoup.nodes.Document @@ -27,6 +28,14 @@ class Readmangatoday(context: Context, override val id: Int) : ParsedOnlineSourc override val client: OkHttpClient get() = network.cloudflareClient + /** + * Search only returns data with this set + */ + override fun headersBuilder() = Headers.Builder().apply { + add("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64)") + add("X-Requested-With", "XMLHttpRequest") + } + override fun popularMangaInitialUrl() = "$baseUrl/hot-manga/" override fun popularMangaSelector() = "div.hot-manga > div.style-list > div.box" @@ -41,7 +50,7 @@ class Readmangatoday(context: Context, override val id: Int) : ParsedOnlineSourc override fun popularMangaNextPageSelector() = "div.hot-manga > ul.pagination > li > a:contains(»)" override fun searchMangaInitialUrl(query: String, filters: List) = - "$baseUrl/search" + "$baseUrl/service/advanced_search" override fun searchMangaRequest(page: MangasPage, query: String, filters: List): Request { @@ -50,12 +59,17 @@ class Readmangatoday(context: Context, override val id: Int) : ParsedOnlineSourc } val builder = okhttp3.FormBody.Builder() - builder.add("query", query) + builder.add("manga-name", query) + builder.add("type", "all") + builder.add("status", "both") + for (filter in filters) { + builder.add("include[]", filter.id) + } return POST(page.url, headers, builder.build()) } - override fun searchMangaSelector() = "div.content-list > div.style-list > div.box" + override fun searchMangaSelector() = "div.style-list > div.box" override fun searchMangaFromElement(element: Element, manga: Manga) { element.select("div.title > h2 > a").first().let { @@ -129,4 +143,43 @@ class Readmangatoday(context: Context, override val id: Int) : ParsedOnlineSourc override fun imageUrlParse(document: Document) = document.select("img.img-responsive-2").first().attr("src") + // [...document.querySelectorAll("ul.manga-cat span")].map(el => `Filter("${el.getAttribute('data-id')}", "${el.nextSibling.textContent.trim()}")`).join(',\n') + // http://www.readmanga.today/advanced-search + override fun getFilterList(): List = listOf( + Filter("2", "Action"), + Filter("4", "Adventure"), + Filter("5", "Comedy"), + Filter("6", "Doujinshi"), + Filter("7", "Drama"), + Filter("8", "Ecchi"), + Filter("9", "Fantasy"), + Filter("10", "Gender Bender"), + Filter("11", "Harem"), + Filter("12", "Historical"), + Filter("13", "Horror"), + Filter("14", "Josei"), + Filter("15", "Lolicon"), + Filter("16", "Martial Arts"), + Filter("17", "Mature"), + Filter("18", "Mecha"), + Filter("19", "Mystery"), + Filter("20", "One shot"), + Filter("21", "Psychological"), + Filter("22", "Romance"), + Filter("23", "School Life"), + Filter("24", "Sci-fi"), + Filter("25", "Seinen"), + Filter("26", "Shotacon"), + Filter("27", "Shoujo"), + Filter("28", "Shoujo Ai"), + Filter("29", "Shounen"), + Filter("30", "Shounen Ai"), + Filter("31", "Slice of Life"), + Filter("32", "Smut"), + Filter("33", "Sports"), + Filter("34", "Supernatural"), + Filter("35", "Tragedy"), + Filter("36", "Yaoi"), + Filter("37", "Yuri") + ) } \ No newline at end of file From 46e09d174bc04a1a4595378e4e7254a1465cdcba Mon Sep 17 00:00:00 2001 From: len Date: Thu, 1 Sep 2016 20:05:11 +0200 Subject: [PATCH 25/79] Travis fix. Update gradle --- .travis.yml | 2 +- app/build.gradle | 2 +- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 10e3f13e9..60217ab47 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ android: - tools # The BuildTools version used by your project - - build-tools-24.0.1 + - build-tools-24.0.2 - android-24 - extra-android-m2repository - extra-google-m2repository diff --git a/app/build.gradle b/app/build.gradle index 7a5d166aa..b178c3a6f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -30,7 +30,7 @@ def includeUpdater() { android { compileSdkVersion 24 - buildToolsVersion "24.0.1" + buildToolsVersion "24.0.2" publishNonDefault true defaultConfig { diff --git a/build.gradle b/build.gradle index 00ced40a7..3f3598502 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.1.2' + classpath 'com.android.tools.build:gradle:2.1.3' classpath 'com.github.ben-manes:gradle-versions-plugin:0.13.0' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ec9f42024..53d374f58 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Fri Feb 05 17:57:08 CET 2016 +#Sat Aug 27 15:18:00 CEST 2016 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.14-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip From 568b90d0b4bc49892ffc2b483b131dc6a338f944 Mon Sep 17 00:00:00 2001 From: len Date: Sat, 3 Sep 2016 11:05:32 +0200 Subject: [PATCH 26/79] Fix #446 --- .../java/eu/kanade/tachiyomi/ui/library/LibraryAdapter.kt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryAdapter.kt index 7b98e5b99..c416c8e45 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryAdapter.kt @@ -76,4 +76,11 @@ class LibraryAdapter(private val fragment: LibraryFragment) : RecyclerViewPagerA return categories[position].name } + /** + * Returns the position of the item. Always assume the position has changed. + */ + override fun getItemPosition(`object`: Any?): Int { + return POSITION_NONE + } + } \ No newline at end of file From d8d93ee344e346335ee0a30d54f0b717e937f81a Mon Sep 17 00:00:00 2001 From: Bram van de Kerkhof Date: Mon, 5 Sep 2016 11:08:16 +0200 Subject: [PATCH 27/79] Added read filter to chapter select. (#431) * Added read filter to chapter select. * Can now select how far back the chapter should be deleted after read. --- .../data/preference/PreferenceKeys.kt | 4 +- .../data/preference/PreferencesHelper.kt | 4 +- .../ui/manga/chapter/ChaptersFragment.kt | 38 +++++++++++++------ .../ui/manga/chapter/ChaptersPresenter.kt | 23 ++++++++++- .../tachiyomi/ui/reader/ReaderPresenter.kt | 37 +++++++++--------- app/src/main/res/menu/chapters.xml | 8 +++- app/src/main/res/raw/changelog_debug.xml | 11 +++++- app/src/main/res/values/arrays.xml | 18 +++++++++ app/src/main/res/values/keys.xml | 4 +- app/src/main/res/values/strings.xml | 11 ++++-- app/src/main/res/xml/pref_downloads.xml | 21 ++++------ 11 files changed, 118 insertions(+), 61 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt index 667a1eb90..605e927e5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt @@ -66,9 +66,7 @@ class PreferenceKeys(context: Context) { val downloadOnlyOverWifi = context.getString(R.string.pref_download_only_over_wifi_key) - val removeAfterRead = context.getString(R.string.pref_remove_after_read_key) - - val removeAfterReadPrevious = context.getString(R.string.pref_remove_after_read_previous_key) + val removeAfterReadSlots = context.getString(R.string.pref_remove_after_read_slots_key) val removeAfterMarkedAsRead = context.getString(R.string.pref_remove_after_marked_as_read_key) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt index 6ebbdbcea..0707afdbb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt @@ -116,9 +116,7 @@ class PreferencesHelper(context: Context) { fun downloadOnlyOverWifi() = prefs.getBoolean(keys.downloadOnlyOverWifi, true) - fun removeAfterRead() = prefs.getBoolean(keys.removeAfterRead, false) - - fun removeAfterReadPrevious() = prefs.getBoolean(keys.removeAfterReadPrevious, false) + fun removeAfterReadSlots() = prefs.getInt(keys.removeAfterReadSlots, -1) fun removeAfterMarkedAsRead() = prefs.getBoolean(keys.removeAfterMarkedAsRead, false) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersFragment.kt index 2353f8c3b..577f96fa0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersFragment.kt @@ -116,8 +116,25 @@ class ChaptersFragment : BaseRxFragment(), ActionMode.Callbac override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { inflater.inflate(R.menu.chapters, menu) - menu.findItem(R.id.action_filter_unread).isChecked = presenter.onlyUnread() - menu.findItem(R.id.action_filter_downloaded).isChecked = presenter.onlyDownloaded() + } + + override fun onPrepareOptionsMenu(menu: Menu) { + // Initialize menu items. + val menuFilterRead = menu.findItem(R.id.action_filter_read) + val menuFilterUnread = menu.findItem(R.id.action_filter_unread) + val menuFilterDownloaded = menu.findItem(R.id.action_filter_downloaded) + + // Set correct checkbox values. + menuFilterRead.isChecked = presenter.onlyRead() + menuFilterUnread.isChecked = presenter.onlyUnread() + menuFilterDownloaded.isChecked = presenter.onlyDownloaded() + + if (presenter.onlyRead()) + //Disable unread filter option if read filter is enabled. + menuFilterUnread.isEnabled = false + if (presenter.onlyUnread()) + //Disable read filter option if unread filter is enabled. + menuFilterRead.isEnabled = false } override fun onOptionsItemSelected(item: MenuItem): Boolean { @@ -126,8 +143,14 @@ class ChaptersFragment : BaseRxFragment(), ActionMode.Callbac R.id.manga_download -> showDownloadDialog() R.id.action_sorting_mode -> showSortingDialog() R.id.action_filter_unread -> { + item.isChecked = !item.isChecked + presenter.setUnreadFilter(item.isChecked) + activity.supportInvalidateOptionsMenu() + } + R.id.action_filter_read -> { item.isChecked = !item.isChecked presenter.setReadFilter(item.isChecked) + activity.supportInvalidateOptionsMenu() } R.id.action_filter_downloaded -> { item.isChecked = !item.isChecked @@ -145,8 +168,7 @@ class ChaptersFragment : BaseRxFragment(), ActionMode.Callbac fun onNextManga(manga: Manga) { // Set initial values - setReadFilter() - setDownloadedFilter() + activity.supportInvalidateOptionsMenu() } fun onNextChapters(chapters: List) { @@ -394,12 +416,4 @@ class ChaptersFragment : BaseRxFragment(), ActionMode.Callbac private fun setContextTitle(count: Int) { actionMode?.title = getString(R.string.label_selected, count) } - - fun setReadFilter() { - activity.supportInvalidateOptionsMenu() - } - - fun setDownloadedFilter() { - activity.supportInvalidateOptionsMenu() - } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersPresenter.kt index 402f15623..3eb6e2ff4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersPresenter.kt @@ -209,6 +209,9 @@ class ChaptersPresenter : BasePresenter() { if (onlyUnread()) { observable = observable.filter { !it.read } } + if (onlyRead()) { + observable = observable.filter { it.read } + } if (onlyDownloaded()) { observable = observable.filter { it.isDownloaded } } @@ -349,12 +352,23 @@ class ChaptersPresenter : BasePresenter() { * * @param onlyUnread whether to display only unread chapters or all chapters. */ - fun setReadFilter(onlyUnread: Boolean) { + fun setUnreadFilter(onlyUnread: Boolean) { manga.readFilter = if (onlyUnread) Manga.SHOW_UNREAD else Manga.SHOW_ALL db.updateFlags(manga).executeAsBlocking() refreshChapters() } + /** + * Sets the read filter and requests an UI update. + * + * @param onlyRead whether to display only read chapters or all chapters. + */ + fun setReadFilter(onlyRead: Boolean) { + manga.readFilter = if (onlyRead) Manga.SHOW_READ else Manga.SHOW_ALL + db.updateFlags(manga).executeAsBlocking() + refreshChapters() + } + /** * Sets the download filter and requests an UI update. * @@ -411,6 +425,13 @@ class ChaptersPresenter : BasePresenter() { return manga.readFilter == Manga.SHOW_UNREAD } + /** + * Whether the display only read filter is enabled. + */ + fun onlyRead(): Boolean { + return manga.readFilter == Manga.SHOW_READ + } + /** * Whether the sorting method is descending or ascending. */ diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt index bdd3eb0fa..a905b1cdd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt @@ -228,12 +228,14 @@ class ReaderPresenter : BasePresenter() { * strategy set for the manga. * * @param chapter the current active chapter. + * @param previousChapterAmount the desired number of chapters preceding the current active chapter (Default: 1). + * @param nextChapterAmount the desired number of chapters succeeding the current active chapter (Default: 1). */ - private fun getAdjacentChaptersStrategy(chapter: ReaderChapter) = when (manga.sorting) { + private fun getAdjacentChaptersStrategy(chapter: ReaderChapter, previousChapterAmount: Int = 1, nextChapterAmount: Int = 1) = when (manga.sorting) { Manga.SORTING_SOURCE -> { val currChapterIndex = chapterList.indexOfFirst { chapter.id == it.id } - val nextChapter = chapterList.getOrNull(currChapterIndex + 1) - val prevChapter = chapterList.getOrNull(currChapterIndex - 1) + val nextChapter = chapterList.getOrNull(currChapterIndex + nextChapterAmount) + val prevChapter = chapterList.getOrNull(currChapterIndex - previousChapterAmount) Pair(prevChapter, nextChapter) } Manga.SORTING_NUMBER -> { @@ -241,18 +243,18 @@ class ReaderPresenter : BasePresenter() { val chapterNumber = chapter.chapter_number var prevChapter: ReaderChapter? = null - for (i in (currChapterIndex - 1) downTo 0) { + for (i in (currChapterIndex - previousChapterAmount) downTo 0) { val c = chapterList[i] - if (c.chapter_number < chapterNumber && c.chapter_number >= chapterNumber - 1) { + if (c.chapter_number < chapterNumber && c.chapter_number >= chapterNumber - previousChapterAmount) { prevChapter = c break } } var nextChapter: ReaderChapter? = null - for (i in (currChapterIndex + 1) until chapterList.size) { + for (i in (currChapterIndex + nextChapterAmount) until chapterList.size) { val c = chapterList[i] - if (c.chapter_number > chapterNumber && c.chapter_number <= chapterNumber + 1) { + if (c.chapter_number > chapterNumber && c.chapter_number <= chapterNumber + nextChapterAmount) { nextChapter = c break } @@ -344,7 +346,6 @@ class ReaderPresenter : BasePresenter() { fun onChapterLeft() { // Reference these locally because they are needed later from another thread. val chapter = chapter - val prevChapter = prevChapter val pages = chapter.pages ?: return @@ -355,21 +356,21 @@ class ReaderPresenter : BasePresenter() { chapter.read = true } + // Cache current page list progress for online chapters to allow a faster reopen if (!chapter.isDownloaded) { source.let { if (it is OnlineSource) it.savePageList(chapter, pages) } } - // Cache current page list progress for online chapters to allow a faster reopen if (chapter.read) { - // Check if remove after read is selected by user - if (prefs.removeAfterRead()) { - if (prefs.removeAfterReadPrevious() ) { - if (prevChapter != null) { - deleteChapter(prevChapter, manga) - } - } else { - deleteChapter(chapter, manga) - } + val removeAfterReadSlots = prefs.removeAfterReadSlots() + when (removeAfterReadSlots) { + // Setting disabled + -1 -> { /**Empty function**/ } + // Remove current read chapter + 0 -> deleteChapter(chapter, manga) + // Remove previous chapter specified by user in settings. + else -> getAdjacentChaptersStrategy(chapter, removeAfterReadSlots) + .first?.let { deleteChapter(it, manga) } } } diff --git a/app/src/main/res/menu/chapters.xml b/app/src/main/res/menu/chapters.xml index cb5c01e38..4512d8637 100644 --- a/app/src/main/res/menu/chapters.xml +++ b/app/src/main/res/menu/chapters.xml @@ -9,13 +9,17 @@ app:showAsAction="ifRoom"> + android:title="@string/action_filter_read"/> + diff --git a/app/src/main/res/raw/changelog_debug.xml b/app/src/main/res/raw/changelog_debug.xml index f3d439346..345c21927 100644 --- a/app/src/main/res/raw/changelog_debug.xml +++ b/app/src/main/res/raw/changelog_debug.xml @@ -1,13 +1,20 @@ - + + [b]Important![/b] Delete after read has been updated. + This means the value has been reset set to disabled. + This can be changed in Settings > Downloads + + + + [b]Important![/b] Now chapters follow the order of the sources. [b]It's required that you update your entire library before reading in order for them to be synced.[/b] Old behavior can be restored for a manga in the overflow menu of the chapters tab. - + Kissmanga covers may not load anymore. The only workaround is to update the details of the manga from the info tab, or clearing the database (the latter won't fix covers from library manga). diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index c21c4f27e..1b61924f6 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -48,6 +48,24 @@ 3 + + @string/disabled + @string/last_read_chapter + @string/second_to_last + @string/third_to_last + @string/fourth_to_last + @string/fifth_to_last + + + + -1 + 0 + 1 + 2 + 3 + 4 + + @string/rapid_decoder @string/skia_decoder diff --git a/app/src/main/res/values/keys.xml b/app/src/main/res/values/keys.xml index a32a81968..1149d78f4 100644 --- a/app/src/main/res/values/keys.xml +++ b/app/src/main/res/values/keys.xml @@ -41,13 +41,11 @@ pref_download_directory_key pref_download_slots_key + remove_after_read_slots pref_download_only_over_wifi_key pref_remove_after_marked_as_read_key pref_category_remove_after_read_key - pref_remove_after_read_key - pref_remove_after_read_previous_key - last_used_category pref_source_languages diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c621a2fa2..4f4e63295 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -19,6 +19,7 @@ Filter Downloaded Unread + Read Remove filter Search Select all @@ -142,11 +143,13 @@ Only download over Wi-Fi Remove when marked as read Remove after read - Remove after read - Current chapter - Previous chapter Custom directory - + Disabled + Last read chapter + Second to last chapter + Third to last chapter + Fourth to last chapter + Fifth to last chapter Languages diff --git a/app/src/main/res/xml/pref_downloads.xml b/app/src/main/res/xml/pref_downloads.xml index ce777236a..8eff6f5ba 100644 --- a/app/src/main/res/xml/pref_downloads.xml +++ b/app/src/main/res/xml/pref_downloads.xml @@ -25,25 +25,20 @@ + android:title="@string/pref_remove_after_read" /> - - - + From c3f61e86b77b6a46f085c630490e201c92eba1a5 Mon Sep 17 00:00:00 2001 From: len Date: Tue, 6 Sep 2016 20:42:24 +0200 Subject: [PATCH 28/79] Improve performance with big images. Feedback is appreciated. --- .../eu/kanade/tachiyomi/ui/reader/viewer/pager/PageView.kt | 3 ++- .../kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonHolder.kt | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PageView.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PageView.kt index 523fbba68..0d78f3190 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PageView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PageView.kt @@ -62,7 +62,8 @@ class PageView @JvmOverloads constructor(context: Context, attrs: AttributeSet? setDoubleTapZoomStyle(com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView.ZOOM_FOCUS_FIXED) setPanLimit(com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView.PAN_LIMIT_INSIDE) setMinimumScaleType(reader.scaleType) - setMinimumDpi(50) + setMinimumDpi(100) + setMinimumTileDpi(180) setRegionDecoderClass(reader.regionDecoderClass) setBitmapDecoderClass(reader.bitmapDecoderClass) setVerticalScrollingParent(reader is VerticalReader) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonHolder.kt index ff077882e..dd4e3d936 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonHolder.kt @@ -49,7 +49,8 @@ class WebtoonHolder(private val view: View, private val adapter: WebtoonAdapter) setDoubleTapZoomStyle(SubsamplingScaleImageView.ZOOM_FOCUS_FIXED) setPanLimit(SubsamplingScaleImageView.PAN_LIMIT_INSIDE) setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_FIT_WIDTH) - maxScale = 10f + setMinimumDpi(100) + setMinimumTileDpi(180) setRegionDecoderClass(webtoonReader.regionDecoderClass) setBitmapDecoderClass(webtoonReader.bitmapDecoderClass) setVerticalScrollingParent(true) From a4b71f4d11ca87b422462bdc0c065cfc34c71144 Mon Sep 17 00:00:00 2001 From: len Date: Tue, 6 Sep 2016 21:22:56 +0200 Subject: [PATCH 29/79] Minor UI fixes --- .../java/eu/kanade/tachiyomi/ui/library/LibraryAdapter.kt | 8 +++++--- .../eu/kanade/tachiyomi/ui/library/LibraryCategoryView.kt | 3 ++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryAdapter.kt index c416c8e45..fe4433ead 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryAdapter.kt @@ -77,10 +77,12 @@ class LibraryAdapter(private val fragment: LibraryFragment) : RecyclerViewPagerA } /** - * Returns the position of the item. Always assume the position has changed. + * Returns the position of the view. */ - override fun getItemPosition(`object`: Any?): Int { - return POSITION_NONE + override fun getItemPosition(obj: Any?): Int { + val view = obj as? LibraryCategoryView ?: return POSITION_NONE + val index = categories.indexOfFirst { it.id == view.category.id } + return if (index == -1) POSITION_NONE else index } } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryView.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryView.kt index fd042ef45..67fa76c7b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryView.kt @@ -41,7 +41,8 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att /** * Category for this view. */ - private lateinit var category: Category + lateinit var category: Category + private set /** * Recycler view of the list of manga. From ccdc3361125aba856977e8db05993299e2ac59ed Mon Sep 17 00:00:00 2001 From: inorichi Date: Wed, 7 Sep 2016 19:44:55 +0200 Subject: [PATCH 30/79] Complete auto updates checker (#449) * Complete auto updates checker * Use GcmTaskService for the periodical updates checker * Persist task across reinstalls * Hide setting instead of disabling * Minor refactor --- app/build.gradle | 2 + app/src/main/AndroidManifest.xml | 27 +-- .../data/preference/PreferenceKeys.kt | 2 +- .../data/preference/PreferencesHelper.kt | 2 +- .../tachiyomi/data/updater/GithubService.kt | 1 - .../data/updater/GithubUpdateChecker.kt | 23 +- .../data/updater/GithubUpdateResult.kt | 7 + .../data/updater/UpdateCheckerService.kt | 80 +++++++ .../data/updater/UpdateDownloader.kt | 202 ------------------ .../data/updater/UpdateDownloaderAlarm.kt | 110 ---------- .../data/updater/UpdateDownloaderService.kt | 149 +++++++++++++ .../updater/UpdateNotificationReceiver.kt | 67 ++++++ .../ui/setting/SettingsAboutFragment.kt | 82 +++---- app/src/main/res/xml/pref_about.xml | 11 +- 14 files changed, 384 insertions(+), 381 deletions(-) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/updater/GithubUpdateResult.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateCheckerService.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateDownloader.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateDownloaderAlarm.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateDownloaderService.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateNotificationReceiver.kt diff --git a/app/build.gradle b/app/build.gradle index b178c3a6f..a60ef1f04 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -96,6 +96,8 @@ dependencies { compile "com.android.support:support-annotations:$support_library_version" compile "com.android.support:customtabs:$support_library_version" + compile 'com.google.android.gms:play-services-gcm:9.4.0' + // ReactiveX compile 'io.reactivex:rxandroid:1.2.1' compile 'io.reactivex:rxjava:1.1.8' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 503af4860..3c8f5400d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -59,6 +59,20 @@ + + + + + + + + + + @@ -79,10 +93,6 @@ android:name=".data.library.LibraryUpdateService$CancelUpdateReceiver"> - - - @@ -91,15 +101,6 @@ - - - - - - - - diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt index 605e927e5..c575c96e9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt @@ -78,7 +78,7 @@ class PreferenceKeys(context: Context) { val filterUnread = context.getString(R.string.pref_filter_unread_key) - val automaticUpdateStatus = context.getString(R.string.pref_enable_automatic_updates_key) + val automaticUpdates = context.getString(R.string.pref_enable_automatic_updates_key) val startScreen = context.getString(R.string.pref_start_screen_key) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt index 0707afdbb..d88f17e0c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt @@ -130,6 +130,6 @@ class PreferencesHelper(context: Context) { fun filterUnread() = rxPrefs.getBoolean(keys.filterUnread, false) - fun automaticUpdateStatus() = prefs.getBoolean(keys.automaticUpdateStatus, false) + fun automaticUpdates() = prefs.getBoolean(keys.automaticUpdates, false) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/GithubService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/GithubService.kt index 7bce4082b..42ff97324 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/updater/GithubService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/updater/GithubService.kt @@ -6,7 +6,6 @@ import retrofit2.converter.gson.GsonConverterFactory import retrofit2.http.GET import rx.Observable - /** * Used to connect with the Github API. */ diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/GithubUpdateChecker.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/GithubUpdateChecker.kt index 306fab71b..8d6210845 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/updater/GithubUpdateChecker.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/updater/GithubUpdateChecker.kt @@ -1,20 +1,25 @@ package eu.kanade.tachiyomi.data.updater -import android.content.Context -import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.util.toast +import eu.kanade.tachiyomi.BuildConfig import rx.Observable +class GithubUpdateChecker() { -class GithubUpdateChecker(private val context: Context) { - - val service: GithubService = GithubService.create() + private val service: GithubService = GithubService.create() /** * Returns observable containing release information */ - fun checkForApplicationUpdate(): Observable { - context.toast(R.string.update_check_look_for_updates) - return service.getLatestVersion() + fun checkForUpdate(): Observable { + return service.getLatestVersion().map { release -> + val newVersion = release.version.replace("[^\\d.]".toRegex(), "") + + // Check if latest version is different from current version + if (newVersion != BuildConfig.VERSION_NAME) { + GithubUpdateResult.NewUpdate(release) + } else { + GithubUpdateResult.NoNewUpdate() + } + } } } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/GithubUpdateResult.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/GithubUpdateResult.kt new file mode 100644 index 000000000..a4a89a1c0 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/updater/GithubUpdateResult.kt @@ -0,0 +1,7 @@ +package eu.kanade.tachiyomi.data.updater + +sealed class GithubUpdateResult { + + class NewUpdate(val release: GithubRelease): GithubUpdateResult() + class NoNewUpdate(): GithubUpdateResult() +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateCheckerService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateCheckerService.kt new file mode 100644 index 000000000..7386fc580 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateCheckerService.kt @@ -0,0 +1,80 @@ +package eu.kanade.tachiyomi.data.updater + +import android.content.Context +import android.support.v4.app.NotificationCompat +import com.google.android.gms.gcm.* +import eu.kanade.tachiyomi.Constants.NOTIFICATION_UPDATER_ID +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.preference.PreferencesHelper +import eu.kanade.tachiyomi.util.notificationManager +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get + +class UpdateCheckerService : GcmTaskService() { + + override fun onInitializeTasks() { + val preferences: PreferencesHelper = Injekt.get() + if (preferences.automaticUpdates()) { + setupTask(this) + } + } + + override fun onRunTask(params: TaskParams): Int { + return checkVersion() + } + + fun checkVersion(): Int { + return GithubUpdateChecker() + .checkForUpdate() + .map { result -> + if (result is GithubUpdateResult.NewUpdate) { + val url = result.release.downloadLink + + NotificationCompat.Builder(this).update { + setContentTitle(getString(R.string.app_name)) + setContentText(getString(R.string.update_check_notification_update_available)) + setSmallIcon(android.R.drawable.stat_sys_download_done) + // Download action + addAction(android.R.drawable.stat_sys_download_done, + getString(R.string.action_download), + UpdateNotificationReceiver.downloadApkIntent( + this@UpdateCheckerService, url)) + } + } + GcmNetworkManager.RESULT_SUCCESS + } + .onErrorReturn { GcmNetworkManager.RESULT_FAILURE } + // Sadly, the task needs to be synchronous. + .toBlocking() + .single() + } + + fun NotificationCompat.Builder.update(block: NotificationCompat.Builder.() -> Unit) { + block() + notificationManager.notify(NOTIFICATION_UPDATER_ID, build()) + } + + companion object { + fun setupTask(context: Context) { + val task = PeriodicTask.Builder() + .setService(UpdateCheckerService::class.java) + .setTag("Updater") + // 24 hours + .setPeriod(24 * 60 * 60) + // Run between the last two hours + .setFlex(2 * 60 * 60) + .setRequiredNetwork(Task.NETWORK_STATE_CONNECTED) + .setPersisted(true) + .setUpdateCurrent(true) + .build() + + GcmNetworkManager.getInstance(context).schedule(task) + } + + fun cancelTask(context: Context) { + GcmNetworkManager.getInstance(context).cancelAllTasks(UpdateCheckerService::class.java) + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateDownloader.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateDownloader.kt deleted file mode 100644 index 3dad020a7..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateDownloader.kt +++ /dev/null @@ -1,202 +0,0 @@ -package eu.kanade.tachiyomi.data.updater - -import android.app.Notification -import android.app.PendingIntent -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import android.net.Uri -import android.os.AsyncTask -import android.support.v4.app.NotificationCompat -import eu.kanade.tachiyomi.Constants -import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.network.GET -import eu.kanade.tachiyomi.data.network.NetworkHelper -import eu.kanade.tachiyomi.data.network.ProgressListener -import eu.kanade.tachiyomi.data.network.newCallWithProgress -import eu.kanade.tachiyomi.util.notificationManager -import eu.kanade.tachiyomi.util.saveTo -import timber.log.Timber -import uy.kohesive.injekt.injectLazy -import java.io.File - -class UpdateDownloader(private val context: Context) : - AsyncTask() { - - companion object { - /** - * Prompt user with apk install intent - * @param context context - * @param file file of apk that is installed - */ - fun installAPK(context: Context, file: File) { - // Prompt install interface - val intent = Intent(Intent.ACTION_VIEW) - intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive") - // Without this flag android returned a intent error! - intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK - context.startActivity(intent) - } - } - - val network: NetworkHelper by injectLazy() - - /** - * Default download dir - */ - private val apkFile = File(context.externalCacheDir, "update.apk") - - - /** - * Notification builder - */ - private val notificationBuilder = NotificationCompat.Builder(context) - - /** - * Id of the notification - */ - private val notificationId: Int - get() = Constants.NOTIFICATION_UPDATER_ID - - - /** - * Class containing download result - * @param url url of file - * @param successful status of download - */ - class DownloadResult(var url: String, var successful: Boolean) - - /** - * Called before downloading - */ - override fun onPreExecute() { - // Create download notification - with(notificationBuilder) { - setContentTitle(context.getString(R.string.update_check_notification_file_download)) - setContentText(context.getString(R.string.update_check_notification_download_in_progress)) - setSmallIcon(android.R.drawable.stat_sys_download) - } - } - - override fun doInBackground(vararg params: String?): DownloadResult { - // Initialize information array containing path and url to file. - val result = DownloadResult(params[0]!!, false) - - // Progress of the download - var savedProgress = 0 - - val progressListener = object : ProgressListener { - override fun update(bytesRead: Long, contentLength: Long, done: Boolean) { - val progress = (100 * bytesRead / contentLength).toInt() - if (progress > savedProgress) { - savedProgress = progress - publishProgress(progress) - } - } - } - - try { - // Make the request and download the file - val response = network.client.newCallWithProgress(GET(result.url), progressListener).execute() - - if (response.isSuccessful) { - response.body().source().saveTo(apkFile) - // Set download successful - result.successful = true - } else { - response.close() - } - } catch (e: Exception) { - Timber.e(e, e.message) - } - - return result - } - - /** - * Called when progress is updated - * @param values values containing progress - */ - override fun onProgressUpdate(vararg values: Int?) { - // Notify notification manager to update notification - values.getOrNull(0)?.let { - notificationBuilder.setProgress(100, it, false) - // Displays the progress bar on notification - context.notificationManager.notify(notificationId, notificationBuilder.build()) - } - } - - /** - * Called when download done - * @param result string containing download information - */ - override fun onPostExecute(result: DownloadResult) { - with(notificationBuilder) { - if (result.successful) { - setContentTitle(context.getString(R.string.app_name)) - setContentText(context.getString(R.string.update_check_notification_download_complete)) - addAction(R.drawable.ic_system_update_grey_24dp_img, context.getString(R.string.action_install), - getInstallOnReceivedIntent(InstallOnReceived.INSTALL_APK, apkFile.absolutePath)) - addAction(R.drawable.ic_clear_grey_24dp_img, context.getString(R.string.action_cancel), - getInstallOnReceivedIntent(InstallOnReceived.CANCEL_NOTIFICATION)) - } else { - setContentText(context.getString(R.string.update_check_notification_download_error)) - addAction(R.drawable.ic_refresh_grey_24dp_img, context.getString(R.string.action_retry), - getInstallOnReceivedIntent(InstallOnReceived.RETRY_DOWNLOAD, result.url)) - addAction(R.drawable.ic_clear_grey_24dp_img, context.getString(R.string.action_cancel), - getInstallOnReceivedIntent(InstallOnReceived.CANCEL_NOTIFICATION)) - } - setSmallIcon(android.R.drawable.stat_sys_download_done) - setProgress(0, 0, false) - } - val notification = notificationBuilder.build() - notification.flags = Notification.FLAG_NO_CLEAR - context.notificationManager.notify(notificationId, notification) - } - - /** - * Returns broadcast intent - * @param action action name of broadcast intent - * @param path path of file | url of file - * @return broadcast intent - */ - fun getInstallOnReceivedIntent(action: String, path: String = ""): PendingIntent { - val intent = Intent(context, InstallOnReceived::class.java).apply { - this.action = action - putExtra(InstallOnReceived.FILE_LOCATION, path) - } - return PendingIntent.getBroadcast(context, 0, intent, 0) - } - - - /** - * BroadcastEvent used to install apk or retry download - */ - class InstallOnReceived : BroadcastReceiver() { - companion object { - // Install apk action - const val INSTALL_APK = "eu.kanade.INSTALL_APK" - - // Retry download action - const val RETRY_DOWNLOAD = "eu.kanade.RETRY_DOWNLOAD" - - // Retry download action - const val CANCEL_NOTIFICATION = "eu.kanade.CANCEL_NOTIFICATION" - - // Absolute path of file || URL of file - const val FILE_LOCATION = "file_location" - } - - override fun onReceive(context: Context, intent: Intent) { - when (intent.action) { - // Install apk. - INSTALL_APK -> UpdateDownloader.installAPK(context, File(intent.getStringExtra(FILE_LOCATION))) - // Retry download. - RETRY_DOWNLOAD -> UpdateDownloader(context).execute(intent.getStringExtra(FILE_LOCATION)) - - CANCEL_NOTIFICATION -> context.notificationManager.cancel(Constants.NOTIFICATION_UPDATER_ID) - } - } - - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateDownloaderAlarm.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateDownloaderAlarm.kt deleted file mode 100644 index 66e01deb9..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateDownloaderAlarm.kt +++ /dev/null @@ -1,110 +0,0 @@ -package eu.kanade.tachiyomi.data.updater - -import android.app.AlarmManager -import android.app.PendingIntent -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import android.os.SystemClock -import eu.kanade.tachiyomi.BuildConfig -import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.preference.PreferencesHelper -import eu.kanade.tachiyomi.util.DeviceUtil -import eu.kanade.tachiyomi.util.alarmManager -import eu.kanade.tachiyomi.util.notification -import eu.kanade.tachiyomi.util.notificationManager -import rx.android.schedulers.AndroidSchedulers -import rx.schedulers.Schedulers -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get - -class UpdateDownloaderAlarm : BroadcastReceiver() { - - companion object { - const val CHECK_UPDATE_ACTION = "eu.kanade.CHECK_UPDATE" - - /** - * Sets the alarm to run the intent that checks for update - * @param context the application context. - * @param intervalInHours the time in hours when it will be executed. - */ - fun startAlarm(context: Context, intervalInHours: Int = 12, - isEnabled: Boolean = Injekt.get().automaticUpdateStatus()) { - // Stop previous running alarms if needed, and do not restart it if the interval is 0. - UpdateDownloaderAlarm.stopAlarm(context) - if (intervalInHours == 0 || !isEnabled) - return - - // Get the time the alarm should fire the event to update. - val intervalInMillis = intervalInHours * 60 * 60 * 1000 - val nextRun = SystemClock.elapsedRealtime() + intervalInMillis - - // Start the alarm. - val pendingIntent = getPendingIntent(context) - context.alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, - nextRun, intervalInMillis.toLong(), pendingIntent) - } - - /** - * Stops the alarm if it's running. - * @param context the application context. - */ - fun stopAlarm(context: Context) { - val pendingIntent = getPendingIntent(context) - context.alarmManager.cancel(pendingIntent) - } - - /** - * Returns broadcast intent - * @param context the application context. - * @return broadcast intent - */ - fun getPendingIntent(context: Context): PendingIntent { - return PendingIntent.getBroadcast(context, 0, - Intent(context, UpdateDownloaderAlarm::class.java).apply { - this.action = CHECK_UPDATE_ACTION - }, PendingIntent.FLAG_UPDATE_CURRENT) - } - } - - - override fun onReceive(context: Context, intent: Intent) { - when (intent.action) { - // Start the alarm when the system is booted. - Intent.ACTION_BOOT_COMPLETED -> startAlarm(context) - // Update the library when the alarm fires an event. - CHECK_UPDATE_ACTION -> checkVersion(context) - } - } - - fun checkVersion(context: Context) { - if (DeviceUtil.isNetworkConnected(context)) { - val updateChecker = GithubUpdateChecker(context) - updateChecker.checkForApplicationUpdate() - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe({ release -> - //Get version of latest release - var newVersion = release.version - newVersion = newVersion.replace("[^\\d.]".toRegex(), "") - - //Check if latest version is different from current version - if (newVersion != BuildConfig.VERSION_NAME) { - val downloadLink = release.downloadLink - - val n = context.notification() { - setContentTitle(context.getString(R.string.update_check_notification_update_available)) - addAction(android.R.drawable.stat_sys_download_done, context.getString(eu.kanade.tachiyomi.R.string.action_download), - UpdateDownloader(context).getInstallOnReceivedIntent(UpdateDownloader.InstallOnReceived.RETRY_DOWNLOAD, downloadLink)) - setSmallIcon(android.R.drawable.stat_sys_download_done) - } - // Displays the progress bar on notification - context.notificationManager.notify(0, n); - } - }, { - it.printStackTrace() - }) - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateDownloaderService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateDownloaderService.kt new file mode 100644 index 000000000..3cd50c152 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateDownloaderService.kt @@ -0,0 +1,149 @@ +package eu.kanade.tachiyomi.data.updater + +import android.app.IntentService +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.support.v4.app.NotificationCompat +import eu.kanade.tachiyomi.Constants.NOTIFICATION_UPDATER_ID +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.network.GET +import eu.kanade.tachiyomi.data.network.NetworkHelper +import eu.kanade.tachiyomi.data.network.ProgressListener +import eu.kanade.tachiyomi.data.network.newCallWithProgress +import eu.kanade.tachiyomi.util.notificationManager +import eu.kanade.tachiyomi.util.saveTo +import timber.log.Timber +import uy.kohesive.injekt.injectLazy +import java.io.File + +class UpdateDownloaderService : IntentService(UpdateDownloaderService::class.java.name) { + + companion object { + /** + * Download url. + */ + const val EXTRA_DOWNLOAD_URL = "eu.kanade.APP_DOWNLOAD_URL" + + /** + * Downloads a new update and let the user install the new version from a notification. + * @param context the application context. + * @param url the url to the new update. + */ + fun downloadUpdate(context: Context, url: String) { + val intent = Intent(context, UpdateDownloaderService::class.java).apply { + putExtra(EXTRA_DOWNLOAD_URL, url) + } + context.startService(intent) + } + + /** + * Prompt user with apk install intent + * @param context context + * @param file file of apk that is installed + */ + fun installAPK(context: Context, file: File) { + // Prompt install interface + val intent = Intent(Intent.ACTION_VIEW).apply { + setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive") + // Without this flag android returned a intent error! + flags = Intent.FLAG_ACTIVITY_NEW_TASK + } + context.startActivity(intent) + } + } + + /** + * Network helper + */ + private val network: NetworkHelper by injectLazy() + + override fun onHandleIntent(intent: Intent?) { + if (intent == null) return + + val url = intent.getStringExtra(EXTRA_DOWNLOAD_URL) ?: return + downloadApk(url) + } + + fun downloadApk(url: String) { + val progressNotification = NotificationCompat.Builder(this) + + progressNotification.update { + setContentTitle(getString(R.string.app_name)) + setContentText(getString(R.string.update_check_notification_download_in_progress)) + setSmallIcon(android.R.drawable.stat_sys_download) + setOngoing(true) + } + + // Progress of the download + var savedProgress = 0 + + val progressListener = object : ProgressListener { + override fun update(bytesRead: Long, contentLength: Long, done: Boolean) { + val progress = (100 * bytesRead / contentLength).toInt() + if (progress > savedProgress) { + savedProgress = progress + + progressNotification.update { setProgress(100, progress, false) } + } + } + } + + // Reference the context for later usage inside apply blocks. + val ctx = this + + try { + // Download the new update. + val response = network.client.newCallWithProgress(GET(url), progressListener).execute() + + // File where the apk will be saved + val apkFile = File(externalCacheDir, "update.apk") + + if (response.isSuccessful) { + response.body().source().saveTo(apkFile) + } else { + response.close() + throw Exception("Unsuccessful response") + } + + // Prompt the user to install the new update. + NotificationCompat.Builder(this).update { + setContentTitle(getString(R.string.app_name)) + setContentText(getString(R.string.update_check_notification_download_complete)) + setSmallIcon(android.R.drawable.stat_sys_download_done) + // Install action + addAction(R.drawable.ic_system_update_grey_24dp_img, + getString(R.string.action_install), + UpdateNotificationReceiver.installApkIntent(ctx, apkFile.absolutePath)) + // Cancel action + addAction(R.drawable.ic_clear_grey_24dp_img, + getString(R.string.action_cancel), + UpdateNotificationReceiver.cancelNotificationIntent(ctx)) + } + + } catch (e: Exception) { + Timber.e(e, e.message) + + // Prompt the user to retry the download. + NotificationCompat.Builder(this).update { + setContentTitle(getString(R.string.app_name)) + setContentText(getString(R.string.update_check_notification_download_error)) + setSmallIcon(android.R.drawable.stat_sys_download_done) + // Retry action + addAction(R.drawable.ic_refresh_grey_24dp_img, + getString(R.string.action_retry), + UpdateNotificationReceiver.downloadApkIntent(ctx, url)) + // Cancel action + addAction(R.drawable.ic_clear_grey_24dp_img, + getString(R.string.action_cancel), + UpdateNotificationReceiver.cancelNotificationIntent(ctx)) + } + } + } + + fun NotificationCompat.Builder.update(block: NotificationCompat.Builder.() -> Unit) { + block() + notificationManager.notify(NOTIFICATION_UPDATER_ID, build()) + } + +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateNotificationReceiver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateNotificationReceiver.kt new file mode 100644 index 000000000..cb8716115 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateNotificationReceiver.kt @@ -0,0 +1,67 @@ +package eu.kanade.tachiyomi.data.updater + +import android.app.PendingIntent +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import eu.kanade.tachiyomi.Constants.NOTIFICATION_UPDATER_ID +import eu.kanade.tachiyomi.util.notificationManager +import java.io.File + +class UpdateNotificationReceiver : BroadcastReceiver() { + + override fun onReceive(context: Context, intent: Intent) { + when (intent.action) { + ACTION_INSTALL_APK -> { + UpdateDownloaderService.installAPK(context, + File(intent.getStringExtra(EXTRA_FILE_LOCATION))) + cancelNotification(context) + } + ACTION_DOWNLOAD_UPDATE -> UpdateDownloaderService.downloadUpdate(context, + intent.getStringExtra(UpdateDownloaderService.EXTRA_DOWNLOAD_URL)) + ACTION_CANCEL_NOTIFICATION -> cancelNotification(context) + } + } + + fun cancelNotification(context: Context) { + context.notificationManager.cancel(NOTIFICATION_UPDATER_ID) + } + + companion object { + // Install apk action + const val ACTION_INSTALL_APK = "eu.kanade.INSTALL_APK" + + // Download apk action + const val ACTION_DOWNLOAD_UPDATE = "eu.kanade.RETRY_DOWNLOAD" + + // Cancel notification action + const val ACTION_CANCEL_NOTIFICATION = "eu.kanade.CANCEL_NOTIFICATION" + + // Absolute path of apk file + const val EXTRA_FILE_LOCATION = "file_location" + + fun cancelNotificationIntent(context: Context): PendingIntent { + val intent = Intent(context, UpdateNotificationReceiver::class.java).apply { + action = ACTION_CANCEL_NOTIFICATION + } + return PendingIntent.getBroadcast(context, 0, intent, 0) + } + + fun installApkIntent(context: Context, path: String): PendingIntent { + val intent = Intent(context, UpdateNotificationReceiver::class.java).apply { + action = ACTION_INSTALL_APK + putExtra(EXTRA_FILE_LOCATION, path) + } + return PendingIntent.getBroadcast(context, 0, intent, 0) + } + + fun downloadApkIntent(context: Context, url: String): PendingIntent { + val intent = Intent(context, UpdateNotificationReceiver::class.java).apply { + action = ACTION_DOWNLOAD_UPDATE + putExtra(UpdateDownloaderService.EXTRA_DOWNLOAD_URL, url) + } + return PendingIntent.getBroadcast(context, 0, intent, 0) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAboutFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAboutFragment.kt index 284995a36..7a7463478 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAboutFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAboutFragment.kt @@ -1,18 +1,21 @@ package eu.kanade.tachiyomi.ui.setting import android.os.Bundle -import android.support.v7.preference.SwitchPreferenceCompat import android.support.v7.preference.XpPreferenceFragment import android.view.View import com.afollestad.materialdialogs.MaterialDialog import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.updater.GithubUpdateChecker -import eu.kanade.tachiyomi.data.updater.UpdateDownloader +import eu.kanade.tachiyomi.data.updater.GithubUpdateResult +import eu.kanade.tachiyomi.data.updater.UpdateCheckerService +import eu.kanade.tachiyomi.data.updater.UpdateDownloaderService import eu.kanade.tachiyomi.util.toast +import net.xpece.android.support.preference.SwitchPreference import rx.Subscription import rx.android.schedulers.AndroidSchedulers import rx.schedulers.Schedulers +import timber.log.Timber import java.text.DateFormat import java.text.ParseException import java.text.SimpleDateFormat @@ -22,15 +25,15 @@ class SettingsAboutFragment : SettingsFragment() { /** * Checks for new releases */ - private val updateChecker by lazy { GithubUpdateChecker(activity) } + private val updateChecker by lazy { GithubUpdateChecker() } /** * The subscribtion service of the obtained release object */ private var releaseSubscription: Subscription? = null - val automaticUpdateToggle by lazy { - findPreference(getString(R.string.pref_enable_automatic_updates_key)) as SwitchPreferenceCompat + val automaticUpdates by lazy { + findPreference(getString(R.string.pref_enable_automatic_updates_key)) as SwitchPreference } companion object { @@ -59,13 +62,17 @@ class SettingsAboutFragment : SettingsFragment() { true } - //TODO One glorious day enable this and add the magnificent option for auto update checking. - // automaticUpdateToggle.isEnabled = true - // automaticUpdateToggle.setOnPreferenceChangeListener { preference, any -> - // val status = any as Boolean - // UpdateDownloaderAlarm.startAlarm(activity, 12, status) - // true - // } + automaticUpdates.setOnPreferenceChangeListener { preference, any -> + val checked = any as Boolean + if (checked) { + UpdateCheckerService.setupTask(context) + } else { + UpdateCheckerService.cancelTask(context) + } + true + } + } else { + automaticUpdates.isVisible = false } buildTime.summary = getFormattedBuildTime() @@ -98,36 +105,35 @@ class SettingsAboutFragment : SettingsFragment() { private fun checkVersion() { releaseSubscription?.unsubscribe() - releaseSubscription = updateChecker.checkForApplicationUpdate() + context.toast(R.string.update_check_look_for_updates) + + releaseSubscription = updateChecker.checkForUpdate() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .subscribe({ release -> - //Get version of latest release - var newVersion = release.version - newVersion = newVersion.replace("[^\\d.]".toRegex(), "") + .subscribe({ result -> + when (result) { + is GithubUpdateResult.NewUpdate -> { + val body = result.release.changeLog + val url = result.release.downloadLink - //Check if latest version is different from current version - if (newVersion != BuildConfig.VERSION_NAME) { - val downloadLink = release.downloadLink - val body = release.changeLog - - //Create confirmation window - MaterialDialog.Builder(activity) - .title(R.string.update_check_title) - .content(body) - .positiveText(getString(R.string.update_check_confirm)) - .negativeText(getString(R.string.update_check_ignore)) - .onPositive { dialog, which -> - // User output that download has started - activity.toast(R.string.update_check_download_started) - // Start download - UpdateDownloader(activity.applicationContext).execute(downloadLink) - }.show() - } else { - activity.toast(R.string.update_check_no_new_updates) + // Create confirmation window + MaterialDialog.Builder(context) + .title(R.string.update_check_title) + .content(body) + .positiveText(getString(R.string.update_check_confirm)) + .negativeText(getString(R.string.update_check_ignore)) + .onPositive { dialog, which -> + // Start download + UpdateDownloaderService.downloadUpdate(context, url) + } + .show() + } + is GithubUpdateResult.NoNewUpdate -> { + context.toast(R.string.update_check_no_new_updates) + } } - }, { - it.printStackTrace() + }, { error -> + Timber.e(error, error.message) }) } diff --git a/app/src/main/res/xml/pref_about.xml b/app/src/main/res/xml/pref_about.xml index b706570d5..e422ce039 100644 --- a/app/src/main/res/xml/pref_about.xml +++ b/app/src/main/res/xml/pref_about.xml @@ -12,12 +12,11 @@ android:summary="@string/pref_acra_summary" android:title="@string/pref_enable_acra"/> - - - - - - + Date: Thu, 8 Sep 2016 18:30:29 +0200 Subject: [PATCH 31/79] Upgrade dependencies, use new Timber's overloaded method for errors --- app/build.gradle | 8 ++++---- .../kanade/tachiyomi/data/download/DownloadManager.kt | 10 +++++----- .../tachiyomi/data/updater/UpdateDownloaderService.kt | 4 ++-- .../eu/kanade/tachiyomi/ui/backup/BackupFragment.kt | 6 +++--- .../kanade/tachiyomi/ui/catalogue/CatalogueFragment.kt | 2 +- .../tachiyomi/ui/catalogue/CataloguePresenter.kt | 4 ++-- .../kanade/tachiyomi/ui/download/DownloadPresenter.kt | 2 +- .../eu/kanade/tachiyomi/ui/library/LibraryFragment.kt | 2 +- .../tachiyomi/ui/manga/chapter/ChaptersFragment.kt | 2 +- .../tachiyomi/ui/manga/chapter/ChaptersPresenter.kt | 2 +- .../ui/manga/myanimelist/MyAnimeListPresenter.kt | 2 +- .../eu/kanade/tachiyomi/ui/reader/ChapterLoader.kt | 6 +++--- .../eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt | 2 +- .../ui/recent_updates/RecentChaptersFragment.kt | 2 +- .../ui/recent_updates/RecentChaptersPresenter.kt | 2 +- .../ui/recently_read/RecentlyReadPresenter.kt | 2 +- .../tachiyomi/ui/setting/SettingsAboutFragment.kt | 2 +- 17 files changed, 30 insertions(+), 30 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index a60ef1f04..5c9fd56d1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -83,7 +83,7 @@ android { dependencies { // Modified dependencies - compile 'com.github.inorichi:subsampling-scale-image-view:421fb81' + compile 'com.github.inorichi:subsampling-scale-image-view:2d9c854' compile 'com.github.inorichi:ReactiveNetwork:69092ed' // Android support library @@ -100,7 +100,7 @@ dependencies { // ReactiveX compile 'io.reactivex:rxandroid:1.2.1' - compile 'io.reactivex:rxjava:1.1.8' + compile 'io.reactivex:rxjava:1.1.10' compile 'com.f2prateek.rx.preferences:rx-preferences:1.0.2' // Network client @@ -113,7 +113,7 @@ dependencies { compile "com.squareup.retrofit2:adapter-rxjava:$retrofit_version" // IO - compile 'com.squareup.okio:okio:1.9.0' + compile 'com.squareup.okio:okio:1.10.0' // JSON compile 'com.google.code.gson:gson:2.7' @@ -151,7 +151,7 @@ dependencies { compile 'com.github.bumptech.glide:okhttp3-integration:1.4.0@aar' // Logging - compile 'com.jakewharton.timber:timber:4.1.2' + compile 'com.jakewharton.timber:timber:4.3.0' // Crash reports compile 'ch.acra:acra:4.9.0' diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt index 483f06ba6..1661d6a44 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt @@ -80,10 +80,10 @@ class DownloadManager( if (areAllDownloadsFinished()) { DownloadService.stop(context) } - }, { e -> + }, { error -> DownloadService.stop(context) - Timber.e(e, e.message) - downloadNotifier.onError(e.message) + Timber.e(error) + downloadNotifier.onError(error.message) }) if (!isRunning) { @@ -369,8 +369,8 @@ class DownloadManager( try { it.write(gson.toJson(pages).toByteArray()) it.flush() - } catch (e: Exception) { - Timber.e(e, e.message) + } catch (error: Exception) { + Timber.e(error) } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateDownloaderService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateDownloaderService.kt index 3cd50c152..562277a30 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateDownloaderService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateDownloaderService.kt @@ -121,8 +121,8 @@ class UpdateDownloaderService : IntentService(UpdateDownloaderService::class.jav UpdateNotificationReceiver.cancelNotificationIntent(ctx)) } - } catch (e: Exception) { - Timber.e(e, e.message) + } catch (error: Exception) { + Timber.e(error) // Prompt the user to retry the download. NotificationCompat.Builder(this).update { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/backup/BackupFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/backup/BackupFragment.kt index 8f06b2cbc..28d4c01c7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/backup/BackupFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/backup/BackupFragment.kt @@ -121,9 +121,9 @@ class BackupFragment : BaseRxFragment() { .observeOn(AndroidSchedulers.mainThread()) .subscribe({ presenter.restoreBackup(it) - }, { - context.toast(it.message) - Timber.e(it, it.message) + }, { error -> + context.toast(error.message) + Timber.e(error) }) .apply { subscriptions.add(this) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueFragment.kt index e50bb26f4..42c073b02 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueFragment.kt @@ -332,7 +332,7 @@ class CatalogueFragment : BaseRxFragment(), FlexibleViewHold */ fun onAddPageError(error: Throwable) { hideProgressBar() - Timber.e(error, error.message) + Timber.e(error) catalogue_view.snack(error.message ?: "", Snackbar.LENGTH_INDEFINITE) { setAction(R.string.action_retry) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePresenter.kt index e4818f12a..ec81d486d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePresenter.kt @@ -143,7 +143,7 @@ class CataloguePresenter : BasePresenter() { .subscribeReplay({ view, page -> view.onAddPage(page.page, page.mangas) }, { view, error -> - Timber.e(error, error.message) + Timber.e(error) }) // Request first page. @@ -211,7 +211,7 @@ class CataloguePresenter : BasePresenter() { @Suppress("DEPRECATION") view?.onMangaInitialized(manga) }, { error -> - Timber.e(error, error.message) + Timber.e(error) }) .apply { add(this) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadPresenter.kt index ca6b31269..d02582a5c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadPresenter.kt @@ -35,7 +35,7 @@ class DownloadPresenter : BasePresenter() { .subscribeLatestCache({ view, downloads -> view.onNextDownloads(downloads) }, { view, error -> - Timber.e(error, error.message) + Timber.e(error) }) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt index 62d441e88..62c783dcc 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt @@ -427,7 +427,7 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback } } catch (error: IOException) { context.toast(R.string.notification_manga_update_failed) - Timber.e(error, error.message) + Timber.e(error) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersFragment.kt index 577f96fa0..8180f37b5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersFragment.kt @@ -376,7 +376,7 @@ class ChaptersFragment : BaseRxFragment(), ActionMode.Callbac fun onChaptersDeletedError(error: Throwable) { dismissDeletingDialog() - Timber.e(error, error.message) + Timber.e(error) } fun dismissDeletingDialog() { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersPresenter.kt index 3eb6e2ff4..df212d13f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersPresenter.kt @@ -111,7 +111,7 @@ class ChaptersPresenter : BasePresenter() { startableLatestCache(CHAPTER_STATUS_CHANGES, { getChapterStatusObservable() }, { view, download -> view.onChapterStatusChange(download) }, - { view, error -> Timber.e(error.cause, error.message) }) + { view, error -> Timber.e(error) }) // Find the active manga from the shared data or return. manga = SharedData.get(MangaEvent::class.java)?.manga ?: return diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/myanimelist/MyAnimeListPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/myanimelist/MyAnimeListPresenter.kt index f9d26b33a..248d04a96 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/myanimelist/MyAnimeListPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/myanimelist/MyAnimeListPresenter.kt @@ -102,7 +102,7 @@ class MyAnimeListPresenter : BasePresenter() { .observeOn(AndroidSchedulers.mainThread()) .subscribe({ next -> }, { error -> - Timber.e(error, error.message) + Timber.e(error) // Restart on error to set old values start(GET_MANGA_SYNC) })) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ChapterLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ChapterLoader.kt index 225a48aa5..e7c8a35b9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ChapterLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ChapterLoader.kt @@ -42,9 +42,9 @@ class ChapterLoader( .repeat() .subscribeOn(Schedulers.io()) .subscribe({ - }, { - if (it !is InterruptedException) { - Timber.e(it, it.message) + }, { error -> + if (error !is InterruptedException) { + Timber.e(error) } }) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt index a87a89596..be9f9e735 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt @@ -213,7 +213,7 @@ class ReaderActivity : BaseRxActivity() { } fun onChapterError(error: Throwable) { - Timber.e(error, error.message) + Timber.e(error) finish() toast(error.message) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersFragment.kt index 8734456f9..c59cd7f71 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersFragment.kt @@ -253,7 +253,7 @@ class RecentChaptersFragment */ fun onChaptersDeletedError(error: Throwable) { dismissDeletingDialog() - Timber.e(error, error.message) + Timber.e(error) } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersPresenter.kt index f08af4599..219b6b807 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersPresenter.kt @@ -71,7 +71,7 @@ class RecentChaptersPresenter : BasePresenter() { // Set chapter status view.onChapterStatusChange(download) }, - { view, error -> Timber.e(error.cause, error.message) } + { view, error -> Timber.e(error) } ) if (savedState == null) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadPresenter.kt index bcae72c1a..9f50473a4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadPresenter.kt @@ -115,7 +115,7 @@ class RecentlyReadPresenter : BasePresenter() { .subscribeFirst({ view, chapter -> view.onOpenNextChapter(chapter, manga) }, { view, error -> - Timber.e(error, error.message) + Timber.e(error) }) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAboutFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAboutFragment.kt index 7a7463478..aa8b97b94 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAboutFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAboutFragment.kt @@ -133,7 +133,7 @@ class SettingsAboutFragment : SettingsFragment() { } } }, { error -> - Timber.e(error, error.message) + Timber.e(error) }) } From 24bdee626fa38d224522706f96b7813059359b35 Mon Sep 17 00:00:00 2001 From: Gilfar Date: Fri, 9 Sep 2016 19:20:24 +0200 Subject: [PATCH 32/79] parse manga from the future (#458) --- .../tachiyomi/data/source/online/english/Mangasee.kt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangasee.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangasee.kt index 118cf212c..a53eee06c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangasee.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangasee.kt @@ -22,7 +22,7 @@ class Mangasee(context: Context, override val id: Int) : ParsedOnlineSource(cont override val lang: Language get() = EN - private val datePattern = Pattern.compile("(\\d+)\\s+(.*?)s? ago.*") + private val datePattern = Pattern.compile("(\\d+)\\s+(.*?)s? (from now|ago).*") private val dateFields = HashMap().apply { put("second", Calendar.SECOND) @@ -34,6 +34,11 @@ class Mangasee(context: Context, override val id: Int) : ParsedOnlineSource(cont put("year", Calendar.YEAR) } + private val dateRelationFields = HashMap().apply { + put("from now", 1) + put("ago", -1) + } + override fun popularMangaInitialUrl() = "$baseUrl/search_result.php?Action=Yes&order=popularity&numResultPerPage=20&sort=desc" override fun popularMangaSelector() = "div.well > table > tbody > tr" @@ -93,9 +98,10 @@ class Mangasee(context: Context, override val id: Int) : ParsedOnlineSource(cont if (m.matches()) { val amount = Integer.parseInt(m.group(1)) val unit = m.group(2) + val relation = m.group(3) return Calendar.getInstance().apply { - add(dateFields[unit]!!, -amount) + add(dateFields[unit]!!, dateRelationFields[relation]!! * amount) }.time.time } else { return 0 From 7c3cd10696fbfcba90751220664daf822996d1c3 Mon Sep 17 00:00:00 2001 From: len Date: Sun, 11 Sep 2016 16:00:06 +0200 Subject: [PATCH 33/79] Notify first page change --- .../tachiyomi/ui/reader/ReaderActivity.kt | 17 ++++++++++------- .../ui/reader/viewer/base/BaseReader.kt | 15 +++------------ .../ui/reader/viewer/pager/PagerReader.kt | 7 +++++-- .../ui/reader/viewer/webtoon/WebtoonReader.kt | 2 +- 4 files changed, 19 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt index be9f9e735..c148383a6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt @@ -20,6 +20,7 @@ import eu.kanade.tachiyomi.data.database.models.Chapter 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.data.source.model.Page import eu.kanade.tachiyomi.ui.base.activity.BaseRxActivity import eu.kanade.tachiyomi.ui.reader.viewer.base.BaseReader import eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal.LeftToRightReader @@ -308,15 +309,18 @@ class ReaderActivity : BaseRxActivity() { return fragment } - fun onPageChanged(currentPageIndex: Int, totalPages: Int) { - val page = currentPageIndex + 1 - page_number.text = "$page/$totalPages" + fun onPageChanged(page: Page) { + presenter.onPageChanged(page) + + val pageNumber = page.pageNumber + 1 + val pageCount = page.chapter.pages!!.size + page_number.text = "$pageNumber/$pageCount" if (page_seekbar.rotation != 180f) { - left_page_text.text = "$page" + left_page_text.text = "$pageNumber" } else { - right_page_text.text = "$page" + right_page_text.text = "$pageNumber" } - page_seekbar.progress = currentPageIndex + page_seekbar.progress = page.pageNumber } fun gotoPageInCurrentChapter(pageIndex: Int) { @@ -326,7 +330,6 @@ class ReaderActivity : BaseRxActivity() { val requestedPage = activePage.chapter.pages!![pageIndex] it.setActivePage(requestedPage) } - } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/base/BaseReader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/base/BaseReader.kt index 360696b62..1e6667fee 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/base/BaseReader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/base/BaseReader.kt @@ -66,16 +66,6 @@ abstract class BaseReader : BaseFragment() { */ private var hasRequestedNextChapter: Boolean = false - /** - * Updates the reader activity with the active page. - */ - fun updatePageNumber() { - val activePage = getActivePage() - if (activePage != null) { - readerActivity.onPageChanged(activePage.pageNumber, activePage.chapter.pages!!.size) - } - } - /** * Returns the active page. */ @@ -91,11 +81,13 @@ abstract class BaseReader : BaseFragment() { fun onPageChanged(position: Int) { val oldPage = pages[currentPage] val newPage = pages[position] - readerActivity.presenter.onPageChanged(newPage) val oldChapter = oldPage.chapter val newChapter = newPage.chapter + // Update page indicator and seekbar + readerActivity.onPageChanged(newPage) + // Active chapter has changed. if (oldChapter.id != newChapter.id) { readerActivity.onEnterChapter(newPage.chapter, newPage.pageNumber) @@ -108,7 +100,6 @@ abstract class BaseReader : BaseFragment() { } currentPage = position - updatePageNumber() } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReader.kt index fafd4f3a7..8f7541105 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReader.kt @@ -219,8 +219,11 @@ abstract class PagerReader : BaseReader() { protected fun setPagesOnAdapter() { if (pages.isNotEmpty()) { adapter.pages = pages - setActivePage(currentPage) - updatePageNumber() + if (currentPage == pager.currentItem) { + onPageChanged(currentPage) + } else { + setActivePage(currentPage) + } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonReader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonReader.kt index 7565f8d5e..9795d8130 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonReader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonReader.kt @@ -177,7 +177,7 @@ class WebtoonReader : BaseReader() { if (pages.isNotEmpty()) { adapter.pages = pages recycler.adapter = adapter - updatePageNumber() + onPageChanged(currentPage) } } From 91829b0e7d35b6e665d31230ccedfa35760988a0 Mon Sep 17 00:00:00 2001 From: len Date: Thu, 15 Sep 2016 18:00:54 +0200 Subject: [PATCH 34/79] Select categories for global update --- app/src/main/AndroidManifest.xml | 33 ++--- .../data/library/LibraryUpdateService.kt | 124 +++++------------- .../data/library/LibraryUpdateTrigger.kt | 52 ++++++++ .../data/preference/PreferenceKeys.kt | 2 + .../data/preference/PreferencesHelper.kt | 2 + .../ui/setting/SettingsGeneralFragment.kt | 32 ++++- app/src/main/res/values/keys.xml | 1 + app/src/main/res/values/strings.xml | 1 + app/src/main/res/xml/pref_general.xml | 4 + .../data/library/LibraryUpdateAlarmTest.kt | 124 ------------------ .../data/library/LibraryUpdateServiceTest.kt | 4 +- 11 files changed, 135 insertions(+), 244 deletions(-) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateTrigger.kt delete mode 100644 app/src/test/java/eu/kanade/tachiyomi/data/library/LibraryUpdateAlarmTest.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 3c8f5400d..d91c3b2d6 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -59,6 +59,15 @@ + + + + + + - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt index b36acef0e..612682170 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt @@ -8,8 +8,6 @@ import android.content.Intent import android.os.IBinder import android.os.PowerManager import android.support.v4.app.NotificationCompat -import com.github.pwittchen.reactivenetwork.library.ConnectivityStatus -import com.github.pwittchen.reactivenetwork.library.ReactiveNetwork import eu.kanade.tachiyomi.Constants import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.DatabaseHelper @@ -17,10 +15,14 @@ import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.library.LibraryUpdateService.Companion.start import eu.kanade.tachiyomi.data.preference.PreferencesHelper +import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.data.source.SourceManager import eu.kanade.tachiyomi.data.source.online.OnlineSource import eu.kanade.tachiyomi.ui.main.MainActivity -import eu.kanade.tachiyomi.util.* +import eu.kanade.tachiyomi.util.AndroidComponentUtil +import eu.kanade.tachiyomi.util.notification +import eu.kanade.tachiyomi.util.notificationManager +import eu.kanade.tachiyomi.util.syncChaptersWithSource import rx.Observable import rx.Subscription import rx.schedulers.Schedulers @@ -97,7 +99,7 @@ class LibraryUpdateService : Service() { * * @param context the application context. * @param isManual whether the update has been manually triggered. - * @param category a specific category to update, or null for all in the library. + * @param category a specific category to update, or null for global update. */ fun start(context: Context, isManual: Boolean = false, category: Category? = null) { if (!isRunning(context)) { @@ -156,45 +158,7 @@ class LibraryUpdateService : Service() { * @return the start value of the command. */ override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - - // Get connectivity status - val connection = ReactiveNetwork().getConnectivityStatus(this, true) - - // Get library update restrictions - val restrictions = preferences.libraryUpdateRestriction() - - // Check if users updates library manual - val isManualUpdate = intent?.getBooleanExtra(UPDATE_IS_MANUAL, false) ?: false - - // Whether to cancel the update. - var cancelUpdate = false - - // Check if device has internet connection - // Check if device has wifi connection if only wifi is enabled - if (connection == ConnectivityStatus.OFFLINE || (!isManualUpdate && "wifi" in restrictions - && connection != ConnectivityStatus.WIFI_CONNECTED_HAS_INTERNET)) { - - if (isManualUpdate) { - toast(R.string.notification_no_connection_title) - } - - // Enable library update when connection available - AndroidComponentUtil.toggleComponent(this, SyncOnConnectionAvailable::class.java, true) - cancelUpdate = true - } - if (!isManualUpdate && "ac" in restrictions && !DeviceUtil.isPowerConnected(this)) { - AndroidComponentUtil.toggleComponent(this, SyncOnPowerConnected::class.java, true) - cancelUpdate = true - } - - if (cancelUpdate) { - stopSelf(startId) - return Service.START_NOT_STICKY - } - - // Stop enabled components. - AndroidComponentUtil.toggleComponent(this, SyncOnConnectionAvailable::class.java, false) - AndroidComponentUtil.toggleComponent(this, SyncOnPowerConnected::class.java, false) + if (intent == null) return Service.START_NOT_STICKY // Unsubscribe from any previous subscription if needed. subscription?.unsubscribe() @@ -202,15 +166,17 @@ class LibraryUpdateService : Service() { // Update favorite manga. Destroy service when completed or in case of an error. subscription = Observable.defer { updateMangaList(getMangaToUpdate(intent)) } .subscribeOn(Schedulers.io()) - .subscribe({}, - { - showNotification(getString(R.string.notification_update_error), "") - stopSelf(startId) - }, { + .subscribe({ + }, { + showNotification(getString(R.string.notification_update_error), "") + LibraryUpdateTrigger.setupTask(this) + stopSelf(startId) + }, { + LibraryUpdateTrigger.setupTask(this) stopSelf(startId) }) - return Service.START_STICKY + return Service.START_REDELIVER_INTENT } /** @@ -219,19 +185,26 @@ class LibraryUpdateService : Service() { * @param intent the update intent. * @return a list of manga to update */ - fun getMangaToUpdate(intent: Intent?): List { - val categoryId = intent?.getIntExtra(UPDATE_CATEGORY, -1) ?: -1 + fun getMangaToUpdate(intent: Intent): List { + val categoryId = intent.getIntExtra(UPDATE_CATEGORY, -1) - var toUpdate = if (categoryId != -1) + var listToUpdate = if (categoryId != -1) db.getLibraryMangas().executeAsBlocking().filter { it.category == categoryId } - else - db.getFavoriteMangas().executeAsBlocking() - - if (preferences.updateOnlyNonCompleted()) { - toUpdate = toUpdate.filter { it.status != Manga.COMPLETED } + else { + val categoriesToUpdate = preferences.libraryUpdateCategories().getOrDefault().map { it.toInt() } + if (categoriesToUpdate.isNotEmpty()) + db.getLibraryMangas().executeAsBlocking() + .filter { it.category in categoriesToUpdate } + .distinctBy { it.id } + else + db.getFavoriteMangas().executeAsBlocking().distinctBy { it.id } } - return toUpdate + if (preferences.updateOnlyNonCompleted()) { + listToUpdate = listToUpdate.filter { it.status != Manga.COMPLETED } + } + + return listToUpdate } /** @@ -410,41 +383,6 @@ class LibraryUpdateService : Service() { return PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) } - /** - * Class that triggers the library to update when a connection is available. It receives - * network changes. - */ - class SyncOnConnectionAvailable : BroadcastReceiver() { - /** - * Method called when a network change occurs. - * - * @param context the application context. - * @param intent the intent received. - */ - override fun onReceive(context: Context, intent: Intent) { - if (DeviceUtil.isNetworkConnected(context)) { - AndroidComponentUtil.toggleComponent(context, this.javaClass, false) - start(context) - } - } - } - - /** - * Class that triggers the library to update when connected to power. - */ - class SyncOnPowerConnected: BroadcastReceiver() { - /** - * Method called when AC is connected. - * - * @param context the application context. - * @param intent the intent received. - */ - override fun onReceive(context: Context, intent: Intent) { - AndroidComponentUtil.toggleComponent(context, this.javaClass, false) - start(context) - } - } - /** * Class that stops updating the library. */ diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateTrigger.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateTrigger.kt new file mode 100644 index 000000000..e58beba03 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateTrigger.kt @@ -0,0 +1,52 @@ +package eu.kanade.tachiyomi.data.library + +import android.content.Context +import com.google.android.gms.gcm.* +import eu.kanade.tachiyomi.data.preference.PreferencesHelper +import eu.kanade.tachiyomi.data.preference.getOrDefault +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get + +class LibraryUpdateTrigger : GcmTaskService() { + + override fun onInitializeTasks() { + setupTask(this) + } + + override fun onRunTask(params: TaskParams): Int { + LibraryUpdateService.start(this) + return GcmNetworkManager.RESULT_SUCCESS + } + + companion object { + fun setupTask(context: Context) { + val preferences = Injekt.get() + val interval = preferences.libraryUpdateInterval().getOrDefault() + if (interval > 0) { + val restrictions = preferences.libraryUpdateRestriction() + val acRestriction = "ac" in restrictions + val wifiRestriction = if ("wifi" in restrictions) + Task.NETWORK_STATE_UNMETERED + else + Task.NETWORK_STATE_ANY + + val task = PeriodicTask.Builder() + .setService(LibraryUpdateTrigger::class.java) + .setTag("Library periodic update") + .setPeriod(interval * 60 * 60L) + .setFlex(5 * 60) + .setRequiredNetwork(wifiRestriction) + .setRequiresCharging(acRestriction) + .setUpdateCurrent(true) + .setPersisted(true) + .build() + + GcmNetworkManager.getInstance(context).schedule(task) + } + } + + fun cancelTask(context: Context) { + GcmNetworkManager.getInstance(context).cancelAllTasks(LibraryUpdateTrigger::class.java) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt index c575c96e9..99432b13e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt @@ -74,6 +74,8 @@ class PreferenceKeys(context: Context) { val libraryUpdateRestriction = context.getString(R.string.pref_library_update_restriction_key) + val libraryUpdateCategories = context.getString(R.string.pref_library_update_categories_key) + val filterDownloaded = context.getString(R.string.pref_filter_downloaded_key) val filterUnread = context.getString(R.string.pref_filter_unread_key) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt index d88f17e0c..d14a5c6f5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt @@ -124,6 +124,8 @@ class PreferencesHelper(context: Context) { fun libraryUpdateRestriction() = prefs.getStringSet(keys.libraryUpdateRestriction, emptySet()) + fun libraryUpdateCategories() = rxPrefs.getStringSet(keys.libraryUpdateCategories, emptySet()) + fun libraryAsList() = rxPrefs.getBoolean(keys.libraryAsList, false) fun filterDownloaded() = rxPrefs.getBoolean(keys.filterDownloaded, false) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsGeneralFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsGeneralFragment.kt index 49d0ed2e5..deedb4363 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsGeneralFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsGeneralFragment.kt @@ -6,7 +6,8 @@ import android.support.v7.preference.PreferenceFragmentCompat import android.support.v7.preference.XpPreferenceFragment import android.view.View import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.library.LibraryUpdateAlarm +import eu.kanade.tachiyomi.data.database.DatabaseHelper +import eu.kanade.tachiyomi.data.library.LibraryUpdateTrigger import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.util.plusAssign import eu.kanade.tachiyomi.widget.preference.IntListPreference @@ -14,6 +15,7 @@ import eu.kanade.tachiyomi.widget.preference.LibraryColumnsDialog import eu.kanade.tachiyomi.widget.preference.SimpleDialogPreference import net.xpece.android.support.preference.MultiSelectListPreference import rx.Observable +import timber.log.Timber import uy.kohesive.injekt.injectLazy class SettingsGeneralFragment : SettingsFragment(), @@ -30,6 +32,8 @@ class SettingsGeneralFragment : SettingsFragment(), private val preferences: PreferencesHelper by injectLazy() + private val db: DatabaseHelper by injectLazy() + val columnsPreference by lazy { findPreference(getString(R.string.pref_library_columns_dialog_key)) as SimpleDialogPreference @@ -47,6 +51,10 @@ class SettingsGeneralFragment : SettingsFragment(), findPreference(getString(R.string.pref_theme_key)) as IntListPreference } + val categoryUpdate by lazy { + findPreference(getString(R.string.pref_library_update_categories_key)) as MultiSelectListPreference + } + override fun onViewCreated(view: View, savedState: Bundle?) { super.onViewCreated(view, savedState) @@ -60,10 +68,30 @@ class SettingsGeneralFragment : SettingsFragment(), .subscribe { updateColumnsSummary(it.first, it.second) } updateInterval.setOnPreferenceChangeListener { preference, newValue -> - LibraryUpdateAlarm.startAlarm(activity, (newValue as String).toInt()) + val enabled = (newValue as String).toInt() > 0 + if (enabled) + LibraryUpdateTrigger.setupTask(context) + else + LibraryUpdateTrigger.cancelTask(context) + true } + val dbCategories = db.getCategories().executeAsBlocking() + categoryUpdate.apply { + entries = dbCategories.map { it.name }.toTypedArray() + entryValues = dbCategories.map { it.id.toString() }.toTypedArray() + } + + subscriptions += preferences.libraryUpdateCategories().asObservable() + .subscribe { + Timber.e(it.joinToString()) + categoryUpdate.summary = it + .mapNotNull { id -> dbCategories.find { it.id == id.toInt() } } + .sortedBy { it.order } + .joinToString { it.name } + } + themePreference.setOnPreferenceChangeListener { preference, newValue -> (activity as SettingsActivity).parentFlags = SettingsActivity.FLAG_THEME_CHANGED activity.recreate() diff --git a/app/src/main/res/values/keys.xml b/app/src/main/res/values/keys.xml index 1149d78f4..b6e36a54a 100644 --- a/app/src/main/res/values/keys.xml +++ b/app/src/main/res/values/keys.xml @@ -14,6 +14,7 @@ pref_library_columns_portrait_key pref_library_columns_landscape_key pref_library_update_interval_key + library_update_categories pref_update_only_non_completed_key pref_auto_update_manga_sync_key pref_ask_update_manga_sync_key diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4f4e63295..e82cf670d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -85,6 +85,7 @@ Every 12 hours Daily Every 2 days + Categories to include in global update Library update restrictions Update only when the conditions are met Wi-Fi diff --git a/app/src/main/res/xml/pref_general.xml b/app/src/main/res/xml/pref_general.xml index 350b551a8..e1add0d0f 100644 --- a/app/src/main/res/xml/pref_general.xml +++ b/app/src/main/res/xml/pref_general.xml @@ -36,6 +36,10 @@ android:summary="%s" android:title="@string/pref_library_update_interval"/> + + >(Exception())) `when`(source.fetchChapterList(favManga[2])).thenReturn(Observable.just(chapters3)) - service.updateMangaList(service.getMangaToUpdate(null)).subscribe() + val intent = Intent() + service.updateMangaList(service.getMangaToUpdate(intent)).subscribe() // There are 3 network attempts and 2 insertions (1 request failed) assertThat(service.db.getChapters(favManga[0]).executeAsBlocking()).hasSize(2) From 72ae243fa263564ab989da8d7ed4ff5a2eb3217c Mon Sep 17 00:00:00 2001 From: len Date: Thu, 15 Sep 2016 18:04:36 +0200 Subject: [PATCH 35/79] Remove debug log --- .../eu/kanade/tachiyomi/ui/setting/SettingsGeneralFragment.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsGeneralFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsGeneralFragment.kt index deedb4363..f16793d5e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsGeneralFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsGeneralFragment.kt @@ -15,7 +15,6 @@ import eu.kanade.tachiyomi.widget.preference.LibraryColumnsDialog import eu.kanade.tachiyomi.widget.preference.SimpleDialogPreference import net.xpece.android.support.preference.MultiSelectListPreference import rx.Observable -import timber.log.Timber import uy.kohesive.injekt.injectLazy class SettingsGeneralFragment : SettingsFragment(), @@ -85,7 +84,6 @@ class SettingsGeneralFragment : SettingsFragment(), subscriptions += preferences.libraryUpdateCategories().asObservable() .subscribe { - Timber.e(it.joinToString()) categoryUpdate.summary = it .mapNotNull { id -> dbCategories.find { it.id == id.toInt() } } .sortedBy { it.order } From 3ce880bc626730115a9923ceba7eb13772b7a288 Mon Sep 17 00:00:00 2001 From: len Date: Thu, 15 Sep 2016 18:25:10 +0200 Subject: [PATCH 36/79] Ignore a random crash when closing the reader --- .../tachiyomi/ui/reader/ReaderPresenter.kt | 63 ++++++++++--------- 1 file changed, 34 insertions(+), 29 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt index a905b1cdd..c349779d6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt @@ -21,6 +21,7 @@ import rx.Observable import rx.Subscription import rx.android.schedulers.AndroidSchedulers import rx.schedulers.Schedulers +import timber.log.Timber import uy.kohesive.injekt.injectLazy import java.io.File import java.util.* @@ -349,38 +350,42 @@ class ReaderPresenter : BasePresenter() { val pages = chapter.pages ?: return - Observable - .fromCallable { - // Chapters with 1 page don't trigger page changes, so mark them as read. - if (pages.size == 1) { - chapter.read = true - } + Observable.fromCallable { + // Chapters with 1 page don't trigger page changes, so mark them as read. + if (pages.size == 1) { + chapter.read = true + } - // Cache current page list progress for online chapters to allow a faster reopen - if (!chapter.isDownloaded) { - source.let { if (it is OnlineSource) it.savePageList(chapter, pages) } - } + // Cache current page list progress for online chapters to allow a faster reopen + if (!chapter.isDownloaded) { + source.let { if (it is OnlineSource) it.savePageList(chapter, pages) } + } - if (chapter.read) { - val removeAfterReadSlots = prefs.removeAfterReadSlots() - when (removeAfterReadSlots) { - // Setting disabled - -1 -> { /**Empty function**/ } - // Remove current read chapter - 0 -> deleteChapter(chapter, manga) - // Remove previous chapter specified by user in settings. - else -> getAdjacentChaptersStrategy(chapter, removeAfterReadSlots) - .first?.let { deleteChapter(it, manga) } - } - } - - db.updateChapterProgress(chapter).executeAsBlocking() - - val history = History.create(chapter).apply { last_read = Date().time } - db.updateHistoryLastRead(history).executeAsBlocking() + if (chapter.read) { + val removeAfterReadSlots = prefs.removeAfterReadSlots() + when (removeAfterReadSlots) { + // Setting disabled + -1 -> { /**Empty function**/ } + // Remove current read chapter + 0 -> deleteChapter(chapter, manga) + // Remove previous chapter specified by user in settings. + else -> getAdjacentChaptersStrategy(chapter, removeAfterReadSlots) + .first?.let { deleteChapter(it, manga) } } - .subscribeOn(Schedulers.io()) - .subscribe() + } + + db.updateChapterProgress(chapter).executeAsBlocking() + + try { + val history = History.create(chapter).apply { last_read = Date().time } + db.updateHistoryLastRead(history).executeAsBlocking() + } catch (error: Exception) { + // TODO find out why it crashes + Timber.e(error) + } + } + .subscribeOn(Schedulers.io()) + .subscribe() } /** From 8512f97386f1d243e3e914df8f17a11f3cc315e2 Mon Sep 17 00:00:00 2001 From: len Date: Thu, 15 Sep 2016 18:39:16 +0200 Subject: [PATCH 37/79] Show default message when no categories selected --- .../tachiyomi/ui/setting/SettingsGeneralFragment.kt | 10 ++++++++-- app/src/main/res/values/strings.xml | 1 + 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsGeneralFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsGeneralFragment.kt index f16793d5e..4bb6d3f59 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsGeneralFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsGeneralFragment.kt @@ -84,10 +84,16 @@ class SettingsGeneralFragment : SettingsFragment(), subscriptions += preferences.libraryUpdateCategories().asObservable() .subscribe { - categoryUpdate.summary = it + val selectedCategories = it .mapNotNull { id -> dbCategories.find { it.id == id.toInt() } } .sortedBy { it.order } - .joinToString { it.name } + + val summary = if (selectedCategories.isEmpty()) + getString(R.string.all) + else + selectedCategories.joinToString { it.name } + + categoryUpdate.summary = summary } themePreference.setOnPreferenceChangeListener { preference, newValue -> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e82cf670d..b5e7c1e30 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -86,6 +86,7 @@ Daily Every 2 days Categories to include in global update + All Library update restrictions Update only when the conditions are met Wi-Fi From 20e2bf9682bc74f3e78a9efac25ab6a7668b862a Mon Sep 17 00:00:00 2001 From: len Date: Thu, 15 Sep 2016 18:46:51 +0200 Subject: [PATCH 38/79] Place restrictions above category selection --- app/src/main/res/xml/pref_general.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/res/xml/pref_general.xml b/app/src/main/res/xml/pref_general.xml index e1add0d0f..347f66576 100644 --- a/app/src/main/res/xml/pref_general.xml +++ b/app/src/main/res/xml/pref_general.xml @@ -36,10 +36,6 @@ android:summary="%s" android:title="@string/pref_library_update_interval"/> - - + + Date: Fri, 16 Sep 2016 11:31:34 +0200 Subject: [PATCH 39/79] Fix broken link (#470) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3492db974..04dd593b6 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ | Build | Download | Auto Update | |-------|----------|-------------| -| [![TeamCity (simple build status)](https://img.shields.io/teamcity/https/teamcity.kanade.eu/s/tachiyomi_Build.svg)](https://teamcity.kanade.eu/project.html?projectId=tachiyomi) [![Travis](https://img.shields.io/travis/inorichi/tachiyomi.svg)](https://travis-ci.org/inorichi/tachiyomi) | [![stable release](https://img.shields.io/github/release/inorichi/tachiyomi.svg?maxAge=3600&label=stable)](https://github.com/inorichi/tachiyomi/releases) [![latest dev build](https://img.shields.io/badge/dev-latest%20build-blue.svg)](http://tachiyomi.kanade.eu/latest/app-debug.apk) | [![fdroid release](https://img.shields.io/badge/stable-F--Droid-blue.svg)](https://f-droid.org/repository/browse/?fdid=eu.kanade.tachiyomi) [![fdroid debug](https://img.shields.io/badge/dev-F--Droid-blue.svg)](//github.com/inorichi/tachiyomi/wiki/FDroid-for-debug-versions) | +| [![TeamCity (simple build status)](https://img.shields.io/teamcity/https/teamcity.kanade.eu/s/tachiyomi_Build.svg)](https://teamcity.kanade.eu/project.html?projectId=tachiyomi) [![Travis](https://img.shields.io/travis/inorichi/tachiyomi.svg)](https://travis-ci.org/inorichi/tachiyomi) | [![stable release](https://img.shields.io/github/release/inorichi/tachiyomi.svg?maxAge=3600&label=stable)](https://github.com/inorichi/tachiyomi/releases) [![latest dev build](https://img.shields.io/badge/dev-latest%20build-blue.svg)](http://tachiyomi.kanade.eu/latest/app-debug.apk) | [![fdroid release](https://img.shields.io/badge/stable-F--Droid-blue.svg)](https://f-droid.org/repository/browse/?fdid=eu.kanade.tachiyomi) [![fdroid debug](https://img.shields.io/badge/dev-F--Droid-blue.svg)](//github.com/inorichi/tachiyomi/wiki/FDroid-for-dev-versions) | ## [Report an issue](https://github.com/inorichi/tachiyomi/blob/master/.github/CONTRIBUTING.md) From 08e26aa30d6181114d2ead6c9378546f8167053c Mon Sep 17 00:00:00 2001 From: len Date: Sat, 17 Sep 2016 11:15:18 +0200 Subject: [PATCH 40/79] Fix library update interval not being updated properly --- .../kanade/tachiyomi/data/library/LibraryUpdateTrigger.kt | 4 ++-- .../kanade/tachiyomi/ui/setting/SettingsGeneralFragment.kt | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateTrigger.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateTrigger.kt index e58beba03..8393243cc 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateTrigger.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateTrigger.kt @@ -19,9 +19,9 @@ class LibraryUpdateTrigger : GcmTaskService() { } companion object { - fun setupTask(context: Context) { + fun setupTask(context: Context, prefInterval: Int? = null) { val preferences = Injekt.get() - val interval = preferences.libraryUpdateInterval().getOrDefault() + val interval = prefInterval ?: preferences.libraryUpdateInterval().getOrDefault() if (interval > 0) { val restrictions = preferences.libraryUpdateRestriction() val acRestriction = "ac" in restrictions diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsGeneralFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsGeneralFragment.kt index 4bb6d3f59..8a52298a0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsGeneralFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsGeneralFragment.kt @@ -67,9 +67,9 @@ class SettingsGeneralFragment : SettingsFragment(), .subscribe { updateColumnsSummary(it.first, it.second) } updateInterval.setOnPreferenceChangeListener { preference, newValue -> - val enabled = (newValue as String).toInt() > 0 - if (enabled) - LibraryUpdateTrigger.setupTask(context) + val interval = (newValue as String).toInt() + if (interval > 0) + LibraryUpdateTrigger.setupTask(context, interval) else LibraryUpdateTrigger.cancelTask(context) From cb921436138acd639e5332cc57b03daeba8cfb9a Mon Sep 17 00:00:00 2001 From: len Date: Sun, 18 Sep 2016 11:50:52 +0200 Subject: [PATCH 41/79] Merge anilist backend --- .../data/mangasync/MangaSyncManager.kt | 5 + .../data/mangasync/anilist/Anilist.kt | 132 ++++++++++++++++++ .../data/mangasync/anilist/AnilistApi.kt | 89 ++++++++++++ .../mangasync/anilist/AnilistInterceptor.kt | 61 ++++++++ .../data/mangasync/anilist/model/ALManga.kt | 17 +++ .../mangasync/anilist/model/ALUserLists.kt | 6 + .../mangasync/anilist/model/ALUserManga.kt | 29 ++++ .../data/mangasync/anilist/model/OAuth.kt | 11 ++ .../ui/setting/AnilistLoginActivity.kt | 49 +++++++ .../ui/setting/SettingsSyncFragment.kt | 55 ++++++-- 10 files changed, 440 insertions(+), 14 deletions(-) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/mangasync/anilist/Anilist.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/mangasync/anilist/AnilistApi.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/mangasync/anilist/AnilistInterceptor.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/mangasync/anilist/model/ALManga.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/mangasync/anilist/model/ALUserLists.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/mangasync/anilist/model/ALUserManga.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/mangasync/anilist/model/OAuth.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/setting/AnilistLoginActivity.kt diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/mangasync/MangaSyncManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/mangasync/MangaSyncManager.kt index 220e75140..9a0093783 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/mangasync/MangaSyncManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/mangasync/MangaSyncManager.kt @@ -1,16 +1,21 @@ package eu.kanade.tachiyomi.data.mangasync import android.content.Context +import eu.kanade.tachiyomi.data.mangasync.anilist.Anilist import eu.kanade.tachiyomi.data.mangasync.myanimelist.MyAnimeList class MangaSyncManager(private val context: Context) { companion object { const val MYANIMELIST = 1 + const val ANILIST = 2 } val myAnimeList = MyAnimeList(context, MYANIMELIST) + val aniList = Anilist(context, ANILIST) + + // TODO enable anilist val services = listOf(myAnimeList) fun getService(id: Int) = services.find { it.id == id } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/mangasync/anilist/Anilist.kt b/app/src/main/java/eu/kanade/tachiyomi/data/mangasync/anilist/Anilist.kt new file mode 100644 index 000000000..dff6f530f --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/mangasync/anilist/Anilist.kt @@ -0,0 +1,132 @@ +package eu.kanade.tachiyomi.data.mangasync.anilist + +import android.content.Context +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.database.models.MangaSync +import eu.kanade.tachiyomi.data.mangasync.MangaSyncService +import rx.Completable +import rx.Observable +import timber.log.Timber + +class Anilist(private val context: Context, id: Int) : MangaSyncService(context, id) { + + companion object { + const val READING = 1 + const val COMPLETED = 2 + const val ON_HOLD = 3 + const val DROPPED = 4 + const val PLAN_TO_READ = 5 + + const val DEFAULT_STATUS = READING + const val DEFAULT_SCORE = 0 + } + + override val name = "AniList" + + private val interceptor by lazy { AnilistInterceptor(getPassword()) } + + private val api by lazy { + AnilistApi.createService(networkService.client.newBuilder() + .addInterceptor(interceptor) + .build()) + } + + override fun login(username: String, password: String) = login(password) + + fun login(authCode: String): Completable { + // Create a new api with the default client to avoid request interceptions. + return AnilistApi.createService(client) + // Request the access token from the API with the authorization code. + .requestAccessToken(authCode) + // Save the token in the interceptor. + .doOnNext { interceptor.setAuth(it) } + // Obtain the authenticated user from the API. + .zipWith(api.getCurrentUser().map { it["id"].toString() }) + { oauth, user -> Pair(user, oauth.refresh_token!!) } + // Save service credentials (username and refresh token). + .doOnNext { saveCredentials(it.first, it.second) } + // Logout on any error. + .doOnError { logout() } + .toCompletable() + } + + override fun logout() { + super.logout() + interceptor.setAuth(null) + } + + fun search(query: String): Observable> { + return api.search(query, 1) + .flatMap { Observable.from(it) } + .filter { it.type != "Novel" } + .map { it.toMangaSync() } + .toList() + } + + fun getList(): Observable> { + return api.getList(getUsername()) + .flatMap { Observable.from(it.flatten()) } + .map { it.toMangaSync() } + .toList() + } + + override fun add(manga: MangaSync): Observable { + return api.addManga(manga.remote_id, manga.last_chapter_read, manga.getAnilistStatus(), + manga.score.toInt()) + .doOnNext { it.body().close() } + .doOnNext { if (!it.isSuccessful) throw Exception("Could not add manga") } + .doOnError { Timber.e(it, it.message) } + .map { manga } + } + + override fun update(manga: MangaSync): Observable { + if (manga.total_chapters != 0 && manga.last_chapter_read == manga.total_chapters) { + manga.status = COMPLETED + } + return api.updateManga(manga.remote_id, manga.last_chapter_read, manga.getAnilistStatus(), + manga.score.toInt()) + .doOnNext { it.body().close() } + .doOnNext { if (!it.isSuccessful) throw Exception("Could not update manga") } + .doOnError { Timber.e(it, it.message) } + .map { manga } + } + + override fun bind(manga: MangaSync): Observable { + return getList() + .flatMap { userlist -> + manga.sync_id = id + val mangaFromList = userlist.find { it.remote_id == manga.remote_id } + if (mangaFromList != null) { + manga.copyPersonalFrom(mangaFromList) + update(manga) + } else { + // Set default fields if it's not found in the list + manga.score = DEFAULT_SCORE.toFloat() + manga.status = DEFAULT_STATUS + add(manga) + } + } + } + + override fun getStatus(status: Int): String = with(context) { + when (status) { + READING -> getString(R.string.reading) + COMPLETED -> getString(R.string.completed) + ON_HOLD -> getString(R.string.on_hold) + DROPPED -> getString(R.string.dropped) + PLAN_TO_READ -> getString(R.string.plan_to_read) + else -> "" + } + } + + private fun MangaSync.getAnilistStatus() = when (status) { + READING -> "reading" + COMPLETED -> "completed" + ON_HOLD -> "on-hold" + DROPPED -> "dropped" + PLAN_TO_READ -> "plan to read" + else -> throw NotImplementedError("Unknown status") + } + +} + diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/mangasync/anilist/AnilistApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/mangasync/anilist/AnilistApi.kt new file mode 100644 index 000000000..6280562be --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/mangasync/anilist/AnilistApi.kt @@ -0,0 +1,89 @@ +package eu.kanade.tachiyomi.data.mangasync.anilist + +import android.net.Uri +import com.google.gson.JsonObject +import eu.kanade.tachiyomi.data.mangasync.anilist.model.ALManga +import eu.kanade.tachiyomi.data.mangasync.anilist.model.ALUserLists +import eu.kanade.tachiyomi.data.mangasync.anilist.model.OAuth +import eu.kanade.tachiyomi.data.network.POST +import okhttp3.FormBody +import okhttp3.OkHttpClient +import okhttp3.ResponseBody +import retrofit2.Response +import retrofit2.Retrofit +import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory +import retrofit2.converter.gson.GsonConverterFactory +import retrofit2.http.* +import rx.Observable + +interface AnilistApi { + + companion object { + private const val clientId = "tachiyomi-hrtje" + private const val clientSecret = "nlGB5OmgE9YWq5dr3gIDbTQV0C" + private const val clientUrl = "tachiyomi://anilist-auth" + private const val baseUrl = "https://anilist.co/api/" + + fun authUrl() = Uri.parse("${baseUrl}auth/authorize").buildUpon() + .appendQueryParameter("grant_type", "authorization_code") + .appendQueryParameter("client_id", clientId) + .appendQueryParameter("redirect_uri", clientUrl) + .appendQueryParameter("response_type", "code") + .build() + + fun refreshTokenRequest(token: String) = POST("${baseUrl}auth/access_token", + body = FormBody.Builder() + .add("grant_type", "refresh_token") + .add("client_id", clientId) + .add("client_secret", clientSecret) + .add("refresh_token", token) + .build()) + + fun createService(client: OkHttpClient) = Retrofit.Builder() + .baseUrl(baseUrl) + .client(client) + .addConverterFactory(GsonConverterFactory.create()) + .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) + .build() + .create(AnilistApi::class.java) + + } + + @FormUrlEncoded + @POST("auth/access_token") + fun requestAccessToken( + @Field("code") code: String, + @Field("grant_type") grant_type: String = "authorization_code", + @Field("client_id") client_id: String = clientId, + @Field("client_secret") client_secret: String = clientSecret, + @Field("redirect_uri") redirect_uri: String = clientUrl) + : Observable + + @GET("user") + fun getCurrentUser(): Observable + + @GET("manga/search/{query}") + fun search(@Path("query") query: String, @Query("page") page: Int): Observable> + + @GET("user/{username}/mangalist") + fun getList(@Path("username") username: String): Observable + + @FormUrlEncoded + @PUT("mangalist") + fun addManga( + @Field("id") id: Int, + @Field("chapters_read") chapters_read: Int, + @Field("list_status") list_status: String, + @Field("score_raw") score_raw: Int) + : Observable> + + @FormUrlEncoded + @PUT("mangalist") + fun updateManga( + @Field("id") id: Int, + @Field("chapters_read") chapters_read: Int, + @Field("list_status") list_status: String, + @Field("score_raw") score_raw: Int) + : Observable> + +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/mangasync/anilist/AnilistInterceptor.kt b/app/src/main/java/eu/kanade/tachiyomi/data/mangasync/anilist/AnilistInterceptor.kt new file mode 100644 index 000000000..a7a1232f1 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/mangasync/anilist/AnilistInterceptor.kt @@ -0,0 +1,61 @@ +package eu.kanade.tachiyomi.data.mangasync.anilist + +import com.google.gson.Gson +import eu.kanade.tachiyomi.data.mangasync.anilist.model.OAuth +import okhttp3.Interceptor +import okhttp3.Response + +class AnilistInterceptor(private var refreshToken: String?) : Interceptor { + + /** + * OAuth object used for authenticated requests. + * + * Anilist returns the date without milliseconds. We fix that and make the token expire 1 minute + * before its original expiration date. + */ + private var oauth: OAuth? = null + set(value) { + field = value?.copy(expires = value.expires * 1000 - 60 * 1000) + } + + override fun intercept(chain: Interceptor.Chain): Response { + val originalRequest = chain.request() + + if (refreshToken.isNullOrEmpty()) { + throw Exception("Not authenticated with Anilist") + } + + // Refresh access token if null or expired. + if (oauth == null || oauth!!.isExpired()) { + val response = chain.proceed(AnilistApi.refreshTokenRequest(refreshToken!!)) + oauth = if (response.isSuccessful) { + Gson().fromJson(response.body().string(), OAuth::class.java) + } else { + response.close() + null + } + } + + // Throw on null auth. + if (oauth == null) { + throw Exception("Access token wasn't refreshed") + } + + // Add the authorization header to the original request. + val authRequest = originalRequest.newBuilder() + .addHeader("Authorization", "Bearer ${oauth!!.access_token}") + .build() + + return chain.proceed(authRequest) + } + + /** + * Called when the user authenticates with Anilist for the first time. Sets the refresh token + * and the oauth object. + */ + fun setAuth(oauth: OAuth?) { + refreshToken = oauth?.refresh_token + this.oauth = oauth + } + +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/mangasync/anilist/model/ALManga.kt b/app/src/main/java/eu/kanade/tachiyomi/data/mangasync/anilist/model/ALManga.kt new file mode 100644 index 000000000..7b4a64ebb --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/mangasync/anilist/model/ALManga.kt @@ -0,0 +1,17 @@ +package eu.kanade.tachiyomi.data.mangasync.anilist.model + +import eu.kanade.tachiyomi.data.database.models.MangaSync +import eu.kanade.tachiyomi.data.mangasync.MangaSyncManager + +data class ALManga( + val id: Int, + val title_romaji: String, + val type: String, + val total_chapters: Int) { + + fun toMangaSync() = MangaSync.create(MangaSyncManager.ANILIST).apply { + remote_id = this@ALManga.id + title = title_romaji + total_chapters = this@ALManga.total_chapters + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/mangasync/anilist/model/ALUserLists.kt b/app/src/main/java/eu/kanade/tachiyomi/data/mangasync/anilist/model/ALUserLists.kt new file mode 100644 index 000000000..7caf7def5 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/mangasync/anilist/model/ALUserLists.kt @@ -0,0 +1,6 @@ +package eu.kanade.tachiyomi.data.mangasync.anilist.model + +data class ALUserLists(val lists: Map>) { + + fun flatten() = lists.values.flatten() +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/mangasync/anilist/model/ALUserManga.kt b/app/src/main/java/eu/kanade/tachiyomi/data/mangasync/anilist/model/ALUserManga.kt new file mode 100644 index 000000000..406ed8e01 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/mangasync/anilist/model/ALUserManga.kt @@ -0,0 +1,29 @@ +package eu.kanade.tachiyomi.data.mangasync.anilist.model + +import eu.kanade.tachiyomi.data.database.models.MangaSync +import eu.kanade.tachiyomi.data.mangasync.MangaSyncManager +import eu.kanade.tachiyomi.data.mangasync.anilist.Anilist + +data class ALUserManga( + val id: Int, + val list_status: String, + val score_raw: Int, + val chapters_read: Int, + val manga: ALManga) { + + fun toMangaSync() = MangaSync.create(MangaSyncManager.ANILIST).apply { + remote_id = manga.id + status = getMangaSyncStatus() + score = score_raw.toFloat() + last_chapter_read = chapters_read + } + + fun getMangaSyncStatus() = when (list_status) { + "reading" -> Anilist.READING + "completed" -> Anilist.COMPLETED + "on-hold" -> Anilist.ON_HOLD + "dropped" -> Anilist.DROPPED + "plan to read" -> Anilist.PLAN_TO_READ + else -> throw NotImplementedError("Unknown status") + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/mangasync/anilist/model/OAuth.kt b/app/src/main/java/eu/kanade/tachiyomi/data/mangasync/anilist/model/OAuth.kt new file mode 100644 index 000000000..05b21a83b --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/mangasync/anilist/model/OAuth.kt @@ -0,0 +1,11 @@ +package eu.kanade.tachiyomi.data.mangasync.anilist.model + +data class OAuth( + val access_token: String, + val token_type: String, + val expires: Long, + val expires_in: Long, + val refresh_token: String?) { + + fun isExpired() = System.currentTimeMillis() > expires +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/AnilistLoginActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/AnilistLoginActivity.kt new file mode 100644 index 000000000..c7209e370 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/AnilistLoginActivity.kt @@ -0,0 +1,49 @@ +package eu.kanade.tachiyomi.ui.setting + +import android.content.Intent +import android.os.Bundle +import android.support.v7.app.AppCompatActivity +import android.view.Gravity.CENTER +import android.view.ViewGroup.LayoutParams.WRAP_CONTENT +import android.widget.FrameLayout +import android.widget.ProgressBar +import eu.kanade.tachiyomi.data.mangasync.MangaSyncManager +import rx.android.schedulers.AndroidSchedulers +import rx.schedulers.Schedulers +import uy.kohesive.injekt.injectLazy + +class AnilistLoginActivity : AppCompatActivity() { + + private val syncManager: MangaSyncManager by injectLazy() + + override fun onCreate(savedState: Bundle?) { + super.onCreate(savedState) + + val view = ProgressBar(this) + setContentView(view, FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT, CENTER)) + + val code = intent.data?.getQueryParameter("code") + if (code != null) { + syncManager.aniList.login(code) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ + returnToSettings() + }, { error -> + returnToSettings() + }) + } else { + syncManager.aniList.logout() + returnToSettings() + } + } + + private fun returnToSettings() { + finish() + + val intent = Intent(this, SettingsActivity::class.java) + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP) + startActivity(intent) + } + +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsSyncFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsSyncFragment.kt index 6e6c595b5..8cf1fcdd1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsSyncFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsSyncFragment.kt @@ -6,6 +6,7 @@ import android.support.v7.preference.PreferenceCategory import android.support.v7.preference.XpPreferenceFragment import android.view.View import eu.kanade.tachiyomi.data.mangasync.MangaSyncManager +import eu.kanade.tachiyomi.data.mangasync.MangaSyncService import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.widget.preference.LoginPreference import eu.kanade.tachiyomi.widget.preference.MangaSyncLoginDialog @@ -32,30 +33,56 @@ class SettingsSyncFragment : SettingsFragment() { override fun onViewCreated(view: View, savedState: Bundle?) { super.onViewCreated(view, savedState) - val themedContext = preferenceManager.context + registerService(syncManager.myAnimeList) - for (sync in syncManager.services) { - val pref = LoginPreference(themedContext).apply { - key = preferences.keys.syncUsername(sync.id) - title = sync.name +// registerService(syncManager.aniList) { +// val intent = CustomTabsIntent.Builder() +// .setToolbarColor(activity.theme.getResourceColor(R.attr.colorPrimary)) +// .build() +// intent.intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY) +// intent.launchUrl(activity, AnilistApi.authUrl()) +// } + } - setOnPreferenceClickListener { - val fragment = MangaSyncLoginDialog.newInstance(sync) - fragment.setTargetFragment(this@SettingsSyncFragment, SYNC_CHANGE_REQUEST) - fragment.show(fragmentManager, null) - true - } + private fun registerService( + service: T, + onPreferenceClick: (T) -> Unit = defaultOnPreferenceClick) { + + LoginPreference(preferenceManager.context).apply { + key = preferences.keys.syncUsername(service.id) + title = service.name + + setOnPreferenceClickListener { + onPreferenceClick(service) + true } - syncCategory.addPreference(pref) + syncCategory.addPreference(this) } } + private val defaultOnPreferenceClick: (MangaSyncService) -> Unit + get() = { + val fragment = MangaSyncLoginDialog.newInstance(it) + fragment.setTargetFragment(this, SYNC_CHANGE_REQUEST) + fragment.show(fragmentManager, null) + } + + override fun onResume() { + super.onResume() + // Manually refresh anilist holder +// updatePreference(syncManager.aniList.id) + } + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { if (requestCode == SYNC_CHANGE_REQUEST) { - val pref = findPreference(preferences.keys.syncUsername(resultCode)) as? LoginPreference - pref?.notifyChanged() + updatePreference(resultCode) } } + private fun updatePreference(id: Int) { + val pref = findPreference(preferences.keys.syncUsername(id)) as? LoginPreference + pref?.notifyChanged() + } + } From 58a2f7a874736d68a301e4da8dd123ef8bd40b8d Mon Sep 17 00:00:00 2001 From: inorichi Date: Sun, 18 Sep 2016 21:12:12 +0200 Subject: [PATCH 42/79] Hide catalogues (#466) Hide catalogues --- .../data/preference/PreferencesHelper.kt | 2 + .../ui/catalogue/CatalogueFragment.kt | 15 +- .../ui/catalogue/CataloguePresenter.kt | 9 +- .../ui/setting/SettingsSourcesFragment.kt | 122 +++++++++++----- .../preference/LoginCheckBoxPreference.kt | 56 +++++++ .../preference/SwitchPreferenceCategory.kt | 137 ++++++++++++++++++ .../drawable/ic_account_circle_black_24dp.xml | 9 ++ app/src/main/res/layout/pref_item_source.xml | 62 ++++++++ app/src/main/res/values/strings.xml | 6 +- app/src/main/res/xml/pref_sources.xml | 11 +- 10 files changed, 372 insertions(+), 57 deletions(-) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/widget/preference/LoginCheckBoxPreference.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/widget/preference/SwitchPreferenceCategory.kt create mode 100644 app/src/main/res/drawable/ic_account_circle_black_24dp.xml create mode 100644 app/src/main/res/layout/pref_item_source.xml diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt index d14a5c6f5..3acee36aa 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt @@ -134,4 +134,6 @@ class PreferencesHelper(context: Context) { fun automaticUpdates() = prefs.getBoolean(keys.automaticUpdates, false) + fun hiddenCatalogues() = rxPrefs.getStringSet("hidden_catalogues", emptySet()) + } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueFragment.kt index 42c073b02..b1f79b57c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueFragment.kt @@ -16,6 +16,7 @@ import com.afollestad.materialdialogs.MaterialDialog import com.f2prateek.rx.preferences.Preference import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Manga +import eu.kanade.tachiyomi.data.source.online.LoginSource import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment import eu.kanade.tachiyomi.ui.main.MainActivity @@ -45,7 +46,7 @@ class CatalogueFragment : BaseRxFragment(), FlexibleViewHold /** * Spinner shown in the toolbar to change the selected source. */ - private lateinit var spinner: Spinner + private var spinner: Spinner? = null /** * Adapter containing the list of manga from the catalogue. @@ -125,6 +126,14 @@ class CatalogueFragment : BaseRxFragment(), FlexibleViewHold } override fun onViewCreated(view: View, savedState: Bundle?) { + // If the source list is empty or it only has unlogged sources, return to main screen. + val sources = presenter.sources + if (sources.isEmpty() || sources.all { it is LoginSource && !it.isLogged() }) { + context.toast(R.string.no_valid_sources) + activity.onBackPressed() + return + } + // Initialize adapter, scroll listener and recycler views adapter = CatalogueAdapter(this) @@ -166,7 +175,7 @@ class CatalogueFragment : BaseRxFragment(), FlexibleViewHold val onItemSelected = IgnoreFirstSpinnerListener { position -> val source = spinnerAdapter.getItem(position) if (!presenter.isValidSource(source)) { - spinner.setSelection(selectedIndex) + spinner?.setSelection(selectedIndex) context.toast(R.string.source_requires_login) } else if (source != presenter.source) { selectedIndex = position @@ -264,7 +273,7 @@ class CatalogueFragment : BaseRxFragment(), FlexibleViewHold searchItem?.let { if (it.isActionViewExpanded) it.collapseActionView() } - toolbar.removeView(spinner) + spinner?.let { toolbar.removeView(it) } super.onDestroyView() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePresenter.kt index ec81d486d..37e3983ae 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePresenter.kt @@ -21,6 +21,7 @@ import rx.schedulers.Schedulers import rx.subjects.PublishSubject import timber.log.Timber import uy.kohesive.injekt.injectLazy +import java.util.NoSuchElementException /** * Presenter of [CatalogueFragment]. @@ -103,7 +104,11 @@ class CataloguePresenter : BasePresenter() { override fun onCreate(savedState: Bundle?) { super.onCreate(savedState) - source = getLastUsedSource() + try { + source = getLastUsedSource() + } catch (error: NoSuchElementException) { + return + } if (savedState != null) { query = savedState.getString(CataloguePresenter::query.name, "") @@ -324,6 +329,7 @@ class CataloguePresenter : BasePresenter() { */ private fun getEnabledSources(): List { val languages = prefs.enabledLanguages().getOrDefault() + val hiddenCatalogues = prefs.hiddenCatalogues().getOrDefault() // Ensure at least one language if (languages.isEmpty()) { @@ -332,6 +338,7 @@ class CataloguePresenter : BasePresenter() { return sourceManager.getOnlineSources() .filter { it.lang.code in languages } + .filterNot { it.id.toString() in hiddenCatalogues } .sortedBy { "(${it.lang.code}) ${it.name}" } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsSourcesFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsSourcesFragment.kt index 56147e256..e29c5369d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsSourcesFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsSourcesFragment.kt @@ -1,21 +1,19 @@ package eu.kanade.tachiyomi.ui.setting import android.content.Intent +import android.graphics.drawable.Drawable import android.os.Bundle -import android.support.v7.preference.Preference -import android.support.v7.preference.PreferenceGroup import android.support.v7.preference.XpPreferenceFragment import android.view.View import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.getOrDefault -import eu.kanade.tachiyomi.data.source.Source import eu.kanade.tachiyomi.data.source.SourceManager import eu.kanade.tachiyomi.data.source.getLanguages -import eu.kanade.tachiyomi.data.source.online.LoginSource -import eu.kanade.tachiyomi.util.plusAssign -import eu.kanade.tachiyomi.widget.preference.LoginPreference +import eu.kanade.tachiyomi.widget.preference.LoginCheckBoxPreference import eu.kanade.tachiyomi.widget.preference.SourceLoginDialog -import net.xpece.android.support.preference.MultiSelectListPreference +import eu.kanade.tachiyomi.widget.preference.SwitchPreferenceCategory +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy class SettingsSourcesFragment : SettingsFragment() { @@ -32,59 +30,105 @@ class SettingsSourcesFragment : SettingsFragment() { private val preferences: PreferencesHelper by injectLazy() - private val sourceManager: SourceManager by injectLazy() + private val onlineSources by lazy { Injekt.get().getOnlineSources() } - val languagesPref by lazy { findPreference("pref_source_languages") as MultiSelectListPreference } - - val sourcesPref by lazy { findPreference("pref_sources") as PreferenceGroup } + override fun setDivider(divider: Drawable?) { + super.setDivider(null) + } override fun onViewCreated(view: View, savedState: Bundle?) { super.onViewCreated(view, savedState) + // Remove dummy preference + preferenceScreen.removeAll() + + // Get the list of active language codes. + val activeLangsCodes = preferences.enabledLanguages().getOrDefault() + + // Get the list of languages ordered by name. val langs = getLanguages().sortedBy { it.lang } - val entryKeys = langs.map { it.code } - languagesPref.entries = langs.map { it.lang }.toTypedArray() - languagesPref.entryValues = entryKeys.toTypedArray() - languagesPref.values = preferences.enabledLanguages().getOrDefault() + // Order first by active languages, then inactive ones + val orderedLangs = langs.filter { it.code in activeLangsCodes } + + langs.filterNot { it.code in activeLangsCodes } - subscriptions += preferences.enabledLanguages().asObservable() - .subscribe { languages -> - sourcesPref.removeAll() - - val enabledSources = sourceManager.getOnlineSources() - .filter { it.lang.code in languages } - - for (source in enabledSources.filterIsInstance(LoginSource::class.java)) { - val pref = createLoginSourceEntry(source) - sourcesPref.addPreference(pref) - } - - // Hide category if it doesn't have any child - sourcesPref.isVisible = sourcesPref.preferenceCount > 0 + orderedLangs.forEach { lang -> + // Create a preference group and set initial state and change listener + SwitchPreferenceCategory(context).apply { + preferenceScreen.addPreference(this) + title = lang.lang + isPersistent = false + if (lang.code in activeLangsCodes) { + setChecked(true) + addLanguageSources(this) } + + setOnPreferenceChangeListener { preference, any -> + val checked = any as Boolean + val current = preferences.enabledLanguages().getOrDefault() + if (!checked) { + preferences.enabledLanguages().set(current - lang.code) + removeAll() + } else { + preferences.enabledLanguages().set(current + lang.code) + addLanguageSources(this) + } + true + } + } + } } - fun createLoginSourceEntry(source: Source): Preference { - return LoginPreference(preferenceManager.context).apply { - key = preferences.keys.sourceUsername(source.id) - title = source.toString() + /** + * Adds the source list for the given group (language). + * + * @param group the language category. + */ + private fun addLanguageSources(group: SwitchPreferenceCategory) { + val sources = onlineSources.filter { it.lang.lang == group.title }.sortedBy { it.name } + val hiddenCatalogues = preferences.hiddenCatalogues().getOrDefault() + + sources.forEach { source -> + val sourcePreference = LoginCheckBoxPreference(context, source).apply { + val id = source.id.toString() + title = source.name + key = getSourceKey(source.id) + isPersistent = false + isChecked = id !in hiddenCatalogues + + setOnPreferenceChangeListener { preference, any -> + val checked = any as Boolean + val current = preferences.hiddenCatalogues().getOrDefault() + + preferences.hiddenCatalogues().set(if (checked) + current - id + else + current + id) + + true + } + + setOnLoginClickListener { + val fragment = SourceLoginDialog.newInstance(source) + fragment.setTargetFragment(this@SettingsSourcesFragment, SOURCE_CHANGE_REQUEST) + fragment.show(fragmentManager, null) + } - setOnPreferenceClickListener { - val fragment = SourceLoginDialog.newInstance(source) - fragment.setTargetFragment(this@SettingsSourcesFragment, SOURCE_CHANGE_REQUEST) - fragment.show(fragmentManager, null) - true } + group.addPreference(sourcePreference) } } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { if (requestCode == SOURCE_CHANGE_REQUEST) { - val pref = findPreference(preferences.keys.sourceUsername(resultCode)) as? LoginPreference + val pref = findPreference(getSourceKey(resultCode)) as? LoginCheckBoxPreference pref?.notifyChanged() } } + private fun getSourceKey(sourceId: Int): String { + return "source_$sourceId" + } + } diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/LoginCheckBoxPreference.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/LoginCheckBoxPreference.kt new file mode 100644 index 000000000..1d4ef5862 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/LoginCheckBoxPreference.kt @@ -0,0 +1,56 @@ +package eu.kanade.tachiyomi.widget.preference + +import android.content.Context +import android.graphics.Color +import android.support.v7.preference.PreferenceViewHolder +import android.util.AttributeSet +import android.view.View +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.source.online.LoginSource +import eu.kanade.tachiyomi.data.source.online.OnlineSource +import eu.kanade.tachiyomi.util.setVectorCompat +import kotlinx.android.synthetic.main.pref_item_source.view.* +import net.xpece.android.support.preference.CheckBoxPreference + +class LoginCheckBoxPreference @JvmOverloads constructor( + context: Context, + val source: OnlineSource, + attrs: AttributeSet? = null +) : CheckBoxPreference(context, attrs) { + + init { + layoutResource = R.layout.pref_item_source + } + + private var onLoginClick: () -> Unit = {} + + override fun onBindViewHolder(holder: PreferenceViewHolder) { + super.onBindViewHolder(holder) + val loginFrame = holder.itemView.login_frame + if (source is LoginSource) { + val tint = if (source.isLogged()) + Color.argb(255, 76, 175, 80) + else + Color.argb(97, 0, 0, 0) + + holder.itemView.login.setVectorCompat(R.drawable.ic_account_circle_black_24dp, tint) + + loginFrame.visibility = View.VISIBLE + loginFrame.setOnClickListener { + onLoginClick() + } + } else { + loginFrame.visibility = View.GONE + } + } + + fun setOnLoginClickListener(block: () -> Unit) { + onLoginClick = block + } + + // Make method public + override public fun notifyChanged() { + super.notifyChanged() + } + +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/SwitchPreferenceCategory.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/SwitchPreferenceCategory.kt new file mode 100644 index 000000000..bf54136f1 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/SwitchPreferenceCategory.kt @@ -0,0 +1,137 @@ +package eu.kanade.tachiyomi.widget.preference + +import android.annotation.TargetApi +import android.content.Context +import android.content.res.TypedArray +import android.os.Build +import android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH +import android.support.v7.preference.PreferenceViewHolder +import android.support.v7.widget.SwitchCompat +import android.util.AttributeSet +import android.view.View +import android.widget.Checkable +import android.widget.CompoundButton +import android.widget.Switch +import eu.kanade.tachiyomi.util.getResourceColor +import net.xpece.android.support.preference.PreferenceCategory +import net.xpece.android.support.preference.R + +class SwitchPreferenceCategory @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null) +: PreferenceCategory( + context, + attrs, + R.attr.switchPreferenceCompatStyle, + R.style.Preference_Material_SwitchPreferenceCompat), +CompoundButton.OnCheckedChangeListener { + + init { + setTitleTextColor(context.theme.getResourceColor(R.attr.colorAccent)) + } + + private var mChecked = false + + private var mCheckedSet = false + + override fun onBindViewHolder(holder: PreferenceViewHolder) { + super.onBindViewHolder(holder) + syncSwitchView(holder) + } + + private fun syncSwitchView(holder: PreferenceViewHolder) { + val switchView = holder.findViewById(R.id.switchWidget) + syncSwitchView(switchView) + } + + @TargetApi(ICE_CREAM_SANDWICH) + private fun syncSwitchView(view: View) { + if (view is Checkable) { + val isChecked = view.isChecked + if (isChecked == mChecked) return + + if (view is SwitchCompat) { + view.setOnCheckedChangeListener(null) + } else if (NATIVE_SWITCH_CAPABLE && view is Switch) { + view.setOnCheckedChangeListener(null) + } + + view.toggle() + + if (view is SwitchCompat) { + view.setOnCheckedChangeListener(this) + } else if (NATIVE_SWITCH_CAPABLE && view is Switch) { + view.setOnCheckedChangeListener(this) + } + } + } + + override fun onCheckedChanged(buttonView: CompoundButton, isChecked: Boolean) { + if (!callChangeListener(isChecked)) { + buttonView.isChecked = !isChecked + } else { + setChecked(isChecked) + } + } + + override fun onClick() { + super.onClick() + + val newValue = !isChecked() + if (callChangeListener(newValue)) { + setChecked(newValue) + } + } + + /** + * Sets the checked state and saves it to the [SharedPreferences]. + * + * @param checked The checked state. + */ + fun setChecked(checked: Boolean) { + // Always persist/notify the first time; don't assume the field's default of false. + val changed = mChecked != checked + if (changed || !mCheckedSet) { + mChecked = checked + mCheckedSet = true + persistBoolean(checked) + if (changed) { + notifyDependencyChange(shouldDisableDependents()) + notifyChanged() + } + } + } + + /** + * Returns the checked state. + * + * @return The checked state. + */ + fun isChecked(): Boolean { + return mChecked + } + + override fun isEnabled(): Boolean { + return true + } + + override fun shouldDisableDependents(): Boolean { + return false + } + + override fun onGetDefaultValue(a: TypedArray, index: Int): Any { + return a.getBoolean(index, false) + } + + override fun onSetInitialValue(restoreValue: Boolean, defaultValue: Any?) { + setChecked(if (restoreValue) + getPersistedBoolean(mChecked) + else + defaultValue as Boolean) + } + + companion object { + private val NATIVE_SWITCH_CAPABLE = Build.VERSION.SDK_INT >= ICE_CREAM_SANDWICH + } + +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_account_circle_black_24dp.xml b/app/src/main/res/drawable/ic_account_circle_black_24dp.xml new file mode 100644 index 000000000..76785806d --- /dev/null +++ b/app/src/main/res/drawable/ic_account_circle_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/pref_item_source.xml b/app/src/main/res/layout/pref_item_source.xml new file mode 100644 index 000000000..27ff9b02e --- /dev/null +++ b/app/src/main/res/layout/pref_item_source.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + \ 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 b5e7c1e30..ab4563c2f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -153,11 +153,6 @@ Fourth to last chapter Fifth to last chapter - - Languages - Select the languages to show sources from - Accounts - Services @@ -205,6 +200,7 @@ This source requires you to log in Select a source + Please enable at least one valid source Info diff --git a/app/src/main/res/xml/pref_sources.xml b/app/src/main/res/xml/pref_sources.xml index 31b2d99b7..aa0c38217 100644 --- a/app/src/main/res/xml/pref_sources.xml +++ b/app/src/main/res/xml/pref_sources.xml @@ -6,15 +6,8 @@ android:persistent="false" android:title="@string/pref_category_sources"> - - - + + From 8be67a4431aeb2d73de6f9354f9f319b48ab2473 Mon Sep 17 00:00:00 2001 From: Bram van de Kerkhof Date: Wed, 21 Sep 2016 21:26:08 +0200 Subject: [PATCH 43/79] Custom color filter for reader (#434) * [WIP] Custom color filter for reader * Improvements * temp image to prevent build error * Shift all the bits * Some improvements. Removed DiscreteSeekBar * Improvements * API 16 + fixes * Reduced lag. Fixed brightness value being reset to 0 * Small fixes --- app/build.gradle | 1 - .../data/preference/PreferenceKeys.kt | 4 + .../data/preference/PreferencesHelper.kt | 4 + .../tachiyomi/ui/reader/ReaderActivity.kt | 31 +- .../ui/reader/ReaderCustomFilterDialog.kt | 329 ++++++++++++++++++ .../ui/reader/ReaderSettingsDialog.kt | 19 - .../tachiyomi/widget/NegativeSeekBar.kt | 61 ++++ .../tachiyomi/widget/SimpleSeekBarListener.kt | 10 +- app/src/main/res/drawable/filter_mock.png | Bin 0 -> 342126 bytes .../drawable/ic_brightness_4_white_24dp.xml | 9 + .../drawable/ic_brightness_5_black_24dp.xml | 9 + app/src/main/res/layout/activity_reader.xml | 6 + .../layout/dialog_reader_custom_filter.xml | 257 ++++++++++++++ .../res/layout/dialog_reader_settings.xml | 21 +- app/src/main/res/menu/reader.xml | 9 +- app/src/main/res/values/attrs.xml | 5 + app/src/main/res/values/keys.xml | 3 + app/src/main/res/values/strings.xml | 6 + app/src/main/res/values/styles.xml | 8 + app/src/main/res/xml/pref_reader.xml | 5 - 20 files changed, 746 insertions(+), 51 deletions(-) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderCustomFilterDialog.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/widget/NegativeSeekBar.kt create mode 100644 app/src/main/res/drawable/filter_mock.png create mode 100644 app/src/main/res/drawable/ic_brightness_4_white_24dp.xml create mode 100644 app/src/main/res/drawable/ic_brightness_5_black_24dp.xml create mode 100644 app/src/main/res/layout/dialog_reader_custom_filter.xml diff --git a/app/build.gradle b/app/build.gradle index 5c9fd56d1..cca143523 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -165,7 +165,6 @@ dependencies { compile 'com.afollestad.material-dialogs:core:0.8.6.2' compile 'net.xpece.android:support-preference:0.8.1' compile 'me.zhanghai.android.systemuihelper:library:1.0.0' - compile 'org.adw.library:discrete-seekbar:1.0.1' compile 'de.hdodenhof:circleimageview:2.1.0' // Tests diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt index 99432b13e..76e7932b2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt @@ -26,6 +26,10 @@ class PreferenceKeys(context: Context) { val customBrightnessValue = context.getString(R.string.pref_custom_brightness_value_key) + val colorFilter = context.getString(R.string.pref_color_filter_key) + + val colorFilterValue = context.getString(R.string.pref_color_filter_value_key) + val defaultViewer = context.getString(R.string.pref_default_viewer_key) val imageScaleType = context.getString(R.string.pref_image_scale_type_key) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt index 3acee36aa..a9210fbef 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt @@ -52,6 +52,10 @@ class PreferencesHelper(context: Context) { fun customBrightnessValue() = rxPrefs.getInteger(keys.customBrightnessValue, 0) + fun colorFilter() = rxPrefs.getBoolean(keys.colorFilter, false) + + fun colorFilterValue() = rxPrefs.getInteger(keys.colorFilterValue, 0) + fun defaultViewer() = prefs.getInt(keys.defaultViewer, 1) fun imageScaleType() = rxPrefs.getInteger(keys.imageScaleType, 1) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt index c148383a6..0b935c873 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt @@ -38,10 +38,12 @@ import me.zhanghai.android.systemuihelper.SystemUiHelper import me.zhanghai.android.systemuihelper.SystemUiHelper.* import nucleus.factory.RequiresPresenter import rx.Subscription +import rx.android.schedulers.AndroidSchedulers import rx.subscriptions.CompositeSubscription import timber.log.Timber import uy.kohesive.injekt.injectLazy import java.text.DecimalFormat +import java.util.concurrent.TimeUnit @RequiresPresenter(ReaderPresenter::class) class ReaderActivity : BaseRxActivity() { @@ -70,6 +72,8 @@ class ReaderActivity : BaseRxActivity() { private var customBrightnessSubscription: Subscription? = null + private var customFilterColorSubscription: Subscription? = null + var readerTheme: Int = 0 private set @@ -140,6 +144,7 @@ class ReaderActivity : BaseRxActivity() { override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { R.id.action_settings -> ReaderSettingsDialog().show(supportFragmentManager, "settings") + R.id.action_custom_filter -> ReaderCustomFilterDialog().show(supportFragmentManager, "filter") else -> return super.onOptionsItemSelected(item) } return true @@ -354,9 +359,9 @@ class ReaderActivity : BaseRxActivity() { reader_menu_bottom.setOnTouchListener { v, event -> true } page_seekbar.setOnSeekBarChangeListener(object : SimpleSeekBarListener() { - override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { + override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) { if (fromUser) { - gotoPageInCurrentChapter(progress) + gotoPageInCurrentChapter(value) } } }) @@ -378,6 +383,9 @@ class ReaderActivity : BaseRxActivity() { subscriptions += preferences.customBrightness().asObservable() .subscribe { setCustomBrightness(it) } + subscriptions += preferences.colorFilter().asObservable() + .subscribe { setColorFilter(it) } + subscriptions += preferences.readerTheme().asObservable() .distinctUntilChanged() .subscribe { applyTheme(it) } @@ -424,6 +432,7 @@ class ReaderActivity : BaseRxActivity() { private fun setCustomBrightness(enabled: Boolean) { if (enabled) { customBrightnessSubscription = preferences.customBrightnessValue().asObservable() + .sample(100, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread()) .subscribe { setCustomBrightnessValue(it) } subscriptions.add(customBrightnessSubscription) @@ -433,6 +442,19 @@ class ReaderActivity : BaseRxActivity() { } } + private fun setColorFilter(enabled: Boolean) { + if (enabled) { + customFilterColorSubscription = preferences.colorFilterValue().asObservable() + .sample(100, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread()) + .subscribe { setColorFilterValue(it) } + + subscriptions.add(customFilterColorSubscription) + } else { + customFilterColorSubscription?.let { subscriptions.remove(it) } + color_overlay.visibility = View.GONE + } + } + /** * Sets the brightness of the screen. Range is [-75, 100]. * From -75 to -1 a semi-transparent black view is shown at the top with the minimum brightness. @@ -459,6 +481,11 @@ class ReaderActivity : BaseRxActivity() { } } + private fun setColorFilterValue(value: Int) { + color_overlay.visibility = View.VISIBLE + color_overlay.setBackgroundColor(value) + } + private fun applyTheme(theme: Int) { readerTheme = theme val rootView = window.decorView.rootView diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderCustomFilterDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderCustomFilterDialog.kt new file mode 100644 index 000000000..dc820106e --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderCustomFilterDialog.kt @@ -0,0 +1,329 @@ +package eu.kanade.tachiyomi.ui.reader + +import android.app.Dialog +import android.graphics.Color +import android.os.Bundle +import android.support.annotation.ColorInt +import android.support.v4.app.DialogFragment +import android.view.View +import android.widget.SeekBar +import com.afollestad.materialdialogs.MaterialDialog +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.preference.PreferencesHelper +import eu.kanade.tachiyomi.data.preference.getOrDefault +import eu.kanade.tachiyomi.util.plusAssign +import eu.kanade.tachiyomi.widget.SimpleSeekBarListener +import kotlinx.android.synthetic.main.dialog_reader_custom_filter.view.* +import rx.Subscription +import rx.android.schedulers.AndroidSchedulers +import rx.subscriptions.CompositeSubscription +import uy.kohesive.injekt.injectLazy +import java.util.concurrent.TimeUnit + +/** + * Custom dialog which can be used to set overlay value's + */ +class ReaderCustomFilterDialog : DialogFragment() { + + companion object { + /** Integer mask of alpha value **/ + private const val ALPHA_MASK: Long = 0xFF000000 + + /** Integer mask of red value **/ + private const val RED_MASK: Long = 0x00FF0000 + + /** Integer mask of green value **/ + private const val GREEN_MASK: Long = 0x0000FF00 + + /** Integer mask of blue value **/ + private const val BLUE_MASK: Long = 0x000000FF + } + + /** + * Provides operations to manage preferences + */ + private val preferences by injectLazy() + + /** + * Subscription used for filter overlay + */ + private lateinit var subscriptions: CompositeSubscription + + /** + * Subscription used for custom brightness overlay + */ + private var customBrightnessSubscription: Subscription? = null + + /** + * Subscription used for color filter overlay + */ + private var customFilterColorSubscription: Subscription? = null + + /** + * This method will be called after onCreate(Bundle) + * @param savedState The last saved instance state of the Fragment. + */ + override fun onCreateDialog(savedState: Bundle?): Dialog { + val dialog = MaterialDialog.Builder(activity) + .customView(R.layout.dialog_reader_custom_filter, false) + .positiveText(android.R.string.ok) + .build() + + subscriptions = CompositeSubscription() + onViewCreated(dialog.view, savedState) + + return dialog + } + + /** + * Called immediately after onCreateView() + * @param view The View returned by onCreateDialog. + * @param savedInstanceState If non-null, this fragment is being re-constructed + */ + override fun onViewCreated(view: View, savedInstanceState: Bundle?) = with(view) { + // Initialize subscriptions. + subscriptions += preferences.colorFilter().asObservable() + .subscribe { setColorFilter(it, view) } + + subscriptions += preferences.customBrightness().asObservable() + .subscribe { setCustomBrightness(it, view) } + + // Get color and update values + val color = preferences.colorFilterValue().getOrDefault() + val brightness = preferences.customBrightnessValue().getOrDefault() + + val argb = setValues(color, view) + + // Set brightness value + txt_brightness_seekbar_value.text = brightness.toString() + + // Initialize seekBar progress + seekbar_color_filter_alpha.progress = argb[0] + seekbar_color_filter_red.progress = argb[1] + seekbar_color_filter_green.progress = argb[2] + seekbar_color_filter_blue.progress = argb[3] + + // Set listeners + switch_color_filter.isChecked = preferences.colorFilter().getOrDefault() + switch_color_filter.setOnCheckedChangeListener { v, isChecked -> + preferences.colorFilter().set(isChecked) + } + + custom_brightness.isChecked = preferences.customBrightness().getOrDefault() + custom_brightness.setOnCheckedChangeListener { v, isChecked -> + preferences.customBrightness().set(isChecked) + } + + seekbar_color_filter_alpha.setOnSeekBarChangeListener(object : SimpleSeekBarListener() { + override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) { + if (fromUser) { + setColorValue(value, ALPHA_MASK, 24) + } + } + }) + + seekbar_color_filter_red.setOnSeekBarChangeListener(object : SimpleSeekBarListener() { + override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) { + if (fromUser) { + setColorValue(value, RED_MASK, 16) + } + } + }) + + seekbar_color_filter_green.setOnSeekBarChangeListener(object : SimpleSeekBarListener() { + override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) { + if (fromUser) { + setColorValue(value, GREEN_MASK, 8) + } + } + }) + + seekbar_color_filter_blue.setOnSeekBarChangeListener(object : SimpleSeekBarListener() { + override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) { + if (fromUser) { + setColorValue(value, BLUE_MASK, 0) + } + } + }) + brightness_seekbar.progress = preferences.customBrightnessValue().getOrDefault() + brightness_seekbar.setOnSeekBarChangeListener(object : SimpleSeekBarListener() { + override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) { + if (fromUser) { + preferences.customBrightnessValue().set(value) + } + } + }) + + } + + /** + * Set enabled status of seekBars belonging to color filter + * @param enabled determines if seekBar gets enabled + * @param view view of the dialog + */ + private fun setColorFilterSeekBar(enabled: Boolean, view: View) = with(view) { + seekbar_color_filter_red.isEnabled = enabled + seekbar_color_filter_green.isEnabled = enabled + seekbar_color_filter_blue.isEnabled = enabled + seekbar_color_filter_alpha.isEnabled = enabled + } + + /** + * Set enabled status of seekBars belonging to custom brightness + * @param enabled value which determines if seekBar gets enabled + * @param view view of the dialog + */ + private fun setCustomBrightnessSeekBar(enabled: Boolean, view: View) = with(view) { + brightness_seekbar.isEnabled = enabled + } + + /** + * Set the text value's of color filter + * @param color integer containing color information + * @param view view of the dialog + */ + fun setValues(color: Int, view: View): Array { + val alpha = getAlphaFromColor(color) + val red = getRedFromColor(color) + val green = getGreenFromColor(color) + val blue = getBlueFromColor(color) + + //Initialize values + with(view) { + txt_color_filter_alpha_value.text = alpha.toString() + + txt_color_filter_red_value.text = red.toString() + + txt_color_filter_green_value.text = green.toString() + + txt_color_filter_blue_value.text = blue.toString() + } + return arrayOf(alpha, red, green, blue) + } + + /** + * Manages the custom brightness value subscription + * @param enabled determines if the subscription get (un)subscribed + * @param view view of the dialog + */ + private fun setCustomBrightness(enabled: Boolean, view: View) { + if (enabled) { + customBrightnessSubscription = preferences.customBrightnessValue().asObservable() + .sample(100, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread()) + .subscribe { setCustomBrightnessValue(it, view) } + + subscriptions.add(customBrightnessSubscription) + } else { + customBrightnessSubscription?.let { subscriptions.remove(it) } + setCustomBrightnessValue(0, view, true) + } + setCustomBrightnessSeekBar(enabled, view) + } + + /** + * Sets the brightness of the screen. Range is [-75, 100]. + * From -75 to -1 a semi-transparent black view is shown at the top with the minimum brightness. + * From 1 to 100 it sets that value as brightness. + * 0 sets system brightness and hides the overlay. + */ + private fun setCustomBrightnessValue(value: Int, view: View, isDisabled: Boolean = false) = with(view) { + // Set black overlay visibility. + if (value < 0) { + brightness_overlay.visibility = View.VISIBLE + val alpha = (Math.abs(value) * 2.56).toInt() + brightness_overlay.setBackgroundColor(Color.argb(alpha, 0, 0, 0)) + } else { + brightness_overlay.visibility = View.GONE + } + + if (!isDisabled) + txt_brightness_seekbar_value.text = value.toString() + } + + /** + * Manages the color filter value subscription + * @param enabled determines if the subscription get (un)subscribed + * @param view view of the dialog + */ + private fun setColorFilter(enabled: Boolean, view: View) { + if (enabled) { + customFilterColorSubscription = preferences.colorFilterValue().asObservable() + .sample(100, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread()) + .subscribe { setColorFilterValue(it, view) } + + subscriptions.add(customFilterColorSubscription) + } else { + customFilterColorSubscription?.let { subscriptions.remove(it) } + view.color_overlay.visibility = View.GONE + } + setColorFilterSeekBar(enabled, view) + } + + /** + * Sets the color filter overlay of the screen. Determined by HEX of integer + * @param color hex of color. + * @param view view of the dialog + */ + private fun setColorFilterValue(@ColorInt color: Int, view: View) = with(view) { + color_overlay.visibility = View.VISIBLE + color_overlay.setBackgroundColor(color) + setValues(color, view) + } + + /** + * Updates the color value in preference + * @param color value of color range [0,255] + * @param mask contains hex mask of chosen color + * @param bitShift amounts of bits that gets shifted to receive value + */ + fun setColorValue(color: Int, mask: Long, bitShift: Int) { + val currentColor = preferences.colorFilterValue().getOrDefault() + val updatedColor = (color shl bitShift) or (currentColor and mask.inv().toInt()) + preferences.colorFilterValue().set(updatedColor) + } + + /** + * Returns the alpha value from the Color Hex + * @param color color hex as int + * @return alpha of color + */ + fun getAlphaFromColor(color: Int): Int { + return color shr 24 and 0xFF + } + + /** + * Returns the red value from the Color Hex + * @param color color hex as int + * @return red of color + */ + fun getRedFromColor(color: Int): Int { + return color shr 16 and 0xFF + } + + /** + * Returns the green value from the Color Hex + * @param color color hex as int + * @return green of color + */ + fun getGreenFromColor(color: Int): Int { + return color shr 8 and 0xFF + } + + /** + * Returns the blue value from the Color Hex + * @param color color hex as int + * @return blue of color + */ + fun getBlueFromColor(color: Int): Int { + return color and 0xFF + } + + /** + * Called when dialog is dismissed + */ + override fun onDestroyView() { + subscriptions.unsubscribe() + super.onDestroyView() + } + +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderSettingsDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderSettingsDialog.kt index 75f78806b..56659e637 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderSettingsDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderSettingsDialog.kt @@ -11,7 +11,6 @@ import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.util.plusAssign import eu.kanade.tachiyomi.widget.IgnoreFirstSpinnerListener import kotlinx.android.synthetic.main.dialog_reader_settings.view.* -import org.adw.library.widgets.discreteseekbar.DiscreteSeekBar import rx.Observable import rx.android.schedulers.AndroidSchedulers import rx.subscriptions.CompositeSubscription @@ -84,24 +83,6 @@ class ReaderSettingsDialog : DialogFragment() { fullscreen.setOnCheckedChangeListener { v, isChecked -> preferences.fullscreen().set(isChecked) } - - custom_brightness.isChecked = preferences.customBrightness().getOrDefault() - custom_brightness.setOnCheckedChangeListener { v, isChecked -> - preferences.customBrightness().set(isChecked) - } - - brightness_seekbar.progress = preferences.customBrightnessValue().getOrDefault() - brightness_seekbar.setOnProgressChangeListener(object : DiscreteSeekBar.OnProgressChangeListener { - override fun onProgressChanged(seekBar: DiscreteSeekBar, value: Int, fromUser: Boolean) { - if (fromUser) { - preferences.customBrightnessValue().set(value) - } - } - - override fun onStartTrackingTouch(seekBar: DiscreteSeekBar) {} - - override fun onStopTrackingTouch(seekBar: DiscreteSeekBar) {} - }) } override fun onDestroyView() { diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/NegativeSeekBar.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/NegativeSeekBar.kt new file mode 100644 index 000000000..44c9ee150 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/NegativeSeekBar.kt @@ -0,0 +1,61 @@ +package eu.kanade.tachiyomi.widget + +import android.content.Context +import android.util.AttributeSet +import android.widget.SeekBar +import eu.kanade.tachiyomi.R + + +class NegativeSeekBar @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : + SeekBar(context, attrs) { + + private var minValue: Int = 0 + private var maxValue: Int = 0 + private var listener: OnSeekBarChangeListener? = null + + init { + val styledAttributes = context.obtainStyledAttributes( + attrs, + R.styleable.NegativeSeekBar, 0, 0) + + try { + setMinSeek(styledAttributes.getInt(R.styleable.NegativeSeekBar_min_seek, 0)) + setMaxSeek(styledAttributes.getInt(R.styleable.NegativeSeekBar_max_seek, 0)) + } finally { + styledAttributes.recycle() + } + + super.setOnSeekBarChangeListener(object : OnSeekBarChangeListener { + override fun onProgressChanged(seekBar: SeekBar?, value: Int, fromUser: Boolean) { + listener?.let { it.onProgressChanged(seekBar, minValue + value, fromUser) } + } + + override fun onStartTrackingTouch(p0: SeekBar?) { + listener?.let { it.onStartTrackingTouch(p0) } + } + + override fun onStopTrackingTouch(p0: SeekBar?) { + listener?.let { it.onStopTrackingTouch(p0) } + } + }) + } + + override fun setProgress(progress: Int) { + super.setProgress(Math.abs(minValue) + progress) + } + + fun setMinSeek(minValue: Int) { + this.minValue = minValue + max = (this.maxValue - this.minValue) + } + + fun setMaxSeek(maxValue: Int) { + this.maxValue = maxValue + max = (this.maxValue - this.minValue) + } + + override fun setOnSeekBarChangeListener(listener: OnSeekBarChangeListener?) { + this.listener = listener + } + +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/SimpleSeekBarListener.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/SimpleSeekBarListener.kt index f0599159d..77f815bd3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/SimpleSeekBarListener.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/SimpleSeekBarListener.kt @@ -1,11 +1,13 @@ package eu.kanade.tachiyomi.widget - import android.widget.SeekBar open class SimpleSeekBarListener : SeekBar.OnSeekBarChangeListener { - override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {} + override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) { + } - override fun onStartTrackingTouch(seekBar: SeekBar) {} + override fun onStartTrackingTouch(seekBar: SeekBar) { + } - override fun onStopTrackingTouch(seekBar: SeekBar) {} + override fun onStopTrackingTouch(seekBar: SeekBar) { + } } \ No newline at end of file diff --git a/app/src/main/res/drawable/filter_mock.png b/app/src/main/res/drawable/filter_mock.png new file mode 100644 index 0000000000000000000000000000000000000000..a6d6a0c00aaa09eb696b23be79e38b2937321fad GIT binary patch literal 342126 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

mb?zl#FWH$fxvCf=BWTX3-V&-Ru5|V}H ztR~M20WucjUAtD=lPHF`Vq=u7q5A0?uiM&4n2sMf5Chf&W%YPAI%^#|1~SxEf|>ut zFAA&M7S@%aqXc|Iw}EUV0`3z45Cj2y2AL==v9qsl13UW>6O(TrUhc$*8f6X-1O^DC zLmCsv#)hDhjHy4Yof6u(xpai{YW3z2&ohHw^8lwp- zw$-GWkt7O8FZ5%9@AhT1gXTE*`TOyd#QnSMv3q9KLmG;$ENjpa=;k8Ii#Fjt2Uk$N z2c3sL{41c;`As`7Y_4UXNf(4x4Jfk2@CGcc6p_g)M-q>w9~zJhwMb>!Rcve3HjFc7 z&OvNh+i=?V5HI5(arTQ!492$7ZI~+F`Fo|sVz$wr1k%e;& zZ-Q5s% zV%O9I&5%cLkof`>p~B)qhZ+{9=%#et*u*5_+2u!XSXo%G(`MRR7PPb;A1D|wILPxv z6`<=t4Qju|2U6M^)^wg#=2@ zZ^Mt74dv0qI=;SNiJ5g*mnYg68j4!rW$==qg8OCB{($KMF6Z!DbsIuJ)b(wV?E86Q zz`R3Eh~5!s5Ldtm`^CKz=)@>ity=oreH0Z0_~T6cqsGSE%F6G+H?IAnl9JgSV?Y~2 zQy6JLv+(-CgDpFER-~mt!+LbzKDq@r`IOhspKHh>Z$L;-A_%JOaJidRPZ3c*i)4&} zF*4lRq!xB;jKyx3kdReku^33&J2?fD>SDJosN?liyAB+{h=XtYcGB>|J9n@j8DlZX zwE$$u&OoC8!UV$$idul5mE`2u%~}&jxqwj-cGNIG+>JjWLUG}ufugc9I_w++fWfk? zCO?yu)CY+`gQXi%kaa7RWZ)HvwKmNZQ+UnQMfuRbR8;I_LXnR`+jM;shkZs-fZuL+ za`WyFRd$PeiFLtwLvNdHXa+sF%qEvWKzg_d~y-wb$ z{Iw@}10SU7=AuKXW>NADq1oubGQ6YMFhbxU9&Y#U?23vFFECV9O4E7!BzdR*;y0vu zDA|y#pN@I>reZkT^puc575Ef0@GeJ)JR^;sT+jWo?YR)V$X5n7HIa5*u;PW znBz0p5b$LKHj9*$sHl;dfDFbao>W1d0|%xN*VrzO?laHBJ~M-+(T-6(KjLa!tx+`GIc=;f^2=X zr_s8^punUYQyn28A;T(=)z}?pDy$>qKQ*}WDYNe}nzg3b6iup~fNq9=<)jB@rjcqL zIm8{6x3H36*x(*^L!nC-y{;MU1h_{hfaHfenp?OaJ$R`zg6_6<5z~2gxw}w*VhnVy z3%j;bZPrtg(_5HIq?3KY-YH0WAjUz8s(G~bhy;)~(kbSW8dU9LZ02QZG32qzlr=_XsEqfJ{aG(sJ-zvBn zG^gJVXMC&5*WysEULk{eh1DDLSir~-^K0PJtuc*AxX4G=*zYklxN9^X4=M&W7ly03 zybqhehMQtB!P=*;PEuU~ePXP}@eePgk&wYb!Uxu{%g*6)=e#j-h!Z@VcF*b#Bu!$VnF8Dlp&)J$wec@P0s-?8^5cK8j0rfy??k|f#s z0&RSAhJ{sP|tc7!buseY6FP>-&oBp(WBaS$x4E^P=a3j7xw1Xs=OAn1^|AhKv_ZtmY-VO~cC zaIhJuVJAH%EYPx9 zgof1(Zc#bQ;YTWBj0qUHC=45@_@EcxBClvfNmeJ6wxBTZ#P)g@N+-+>G;+Aq{vcFY zfe+51@us43b8~|XK;PH*o^o2xExVZe_ZOjyg2)-ex|^rI`=HgrwO~JNrLw?=ASCBo zH*XR%7pmzcjp4h;-(iP7dRIU!Nbo8^^|M7vnAMO&8$3#lE!_a>OhQL|KfaP^9WkufBf1>VUPd6h9N<<}eoA58IEhEg0ZmhZlewb81YNU{nm{(epGm zuHJa~9&?x^TcowK^ZAvgwzj+sp=3lrl?(I=lmgBWhhJ-w!`Y#{hmH?}4h(=tAHl_; z;VEKO@(!QtuB~0sNID^~cp{&1!;UN4(O2Rqb#4gDaTVxBfE*t_Bp;Ds48VlE36N?p z#Bhg`F%yFWK@;5odNDHcAm4iH`-c3`k|?fx0%TA-VC!MZadu|2Ze;6utEiCK4Xo1qjmo&l!SR%RJ zKGeo*f$hDB*@+kBAXldVDj;MG?6t!_*W%)1kR7?YPM#)Rfhq(>bEGASnVz1Wm9-bh z`sh&w>@5UbY)Re;hFD)u&-=uQNC`GY=7TcCyHWZPh8W^urFr&UofsLB#AG}E zF(S7B3mm7uL=>fd&-@r18JpsEv^kwJ#1L+FI*4z}Kc0l^%L zRmOvNC`vg^SWFD^sWkkClmeMW-mE4F_yI}+XlOwgot$;V8$23JagvW{DFD(e@1NhPh+%;mk^Ez#7Hp^RPW8 zxgZ!ovhQyka+QLX4ox*y8J5;H27Mo*;;u(e@N;)fc zmi>4^4CW*v5C^XI!`z!K^6kJtJ9Z1SwVifbX&s9ZFFN9;rZZK<%|`&kAgdIGT@Qe1 zj%Gen$Hx>dy^;3YPHY12@$-M=zBqiH;)a;pkUB%C$7H|KtvdQpRY=~dmKMKaXiiS1 zgx!Fke*p#{X{4at$bt=TEG$H!651umQIWWwVn(1}0JahM7mIV;%j-2#TVkPBkuBL?G*X%3gL1~S*()SS^!cE>lYp#j;&w% z7+V8ky_o{>h)oQRjzLXfkB61NhY73~vLdj~aa~9ah=P+E|n(?`E5DPGAj6I74F){L>$JQlS2JGv!ZjL9ta2sBow|e0iixcR| zj5~K&o^avYcqs#_O0OoGSU3&t7$}WY_VhdQg(!4!r;1BU*Dut8&K2$luRZ2*7j+L~ zTI5G4fM{H}f|!>sE(DCYK|}+#2O9K&O17%6i@UemYMeU^D*|w%4M2@BIdY@``)XEF z$2|`2fL?A>6K})S@87>mIz7MTHmq^S4htO^Lv!#T0JDQbUU>N4!-tchnNHS*?5g?k zD|mMRA_yft+}&498UnDw-;m6!C6ZM+Q5tK( zz_+zc;sIeFfF1NI@H)7CNVm1J@|n0e14OWjN+4b|fsMhyGf?y1Q2+3{if&r~ZYT9* zGeA5sgxlM}#$YqGVEiI4V;H1G6oJP8eqX&hg8B|&gG};O@Nx+W*gUMrauwV39@W+L z_Vuj`eR%OA10eyTXFtFo5Wv`rL)@bc9s#d@K{g2pJcv=Bo7+wJo^l$b+1T<1k|^M2 zIp*Nl6k&-i4@r>oU}OU}A9UebE;UR7z#&aMFMS6P<|uIzHv+a5lTU6uGS|+XhxnP$ zZ$UeWm!n|T_+VEV#-@z-GZW1)^txOo*K>0lo10aiNjvrk6B4e( z#H{#8cSRBj5Tb815R-ctsDi1P_~T_yxIyNEUJy$SxzTuM9?}Uk$Jj;*%!|nw{%__l zCni3B_AF@pJNAjAV@3xKQ3w!bB><}j1v2=qs3?rRa9X?wg@ug`#(v%1{RJ@Z^e0qD zS>RCs4)_2D@dzNmC9hqNk*v#OpRywc@6 z2RXzBw#d@x<{+f;!@GolhEPWFIw2__53r?ppAX2qjS8oHpJwiX3<{<@i6h}PM9K`` zL5)PiiN{JoQNpR8^5?=EUrTk%`I1zz0$% zXZfQZt-!>x+u(rnd{xT1?=hyPGv`V%bOo=(VS!N55c?g-IQ^O80Nl6z6 zhZSB#ed()L>?;)}PNQ6lv8~xEiAoHnnQ;>66aW`*Y{A$YkfZ)AHchF4P$Idhkwp*} z4;~RP&iFRy(p$APxSn%8{UC}nniAww8X6kN?xIPt?-U}0OM{9gsyTYPx&+i?ED0zi zU+|dyht5clMKJrX^o-_Zf&v5$6jBMM9gF5iLp)Z^tU9;(0uMGDK>$M*P)#JJcx(rC z9$;XBRtc{rgGZs;#W=P)6sw8}`aO*~wlkkFx*~N~;z0cX*2W0pMPFY9L?M*hkX%5{ zJl{Kzh4D6+En(iwd{jbud0az>3Jfbzr8Sy+N_yqe9(&3`A~e7^xrGfeFjQb@(Sys!T~q6oDVxhUJ6dF{(}Ek)DjgAP;K} zg&Ld+SQdNz)Tv+>Gq#m?-5{66t|Ew6NVNiw$zW@W?a8)xRA=eM2{&7L1$fd%^xIWI`~ z6_p$D$_dPB@P3d^$UxB~#mBRhn=C>nOnmVI-k=5E2%}i{8Yy64 zOPSASdt^YY-)ST%M zmJ8Bbw&hbi;4*v(9F3R@pb1vX09{+NKKOxYd_qFnI7xFyeLaQwrs~~jhk54}J)qMe zvEjN_1PSfn>Sqzs1Y`|tNywL(%Bk@v`f~Aah!}Cf4 zr|E*^$#b^zN*fySW(=-6aXUJCZL;?6vRyr8HIvQQE{17d?}n>n_fDGfVK+EkoIE-x zUw(Bt4EZ^C$>QB=XO+}$p4%Fse-0k{Won95*p!4e@aXJ6T9hn+vV`&;w$*NZ)2H{+*B3gJF0UA|=<4zC#M38E=s89-ZPe^9tK4e^5Q|D8 zn&VD&aJmp8`DSOdu-k`k%OW|e**0! zJ#r@z+CHQvXY%nW&Rfd^EdzHPKRTc|M&(RD`#nntX+c4`aL`7bw|hu2T~=C({YzKJ z4{s243`dAf{GW3AJmkr5hK55;8ct?i_C7fy;}NQn6ul8=2szZ&bk0kkoq5mHg49l< z%W3d~oWle~gmSXcOK$ZfYwSKv}yM z;^%Sf8R$ai$)K(o%4?gW`C0&CD3hSpH4?~k+X$et%QXrit4Ea}aD{(XcLxTbL4?n( z=*B1$RaR<2+{UbR{N-4kP)Uv$Fkqv|03z*F&9A?d^rwunu)(;p-0xyoA+Aub0nadqVD_F%_c2FO?X015^cbZ}IjN^y z?_wTUIP*I#8xNBh^Mc~%eQA2S#QAvb+I*qEscK4pt)O^w@pc;7%B`A4z++Ih7{SLOat8S+c;5Jau^>z`7 zBznkj@8Db>_b|Z*MX=jTPDXA;m$Y^BX16mvb##PMHY!RY-og?5Vhva7s8aNQhABwTmR?ka*ut`P@ z9okIpQwv+iBLMlN$r3)w!9WU|yVcckF8EK0;pS1DQE)|P;#TgaoDOmOtvH@KE8s8hsRZt?NY7>OqX3&a}_QdB9dSZx)#<9gu&9q zNCN6{_Y+wZ;6Slr)nn_p2i6AVm7PUB-x*#g?T>F!?APS83JP>aXSk}|*cDeX2%@~Z zc;-xu-Gq}X`h8gA+ax!O=xwf0TC>9xKTfMo_Q#0LWJ^x|TK* zCQhV%sE?PA*V}q?(BQ!p+12;%0Y`H5^@j#an*{Tyn~qg50`%sCCRV9xt>#aDo*$v7 z_Nvpn)70ik-lSv9PD1=_t_}Qi{5p5EO>!*k}ym3A)GeJ&sYFN;lpi*&F_s5Z8Agk&J#n&FZ zvbZO&FCcRCt}9y0-_g4sI6@sx&8lr!ndkzaI>^sa#A{@@Z} zkd5%pem53uf>7GbO#TlRdYta-C{y3nHCSL7NpkS0-obCBNQ_HJ7`67NQd&?)TQm7F zHT-~q4eGV-nx=K_%yvkL32|{yn)a_AJWsgH`I7|HgAeGv$G+$7I>Z!Z*dV#pOxrfDCn!^_g2! zQn>E@e*Pwf#hTp0ZMyN*9$=7}GqW-7K}YphJJdYWe*Nl~jJ537j$@C#g9GW%Id_hw z4i}os&kK?J4eI^i;lp!yO`2=T$;ukt{f22Iue|3OKl6E(Iv^>~m1qLwAJuPCPILEj z=id;EAYB*lm5SzlXsilJY6s)O;~0lSxUZk@`wTID7q`(BS#-;2Bu&^M?b=oOei@ZB z!}r@Ce=z+Z@uy}OpNj59WkFy8-ehP}`djMjPXW?XxWDHOqssH8W}xY%QWrNyLZwV z=XULyhM5vQA(m{A>pp({isn(PZ(kH3%j-*JAnl|_5=>!tLaRiX7rplV$H$e{213CeNr85 z#>L1>y95U#kbB5^X^I9zk=fs-7ct-5K3mq~=M#MPnV;va@S(cJvlu?O{uu}hXtZ3`6=qQc4-Q52 z)CVEfxjsopx~)A3rk%UaRix*%{njgE(2?Q-=Y z0iwG>H?(^9zJmV{ybP+6sPj?YW$&U3`}rsvZS$Kv`H+tpfJ2l+u8;gM9vpDf9w0q6 zHHlvfX%@txxeGQwXnI8z$5i_|{U}U&Xk0b-?|Y6N8)T`&@g~n;rRp-x(9*Jr)|~{z zc>(BPZf~@W(gNF#;*UEjf(@M{L%)Cc!1KH)U^E#dPEaGXbB7A|mIOLRcFi1^QF^}x z(2_d|GnKx&wW&$Idw1#`8tk4~vzX$*pM+Ko{Ws9?pJ&ew9X`Bc=T4q!YvR#k$J(3f zyKQl&E~HB1=i^38Sj<0vepW&7Xc}e!rw|Jhjmp|u^>bt7|Dhf(oVmo)Qz82u10CE} zp`b9MgC&0CNPmzikbaS* zUt`Qk^78n}$$+cC9atG2F4?b|lJxa!{p1BYMGJnKZW-{tNHz86>E+bNW8Bf@*=T!w=hFO1y%C<}-7tn5Xf?C{{aGiwBKV{4*o< zG{zS+*0l%Tw3G5Rjf;`;8sw+GFz&O#)#8+uBoZ&vVYaqY zu#~zed49;((tO*B?m*NDwC?W=x%j1w8%JTb2yOr~r46egM$+y?nhk6e`|tAlNoE1R zhj0Mcr?C+rF?i0LxEjruXKM?`9-Tz^1PLPOV?Il_XWb2xbhJ1*5me~G8Io(QhYfqX ztgxvBgIPU?RVO@5Vtl;KdSG7BG>Zxat-jhMw_%+&{n3-ah5`m5;}btbkMBoL==3%| zplc1*jw2ydOx>tsH@^OAR8|~oavv%$`gqD5SQOxvhPLspeQ%HE!!RdP3ju^@SoVh) zPWB2jY-(u%%gzxqKBeq8{!T&0riJ%FY(TW=pND8|VjKiuiJ=1WwdoW2^RytP-xW{# zqId_#f^t>6Kx0;(!4fKR7&F}#bvm7LYuy(?`!x;c8E_&HJvuk;8t4PFQptj4leRNQ%3j;7k(yetG&xrSVhP)O{@I)gT_TiO$}a}3@!Zn zl{mkkHpWC{@W9h!QuMgb-RGR9E)ag3{fY+Snf3GAD8Dh3{6MLg*UstRQ}rIM)2=)7 zz|wD&mqQoWGirUWg40kIiNieG2!@j6UhE?1PZ_uK8DiWog3{6?^Oqb@=nZid1`2Ht zzm3vT-jJ(GCJTHI-3cS2NQe=z7wC-0Z9*P|ThH8WR&o>a)d!zeB(*aQRv)L4ylE52 zAQU3%cI1Y7J$sHm@_Y_NtpId;y*opPp`?fjQslZ`Nk`9lk@Bhv!{9pMObX&;;TFPNeKV ziXjH>KtXfnm~_?9U-tLPw8Z_(PnXNPlp+bJKu8K)ll{cgZAFons9`N#an<$jS9LgruFDtYRyYE*`S9?fspo*0>0g zA5`B?RCXk%b-!qNM<07D6tknpDjjHs8GnAd@=d$V?sOZB3qPzORbNYB% zZ}ffZwX%6X$sPC-Q{pBSz{-zvspoIZKS#MH`3RFC=rXQvr)ul#pLV{`yejaP=x2?p zUKl0`?OmM?KXt*R#%Vu~$9bF^WMgej7VNRS_xs}l4^uvBLBD7bOpxOK;;Cx2%qI`z z!vTgg{{ykgoyP?zQyC~&&nON+PyO(#5|I~uJ09g~5<&4ULsx!AU;Ubye_aem4x612 z^9S5eT?&*7{hNL=t>hZfX=zNo(4So@vSRrxUJkg@q4Vy#>2g05qj<;CH01at^g*?O+QF2B-oB z0XZS65VpG@dBC+XbvNS9r$b8T!X;oPj6a15ecJm3W;}37@ZWs{XK>7xUK!ch%+&}8 z>%kE4h~pOrm>IPz&O;^HGkG2vWkrL}idyr50}+>j^sR-n z2JdCmDjIuma9pQ|W)yrr0Y+C{!h0I2DSz=U``p!kb+Pzii0tRUZ65DnP&|Eli7J5* z&gU)N{*6fpSr0AYKX_}{j|Fu72Eai8V#1><-%fDE^nE5gSq3^aR~8YLC^#)`Y)1dp zN|lmxQ$eW@HRR1XR-oLDjMSMIr)Cm#&cH;gD;5t9&y`&yjGMdw=Kszrs<8oP zW)Z(za6_Ri`S-_Ovm+d~JP#pz0rVhOOWjVq z&|Al7&Ld`y+?2o$mnpdBv9Cg=sqD_3Z~S9k9;YYpaVAgx z7gZG9Grbmb&V0WUoI#QwsflMstiXf6uXMi^8%sukJcwD4a_+(fSs58_K=)av@;h(} z>ewsFc3(b@?$5eh)?p9qQb1g%b!}>TdP))_!I5dtc(g)QL_G`%2|Hs-tN9BS{3fu$ zRfWqoa^y9F3*v~VTS~H&crk89utg69q{9`rK1TjSO2womuNNQZ6$uYvN}qTLA4wUq z`%QhOFx>{Vo1GyzRO1D|U#oSSA6(5s+vc2U4CB!u+8ujCHxx1ho0e%vO&V&AwKkoHI*u5O1`Bov zCMZ7eD`4fjE*4+8-qh&UAwEj-$c-upV(LOF!&9|*-dXsn?7YDa>^#;JO$T6};flyy zCnj2eZL+hoff3hrw1vmJ6i{IWKB2;*ZEX7W3)_v?%`0*K2G8Zz;w9NrUw`Z7)s@@( zQGvYs@tckXGpS2dLer)_V?Y2-d*o*@7{j^wIh?f-@rN9O^@kP3yl~)OP`TzRzTrk z=)|v(mLtcHe|h&VE^vsUVLkkfs5e8svA<>HB_F+sKG)2NU ztDFXAtYO>B7RgeJt5f1#qPtXUVtec87)+;*>4^(NE`Qju@9E2zdv&aAod@V3`pZC~ zi&g>&PfPEouYy}^?#kF$=Ghd#UV`Pz5z&fBoNO3o!63!d1$G695|krxMUU2iyF2iW z!QQ2N?RxRg`9)W?#lpd*XysDGGzZL>v1y(nz~#vRIt~f3KjI5o7vdRz7kzyG+BjRK zNp@5~Jv<8@Dd~^W+qee`E8f%Ou-$iLO9+_i8Atxx9;NhR1LF&(CVcReH2_H>c@AYb zKMiRp5LKJg44b0-J5;Tz16ybsIyyVKThw#lnPU&s!pd*?{AduD2h>7I$I<{49>5zS zDHH3g;Y<78+DdN*$C?a|5s_f6Tvde&I(gq z7Bx_SXq)>rL5CJP6vybjs+QtlOC1JwXIVwVaiykU9~EsjRQgqLD`{k`*;%WqNxJZ5A!r1RGHa}%-Y`LxmagVw59=`Dsrs$% z_8GXtW~$MN01dCCpyAnKI14YMnfJbGRf7MPdmqc@0>Huk0{FrycEN&O`}V>3$9xWr z4t?-6^T#|wR~Hwc-z`TshRO!~-lrHRKJvOOJ=^mTM3Ak$Y(7IQgDN%i+8P@(6QG=& zgKyVxKvsOYvDC*mwzu9KR|wcUu3zf=BAlvP@1Oi{?){DBhx0Q?5V*doK;!~p0xaM^ z-;r$%sIM|u%dra&2@%#6p;uP!wj}+wuM^Hj0%|zLi$IbK^Lm_#MzfR8P1lK%ODCuW z3V#ENBfMoiv6nW7K8M~Y8Lx1%N|DN7j2?`GR9!{gLSV7TuP8pw7oxe1spki z_y(zw0vEH>x^i4Q{jd-E=Pv4c+U*zuT_UA2#W`Mk5_SLTEawmADBloE+Kcb%9gw|b2+>$cu-i+i^N za+rw~lojnS(!nldgNnSIsnZS}2_2n>K|5k{Wp9oT^J|(~Ni z3hVzDa7P+x{p%zXG-qFSHqJ?~BWbL{ju%fFI=@aw$jZ>+4jwtepbaNw-tM|-)sZWH zh&)jO0hlp3s0ma#Vj45)R#jbuOjX349h~64XFXbn?xdbvhKP-uv)}#SqGO6(XW9+^ zoq-iKiIU9#Ghg$1R##(m63|%Nw5Au8x=d43vd9 zX~veY@`FVN0el>q9J~bmeSD(s_vteFKJmw@7u`1-&@nioaPG)5!l0ovU3EkOnf&gJ_M4)$w>yH)N33CLmRx?^d?9SaZULl#Xbi$AY3hrIEx=Ax+i5 zJ0V*8!%CSvIRkz%t0R^Eb(VpYN#aacgVr1u63w66nW!kw+vxU&xGuOfbpH8U&3}XM z$>>#$-@XZCP(B3)Dmpqk9bJDKfr3Qs@f*A)oS43gzdI)CQyfz(@-1FsUc%R|y8X{T z4y?2tK3x0XNVgHUiY*Il?wa4K_qi84DZ3<4T3VVU`~qP4PYg>BPoCrwoH%+k+Sm&! zA-({pKE{o^0~A5y#3D1Ono4#ADqR#zq64=KBquT^ZD&A9(iMm;uXFE;zu|oGinKux zK)(TvmrCUx%?mspXo3RIo@SH57P*xlaF6lmxN&0dbL~o6Wy|v8;h|28t>34Q_d=yqt^-ua&-Y*0nV~ zA1_Jzz(1kq$W^3CwENfjWQv`E}Ajk86H9sUb)T>=}Q<^7Qcl?9@)`6hTd-7I>!r7 z1Q{I9;r)B(s(-1R0BTSiyX>Xo5FlXK#Q7q=b+jtlByp+bX^O7 zhFLa(?RNkg&PxUOn)pN!-a{&fV89&3eAeY27o*cRi43?HWLVBOttJdH*AMr(N3f>} zpZX4~l%6<|Wzg|ubwyGe^GxDB4mtgOxajj2{V9dw?(1dUl+I6Qz`jPWH^<{)Sic07 zKA-FBxl+dsi-;~EtuYw2061lBh$CQ8vQZlLQhq?Mi@2foi~1Qum}^-5)XxPf1h=KQ zMOAcRY+dAM*`|--@QPoZ@ooodGmY*>`Pyu8 zN0PFZ(ieYo=fB(Ef7I?enV5K!a}U<$`+?ctBiBh00kM(qO{}&oPgX;i6Y?Z)Upkg(0ro>nFQ4^fUAI-}vE?Ea+2V3jS(oTo+LCOLA(XnM- zB}Oh+PDMrtT{QBYCNY0oogeeag{G?0N5TO^CgE`hD!F=n{SK(HF01bpV_OulVSh6V zonKqV12j{v;1(&mgyjPb?z&N8T3;QJ^*#JLZyErO(+^;9(RH$)cxsQcU5m91Z{6r{ zR=h?h_CLGlw;$*~=-=fYjGu{Kc09E--+xhhIkk4L?SZfd*g8US_8mNkO)h0GUv~0@ zF9m|_z+-K2mM$3JB`dl^w4H%9429f>e-^*EG}OQ#5Y=_y{+IAMcJz7b0pY+sMNbzD z!6j|_N!fFX&z*ig0zu_;y1w6)ks-Vl0N&S;;yxEIvI2`Z!b@6|vXxE*e+}hHJ306z zze8v&vQ7%vbmur!dO>FJs!3+f+6`a7(n4GI7-V5_^J+zYnU?6}l(d|Au;n@GOf-oH z`TG*Aa01du!I2R7=oP^fW8R%O7ByktK+VBD6-+tlTD7!fB_!8-1uhz(CJ%lPxIyye zUTj_v*@H{1TsivFgdscWhLtf+wO-?Aiz_`FYBst_X@3RnY3pEy#b#%ev)IQ{FlORI z3&i2|t_b*w0U`}vetr#-$_;uAz3d^aK*q8D>o_?`QIU3znu&TD+}nK6pxClACr>WM zQ;K#Rm@dffBklI#&pEcjkA8XBI<@Nc#Ii_TrPAvvbv;9BNFbw!*&=)aIoiCA)mgyU z_&*{=RXoBLmOzF0WwcA^&Z4F=C8bX%UWM$|=-YQM)P&SjiL_17+(Il<^--dO8ojy{ zet@q{JI!(B_CsOdkNHgOm})se`L^$j8PMdO5r>S+x(&hZ*8Ru>{M>79D0S5)eW6*; zO^sDk8~KHaT#lSXtDJ<}p$QzP$y28;FPzDtgQ?AwZoln+2GGnI5~M9HEbcvj-phVP zo_W+Ugq74^i)#0eCN+v3oCWDKgVEh~;YJ-Xd{L|6w>!Vj7>35hv}pR+_R^kU1Bon6HL)G0rI{Tef@6&f7;A2!z111t$dT?AxYpI={} zE$83SiSf<5X>H@nGf==G5vIYjXS-Nr$*_;97f*zeVb91FD_1)Ix_NMMNC={9*I#4E zgjlf{l!bXLz@*_WTLsg~>e&v%*CyfHnesQm*Sj?fW7o7we0#7@4IF_*FzMkW239)~ zJ5SCO7Z%#?|4dFQynK0KinN-3d;bR!t4aK*FJV$*7q~yAoBI&aERVnDTJ1vX>)|2a zYcNe+uDa>fat?UTX|6=M^lVEVK|a7C+ZWk|l0xu!ayis(X|I|^_)Ozgl5S$@ow$C_ zgrgW>lU-|c>G--xwqOj;h*w2pxBO15Q8&0kjgzyrju1LL^eG#Y1jH-FEWdNpmMwg5 z_z@sCeQXv^!uyBt!8xz}Dd8CmsQdIMX`6aq^L)=-AHhAGS4U2yGLPwB@>baPZP*tX z==U#QT>CFa2#v7@>G(tcM3=WR$gzeX+vEFBxE&BkY`~=$*`^WwO<(+}80M8m+vdqQLNLID&r&B!%xf z$cu5gIC^w|v2izp2rn-sbWm521g-@RV0eV)5HOi2lw~#dHfWCi$Tia2k1>a)h$ISV z51uZ4v5hBy>gqdJfYJ(YJ6aJscxtk)pWk+~%t9n8CQeddq6rQR4lWHYU?ucJtl+5@ zk=mjvnf~^+^5)jpU+sQWsm6q0h!b2On7dFN64GgCpy7prnly~L6`M2O3b099R3P72E-%F+rP;W{aeW zH$O*EsFM)F)|888r*{OTKaJJ3rr86}2c$V@0BPs(;|uJ!MN=?dRVxWNp>7Z~2+R_3 znG6YJ5+l4knH#LsHQL7LoyEa@x9(*0ux}rx;7f`#L+-d9c@Q+3J|zg3&q)L8E2cA5 zSTZWH=4tMvK;CgNQ(KmCPz`tO&HM8FI-<8ZR1s`XS1w zK7Bg>&K`j+7NrR0TVy2|gU}`EexXCbj=~$|zMe;@?F>YT@7L#V!Ni@m^-rX*_BvHU zR-EO}(#(%6Rt02(?!*%^(+u9q-V7F28@y(_nMCM>RvR|Q(CWFX`li1F+o*Bxoo!70 zRQ?2v32hleiDa)#htt!~E9mWtJi8b}=Bul|J*SQr%pX3Q4O}Fu?UKWeH5NxY7ab>hj7>b2r~lvsxa!Y3k;6nnRnM-8 z2*@0d7snrI{q@V~@9)_n1HnFaAAZl9DZ1Xg#Cy!c1T#aWj%R;=eOiyOhSb71-Z~*K}iIRW&C;`_awh3 zIGEM_dL6mXYHsp`qmaLU3uor`ZTc?5bO#^=n-NQ- zUh%I`ElU>$(HM}#fsrj4x{6LyJ>YoY1uf+MZO#Wd8nww+D#FZFw3N!-yG#Bmzjtpb za}l73l#Lr%^Ci8|8lxmUu#Fzz6~N};Ec=xMlNP7$^_wT+te*@v}j{E+}kOAFY8a{~i&B;CnW@Y|9m zO)&maA7&SIKG(1}J_OOCq@<(+EgQ%J_3mI==*4D4&(4quaq3J9WdZ1l_o0#z*hNC z5UCPkQG&+LyS%XaRgO5e$v1WYK`3{!%_&BYecFc@`r}bB2!4?0FrW!#P~f3@P52Q} zlVgiomfqD%|5j2b)BQqG<4cQ8NO=8*?aI|gK@@ZO9A>kVymNipT0c!}dR6eFRsSC7 zRK)Ow37xQ&_>!cjwh3z0s7WyM@NiR-u%TcZ?mN3$pwt(*h(+DBjNwEB3j;{a@qIV+<>*COwCd zLiS?nlvUEv{_mh;4=pX+nxu>5#|@U%TIA;g{2{2`!@?{RSPv2o8>!Q<25^LFdpLKM z5)Xek{xtM&DzA!8RQC`9*71^hdDxm#qF61%&{rpp#|@cq9>KzLE8OG`_!P$`9}k=Q zAFs0?++N-<>5SPBqtD~V6>YH1o#5+hh~=ALA zhcWx)ti;5|zF+p6#6)*qyQq?p^T?5X!h>m*KJW8b8g!74h{<%_AEja27**0#E9+R2 zS1b(;gM`O(lO}<^l9b~{A9Y5pv#UNts%5gV?GMYn-7wcWc#tZJDJ+*;vQ6l1nX#T> zD-OUR07!JakGrRGEAW=E%n6>^yJf!#FR<}2WM&&H%!$j?e$b6z zKQdhIBBCO9=9f@1a7HK@a*Y64x~Ok*#=OW6icL(E%kYGK`Gw0xeU!d$pFXxBJa!yt z$qB-oTd=JDyK(&3vA}^%%$vLVEBq0^XOR$e29R1@ol^fZlFoc;_>Y-|1%pLS49u#i z$#Mq+!JToT?VnajZsv_;zTv--E(xItx`&I3A|ct(R{?l)6!CZXy>8Ujn?IVGZejIH z3vO$Bx2g)4RhIl<2lek(UKmT#8~7T6o(Ydh>tCzsRrst1=50ivZ62(aI_iEyLZ$+{ zS6G8k71)Uec+u_rnZrlwfaLH~W`nSS)+QVMO$)z+-&oFoAzr79x8BB4W9(+7w(MWs z*M9V%9tt|&4xEY7qDM69-+zpW27L!@6%6J7s9d*IUv9f(Xt-V` ziQEC&nD^l&#kxb{C^?h}1>lFj`nplL4qQBOVl$ro&%Q!HVXle+-ouA;7B5yFEUbG% zUg=??K3o;065=%aG$PA_?@W4~)EZF4F!IT_1xaz$NRm(w@=@hSOtmdWNxF zo8FI7x2bikiO^@#5E;n*&k4I#IpIhVVt#%DybpFSt{6qb-{IlYkT7#fNy-ps8K*H64g%T<4Fwwg!*5{)gfOL}Q{m*lVg<(* zcW6yd*vt*L2M|t>QGtfA-;W|mKxF6KrLacG0(J1-?%_lO!CW~mt^=DcVk2wbVT*ua zCnX~e+(#?V%)Oz3t!n47oWjy>mJo&UFfcMIMMRx`9P%?YH~ZiYNUntoy<&y+&Z_TElLqL}AzLfF8Grb)41HK4wP7#uPZ}5OO%vuU-+rU)0nDE?J_|sT#LB zDhb4D&6DCjY2%L^uuQz*7}Cg|z6I93=GZ=sK&QR+rZH~CTgD#&hv_xa6a9dF1uOx9 zs^)M7P?#BuPuQ+aKYtAe(8RK_6lLLWq~l$t6>V7W=EfGlmY+ZQBIp!#My_d%lxXSr zu~g31O3mc_bS(K}%ClT@bB4$mnLU(Z!vnj#hj`{B#F{nK)kW>~q{WA*7-Fn4_8Dyt zUzC)cTTK#s|Km4>%&mXg)@{I%hFu7r380dgmV`B!nVTcVh&r|s6px!$Ty3bIAa^Y` z)|KiDdrq!rR50i#vTfl7;}BdNB}rRGK0+18gF1&SOx~gq#%3yUPWU)JulOo zVTTLg1VV->*tv|l+kcsC$B;2%9YM_cg!55ULE6_Ws0U6cZ~(8KL(;)PT*2dA5f>P{xR3z!OeSWUSr?0Gz{r_?;NlOy73kEtT_bY&pGINtoGTn zji)R){t^eQH?|aIs}kjj5EQ@WpS>hTi0iXg|M`hKFmt#=l-Ky`PB=<^;I`-)rI>7N zEpV5f-e&)yq$8Lo{Z7al%ymAh~%npHfs*1pSbwo|u^EX7rmZ4`sKjo86?cnlAnUZ!3foRJd(0 zo4f5UBpt%)fxjOm=TD%485igQ_5myL`}6b1xg0w){dd!}k#(Z(_-s@aas&l~$36uf|MR1)KiXD^M47GGMDAj3|O?}jw7QWbS@VIux z5SdMqS`s=w>4z6s3%gO^JYeK@Y334MBxpK1jjKv`U;4* z5Mx;yH;lrdczw5*3%mB5Js7#VyPNFS0rE?MDCu*3CQptJHd&|Uqu?Iyadc8?b*~P2 z$F+_iX|uTNt!ZWdW}R}+)V$pbCkU55?YF2 ztq#pf=@gKah_!3WD%+h#EaE;e-sT=N|C|g^a_!n4gZ4MQ5)uDp8G&Ua7)>My96w z5wBe?X6{|SMIr}joo-bl`2xt?iJVY+AX1fYgN8~PKA`doLIx$Tqm5-~b6b;}NXBS7 z90Rr1zNx*X0!1HV0>}FK>CAn|!U1MEv|2sJ5SzE}SiB&Y%DSZLWu*f0R{~{FWVan3rEDe{KjUB-+=LQa`J6r4uzBQ7GJ2p zv@+YyA3Svm|Mjb|+1Y{E)wve-Kg2)RwElwzVY=<&@h|Nph9|kt7&6U3Ue8FH!WGP| zWKwH$Ge__lvmYu)fCw_0oSYm3u7buc*G2t)F0Xl0pPRj7hrkx#P6;LA*U)z=81w}2 z%hPk9kQb91n8Cv}7I$p4C3B@wf|6E(*MSDo~)VTXR}~8JVTX-Nvr?2i&CV zxV>?X;NyT>3>`dpF={R%4r6%i0LB!*65pEXArfI~F?zJ1Q{oy|-n~nk#7-|^{le6# zaB^^sM>0@(=Z+1Y3gh7BkhK5u^;LH*!xw4c!i7Z9hmRhCfNtBnZRbw>{2sh`af0n! zH*dR7P$0iLjo zA85gG4J_8`Ib3UVz~*4^S85GZ!IcvU>3kY+U`{K{Ae_cHBSZlYjVUZ-fAnbZfB_4Z zEa4A)Z)iZ^dKwFXlTOPva$pO-B_x)yyQ`_mGRK#b0QHowZPWqfQ&Hzn(HCPP6OA=P zuRYth-v^`NEwhmjlPHy8aa;`QdkRZhqpV%K`t|Qmxo*KaHS!f)2l7bjPF%kw~4EQ2-DWnS=4>^Co zNE9S|o*@7{9RpuczTbE_s`u|hO2NoCg>1^SE;MxJ)rfG+i>`K~P7I=DK11^=W@&u=D!UDThOG`>5&m8B?AXPfNn)Ac7mCy?@22Vvk#Q!gb>7j6l3fn6M zc`zdoI-BO^aDH=yG%Rn)G^PbnJ*(^L3>1VzB7eB}fxf=J+$_DPN3kq1e-i!iM@tL$ zBaKu~LoU*ETKSm?jbL@nKXO_w0GE?pg-q(;u=M%4%4g3;pizy8cuH)gJLemF?0C*Y z1bt12F*CT%) z%j9p93WId7^f2pto%w%Nc(&~h!0KyWzDf2qksx^Y| zH}pI%|4huH)a7!#8vl$cz9v#<5ib|*U)D;hdpDo~j%zH?BssRe4m zt+die`n8yrsCq3}j>R%p`#)`s)sZ>+W#6S_@8n(O_YlO?w5(md8n(59N>YxdfahgqMwxe$ zV#~L2gg4AFZB0$%^gCqF;3Kt6UWxD&>zD2s3oFS=ymfXb^8#l~o5s!!4lm=)6Eh2! zaaMF1n0Wy{5;41<{mP|>>3}bC{RdhmY+lPyuwCTuQgqcUxlRf+-X2`LA6`BI(2AKw zei@emr&$mRmx`-Yo}4s_X$-^;0S0qgk~5iw28 z&CV~o4=#+0a{~$?l=DC6l!=OSw({DG8$jA=fgvq1j`!(_OUVWJF@sStZyt0^g|Cu8uh z=pM+cPj4bpr*je&`c9Yt_7ywH1qzgOrr~aXH#h0o!$05Vc9KN6G`y-BLwFr_zFSgK zf}cp0&CQ%IbqK!78Hf#}v9C^Q(?;Qh&0G!hR1aGAieOxEg6U@&A#6%XId$yV^y}+> zlULxu{LdPy&YRWN8ChALIYZJ`EaiFAWs+9J9;>>0X$)}TWb%ubOfkgsM9|&E^Woxa z;cdmd0yj0pA!ATkPpKd{QC;wq)&WjXE%Ui?$s~6B><$eHVK5!P7FI_C0T|p5I%vI* z!U6M$k4AsVPn7C0u_n3xQ`C031EZ2|$cO&$&YS#H1>X>sLj{Fq#ck49w+qTC9gWaT zJl#6}NbEz2LvG-0<>hgBnDC7S=R*p1(w%!kQj!NWC7J>!r^u;OcM^VDRWZS%K=kBC zj~z>W#;DvN#!l^ax7Ri%Dn>8WG=lP#;r8&W@2GY1FDGG{pr_NdZSqLp9tgeK2#T-= zD8!+Bsr$+`Rs!rGe+0jvpohWZw7SJ)`0ytrK=7l26+8!yB+xcB49A+-`j`z!pQZ)` z2znH*IKeAWRx~yg4I$18^bklXIh4E^vi!a55yNQ1Y;4Z6S6WhnVv+KJv<(i88j1N3 z>@$8sV3z2f>*kNVGa$lI%ihUDBMS@Td~QoiJD!*4fsx&MR;S`YgZQxYdLlfLyZe${ zvNJO!MlXS$!VgOv^^S! zxG@C{!kT!jY1*GZe+UdbXbKyV{u*|rz*Kh2=Y@Ohov3P4|(?-^+vf?R#OCbDEm^g|e0no@S5!|&~J=+6ZD zKckc@Y$C@Gmf0i(brc)`uKU?Ll9)IKBVTLlR`N1$1$T6SB(|6fo7kygb6Xe!Zeal) z(+7>`cpg)f!n028CX@ULGPa3{3CGE-$L)imh2j=rPawD`Q+Np5=#K{UNKQ|mguVHu zitRggU?mH^6I7M{i;~1(D5iB~<`L`G&E$p9jGqdm^Wv}1TP1Q#nX{cWVY0E&0|&@{ zr*O2|MT$cx3!kkYQwPyvN+aeJZpAE{y3Bnb`jLy}k9}myKw-~+g<~UgSAELB2A4Fq z)}ryrMQctLh&vXo8CGH+d`4`wH%INI!f~P>e{a#KgRrD=Cs6%$dL3P*-f6d&Mr4nWiAFaT&-m0FwA33)OyK~ zJknrxW*}K*-hu_)`?%m178_f}v`O6=8oi;hF-D^D;ze9!YDt=7>?^SVrl4Y$NqS%M z?2L?*EUxX@*-ATB<7F=R{=v*){0cvYhd}EC?p<5!OF&L7LP3)ch-2P^`DD+Y6}Pul z1P6_fP(Dwi9b1x`k@2meL8o_byk4C5X#Ax^k9n{^C121oh=0Q3I6Rs(HDg~%EYf5k zLA~@#T!GX*UL?Fe!s z;slyLGuBmB$GF9HE}f9qb${*8U&GawIvl;95cxk7KcVl206;wBr&3igzlW4zGK3q? zB&r=W38b5h1CIu-Eb;FpA`ilL%(GdZo&q0VB9wB2>7r^M#U#fv zfWVoft$Qlj@XUD#JbZ!%bpk;H`_mhtZZkheuE{Eoy)mr9g4&q+m*63I2;%q6`$Eyn z9cKf(bOLn|ce-u|HXE=!6}xvgoVAqsJV%r2KhNH|iEo+@TGnGCPo1u>9;OnKnwvK- zLMKGJ!+No|!*R0TzaL@BcE-7wOYn<2jtvfi5a8%hmNX2rv6+kwGwV+bKbpPbd*ko} z?JCjp5PU+yQ*P$_PoFT%oyU6sOyEZ|FA0nW@!^JIHKVDmO=UB6@?@K#Lu=`qt*o*f z{vN{clU%fUGj4W^m?|R?zd|CR3x~EPub=>-+IW5f;>rUoN$Q5Ir#3`hN1}5KNJ*S3 zacRz-w@YRpbM@7~B6<;7osy|o`_TDMsU~M%#xMbgE*1>;mVE~$B!4M1{EATkx0>PO zc-CY%im<@Z`{Ps|NUAxox-Z`$$Z8kL<>I5L0jZ98W7J(7JTQbp)*VDfMnW{BkOD?z ztPKHoE=>bI1{qfw=XG^$pbKI3E8}GP58mm52ZybAAwB2C9Yb8|el4m|k8MPxotPtPfvW5tFr ztQjOfB~S%Y6vBd_DC9qK2vYq|_+Mk@Q}*J;RR*LZNA^+FSBSR)vjND*ZUZm}K`?wG zoEBNNEJ(h{5#v2PAh`m#NluK-sqgH*RklBkQNEwK^v>e%CV;M}N_ihRDiS<6+O%gN zlpxQ9!|0d4xqH6;|F*OU`JYkW@3uB{X+;Yk4J`jrdLFZkd9V#FEE>r$)aMWy$d#}Twrd-wXIn)d5P_+bo9)Uxh%5@o}S7un;F{QCD_ zxT~eMy__xSNiql4-7z&Xl1j(~1>00%1j*BCHF89E4Qk@z`AKF8wBjULtSGQ&0KE9c zV9-Qk03#f(oS|WKQx(m~PmnB1QKqTnNOh<@j!vUpf^=l_fS{IyVSb`LkJ) zx+V|Xp%XvBoB2t^!RV9U=w97MkEWTaARNahJ=zYj7_W;HS~Ms*TV1S8m3~Bp1&@fS zOcL$wo?Jrah`&&KcHvD+SCVbOo)5pFoeCpEaw)jRj@ z$xUT-uHxf!^gX}_v_2pRJW#XG+<(JtrNifd%5g1vaba=qpqwyzdHNBOrpD?Q1Qs4U zP+BDRvdPjq1LzIHy0|K^c7!>F(;o0eLq7@0nta0zbgd{lMcdt8*;m_j$$XJsT_N35 zDnwwwNd#esLea_tfSC3sj^PD>TQxGo$AdFBGpH$+{sbSJL%{6GR|7kkCfEc5tM~7% zylr|dCjN$NH5|5!o%=6&5sg~Jz+P|K=enC^-{C!;gc)`OvMh_hM3#!N8NA+dtGbsW zwSJd|`Mk~bNYQKxAYwf|L-^6$n(;D@4F-`6e&8HKPB9xeP(e{qxLPKfdZBWk#QRjx zNYOj;De0)xhNrgOF0Xz0awLtwe@3x_K8J7zvXV6Z2($@~?3acHX&D*y8}645#m(gu z&bQPF)D>ksdi~n_s?)lU;<&#aVYaH{zE%8s_Wb}NCjA=rW*-v^bn3M1=4BEQr zHX;2IK+r20*1tV);shl9=`oHWK|%GOK5Zpb1dSaSI8}{byTbg~e+{AI>hw`aL*3Bn{V@r+WIW;)gTFz>a9OO^* z%E8KR5eJNC?hUhW*|{&!!PI87wNlI@>Wt`-eVYZNeIFCk?76qTW-2)^HkvX#PSQYD zDqA#ZOwzyV&h17Vna#T5@OJ@X9$9p4HP_c+!Ak6#m(a^sK0OxcHu`lHRY zQd72U8D<)pSx8t+t$fEW6BBiY13l8HIq25ZyA48Y-f;xLb6rUJOLV!x-IczK4;gG| zucxa^U_w>}LZ9b|ULO`969^p-<4qL+BhgO=x7q`4QF-ywsO|VgP1S%Z_r!Et($J3%dlbIe)Xy&DtGvkZ-T?#7Y|Q$Na|F-%XmqqIde8eOtvKo@laX#Bz6X;?P!tHfjRP_c7;>?%)5Wyxa|HFp&Mz zm*+NqyIbU*u<>}O{_8%r#JSeo~hl+ z&*>1ZaA2avq+V?{ue&WDtbR*AX?a;)jH%m0PqEePJ+rjs^&@{)zY4fFGe59|CW8@< zWgf6`&{bm%HOPdpq~Uw<6`4ZFSZ0SvL^R~6{Lf^Xkr0aPUr>0#(x~4i%+uNwzsP-? z&WVv$yIu%OA+M3wO()`xvj%(tPF>LKKZCA({NFmv2 zyf#WS-Yaz@SlX?gZ|dr9RaOdq^YWKHmfc9Wbmq)5dJIZVwZ5WN)#GpW*4Ib!z76CV zq_Gq22;e>zv+MY!JI*yF_+# zP5=1A+tH@K;E%VI552h%LX zsvk`JW{EDutHc)jKkd`p)RdH*42_YI*R*NR5OPuo!UnOJelCQkN85>C!=1Z#scP)w z4ETT?VN}9B6l6kg(7axnYqn|lx{Oo|s>ic58==JIarojne&{jTz$^NKSU;V&69o&EY zA4_KfR^$G)|J0^I*eRtHyFrCg2&rh)BorNTNFy?54$+*Fp=78-G$C^tlCjZ{F@%f_ zGKW%#Qvc8Md;jNpuitgfdyd-sdA{GZ?sczw-Ru3inf~Ri2PUPIN#@APbdFA(^3&ek zy#k01+#u2zUOjEimhS@IA7jQ)c+a0b%jk&Kg@WWU^4fUlyiSAT!E&#LJ^9CXc|7H>w@|$)(oRz*pizG~w0tDBBaYBsND~59htD5% z6(9A7R4a!MJHZAs*-hiEWHD8JY3~8whpV(HrFWU7GHey!mcvbbioylWJ=mk`qDF(V zZ!T}6_?3)isgxK{fpT`hVwh**p4N#~NA%m9&;X!j+je0@vPX}%b#>2~qGA=+v}wEP zqH#;0V2HNGF@oPTtGedSJ%m`CsKG1{JOdT?SSu@zE|G($AwPBO+O@waDX4xIJwNM$ z%G}xc1G?pmj100Yvy3#FLU@Bz8axGL+aDldQc?u#%!LaRwy@(Xh1?6zq@OI`+m-nY zRK+N^u|Nq~T+yM^Y|ADdH@D+xBta?Ztu#HE9XnZASdCEtU|P^#r}Pnv!+%>2AHsji zLlKwC9~&ykxu59mE$WJh^vDtF zs;cZB&0||+%8QQHCYd=e08B`=sffESyh7b>vp*#%sf#mZ^g$8|4K4R(GzqLOZTP_A zJ2026fo|hjQW{K3O6lA=b5Nl%WMkum*No^x4a`3cb-MeTP7Wk|VGT&}$O#h7MPOD! zqeoe~7&HMlL0iE-f-qW3t;E2GP7=~P;xNVJ`WBFwJcD1(D&ZWLnex4$ojzE7zQKcj? z(C^dEqU#CS%i%cRa;zH53>YjO4y|^|()3|j1BE{_aG=sU+m@B>5~5RJAfA_-^>IX#UK*HPVVZh}3q(;2$`0qAn81&wv|_>BPpx++&fLLsF1l@8NTD1@w8Gg_`Z+M&7%$V1PEG}(1L6`)!*7Qi zmQtrGqhnGtBRqQ;v5!t8r0DV09{p)bwl%$xHCrq<;6hmERrGeKm-$o!uvvt*0_gqU zT*mG4a@a&76?=?L`zjjQ(^ARK%oLn!7{Ow@vW3M(m>ejON=2f$O-S2{EzHg-&vy0F zO^AyF&*Ll^3;{qq!LZua7L~~TCr@ZCFcet+@}(7R2@VTsnr5TSGg@3^?cY!^a5|>) z|BYJwoY;v79j6^C8VFblA4o$Uz2E%4{rh)drwQwpII8ooba&DokAP^5Ydguhslaco z;J>)nk8hd=j?B-#2gT( zO2#yW-VyJf5k5s#Q@94X-JA9-@e5!?BQ7A|4Mi(k>;Q(m8kksZ?F=%x zU}2P<&GfJhHd!pbv&#TE;8#vJ5)y;IXmZf(qrNL0e~j71i4#v;x&#(ho?gJnAtEc# z9^*4YYW{SJnlrN|#@M`SvEePLW1^YRKGC^Q0#b8wks$Ht}n+WDqsGqVnej4 z^rN7&1d?qWJ>7=A7?26RUPTfE17WB`Sb*AqY zTD~;;F-VFHxXq1b1TY1s?3EkgiYPpQMOsu9iHQ)9Sj}^4$Jwps?ea^RSpehF`reOt(T75iSRv;$9H2Xre>LbCg)}OI>ARXlT>?$HXbPPK%+9EMU{prM-SEqzW(77T>X3 zJN$^a)nL&~!#%Wemi}*OMQE>(aH3We_m}^E;N1un19!RKKf>oGXeA;m(mOsIfjYu+ zf93#B&ZS!2y|7UFLh`%kE;m1gU9i+En#nstPMtdpJ_Q{N--j_jUQT*Pp%ue5!m_Py zZaPWu4g5H80L~8?Cr)g2HEt_to_yH8&gFMo8%9ZXX~QinHlG{R4Tf?WvL3Q5vFv}2 zJh_mkHS_twbla(1++V@zVlPd_ghGWYNRxn<#6c)3)6{cxP>5QgO}R#h$nuERh+Mb8 z*SCTkZ6lwV{rA03Fw_-lh1$BhbqHKJyy(CwwfU0s?*8}o^JgS4!C3NwhX@GFg^~AB z>m1I>iL=YlvcsNf)ak5~(NUxA>z3heGw8$Dugn=Siu8s6Lzj!oPo2d4l$wf4^oQl^ z*S8Ivup5l{x-e|OhKg6OyxrW4SZl{dmGBC?*Hn#Mjv5dS zCn`cmUq2tWHAK&}w)T8MG^D*%D?JyR3Fz0F;vRY1ATp4v{hWtKE2Iq_UANsZ`n(Vd zR_NNOH$&-JeEvK{U%y-DOU$RKeg^Miqzc20dxSs!t5O#NEQQOwl`B2@8z#XlgAk#3 zDy+0FsH@xP*Y_<0R(Ky>-7+N?Rm!JrG%?Pfr~TFE(RREUef_hms-8XL=`$o=qt0Y5 z?KH^-PmcPwcThMKAF#XW^@6_w4OJAjFb~5v_L`}hla}IkhzN>8ce5B82GWCk1s=^)+tGMX)V?0Qw5!K*-)Cm!wQqZ_#SqZr>Lf-j~a^Nd8Yy*&mYv6RJWwdk{2<1 z_OQHXIXY-SKgo`tO3`aJYEFaca;+VADr`BNbXsh;9@Jv}h1x!P_m z9B2+Lij9cLEuQS*7r?=LFf2xj6 z3!m8W$cXJIHisVxQzl=YALNEPT@HGngBqDMN#7o@gqFP^Bz)b*H`jX*6_DQe3~_0= z{krf~Sni8$$|lbuX87yg?g%1)0Xar6IFz#<85_*+Bgk7-)zttW{|*+YgVyO4gb|W) z6y}hAt*H^*f#ALgImxU+M3+IWCD@L6duz45{H8{1CyIBMm|MWW-tqTO=T5sm`^=I9O0*Mu=Vs6pW9XgLA9IVX%O6Q5(Iwf-)FnBa4i!S@FLia1kL>|T z7-%z!I2)YIdm=T}b>ls<^q#pADd)1;FdN-}s0lE8tjAr%>)btFK12!5`wo{~=u)Ed zAnuS)I%x0_*#xjp8H|iyR2vsYM++p4Qi}_>FEz#1( z=2LA-8hu*F`hA93?djDt22V_xe+MsnSgEhR}%0fQ516 z$AaF{pI20*XJv6l7r$?t}j=%pDPftiY+fY-LBJbm$vK>%Mw)y_ckZmD z0A?5gp`KFgdA3v0=P+;Y$htPNW)4S5u7QRRW{IS^>5Y5Bk28}k=~-lYr`Jc#Y=;0|PWH1cZTOMPR6r6;Ji*e7)J)-ARqeS(`Kl93iTj{E{Zol>7Z zz`6#$oA@>Q0{ExJfemO{p#2YFqLSarY34^e#`VcHOUW$0C~_2m74cAIIH6b1p0W3f zAm0#_ME+KEb@$zoe$h8y_uZ(~QzNSj&Q4z79rktUqz~PxJ>3tNHdvaOMdx)rz7G{e zu_=mEF2d}LDl1)8>LCugZ6gxgU9*FlbECqnJ~7pPXjC2*HnLJaTmR_TD;J;3YwiYXRnVK0$NmI_4O_EYrFJgWDJe;pNR>3Y`;4 zUP&KjwTFxww_h?#u*p1XY3KFRr}sR$BkqJqx)4Q0QU$%7xBgzUwC4bQ&hzD$=a(M9 z@9bxn221NYv67M?WlwVEL55T`IHBI8yrR3$A0wp~tF``kE|(Q}Hjf-c*N4*kB}wvW z0$WLmXM0C2j_jfEq@+UQ@4$aHjs4MWyVN_4FuV5O=PFJ!3l022aS`$lVcmWgl#3w5 z(b&40>k#I!@%Ldjbxn>ieKpBWKgj2hKSru_>C>HZ11NW? zyYnQd9=gOk=wwzgFbWd|OHG5NVkF`nP<-+Jek=(+d6EG)o2ir8YGrsPFE6R!d9`w2 zRpD}Xck$N|wyV^&w0g+Pr;c2xdg;^^24%o(O{WhXx=u~aHr)>knNONNWeT1GLuczq za}alh%)m#IbB&y7D->rGzqQR#WP%|0E826C^94h-2f%%aP4?foB-grP_8KXic`<=!O_9iHUs~}eHzhQi+|^mDd66ARzsrzN=cdU$+?UIJ(MbV|Nea^U6sH< zwif(X9!Oq=c7|Bs6BRr5?7I~9J1nda$3k*26)Ki7n-30re)m9nR#}kT;fa?k@hl$= zKC^{CY`+fa5?}d<`4UN+gILkV_cB^;+w_55nX+nLWd@9PCd#Y&d`t=OJ@?3oZ zwN)?E;W7+l7kti+86np-uFQV6KD)|BjvP4a(8e=k_KGgjR$>hWxSE+jh?%oz&Ek@f z`t;M#5Hwuy#x6_z!u(Z!o$qNWIQd~nZbF>XRcUtv?{z6y_;2xGZ> z5vPUq;pA>kxsxf9E@Q_#xKJzG?YzR0=ClwMG>WO?Jnr&AUtJFJu1c4{>p9hb)&`E4NL*BzhgxF#=+W=z@?J0-IG#ckjM>{rcYh`zEMT4^DxO1R6RzIUx-LS8l-a zS+F`de;ztjr76_?YTi`us*xWGW6!o9RIbh5L&Gp+XZSyzp`NKN84j&uiqj&-jEd|{ z>%*KGFYIIzzrbM+*3)BHIA;EKU>AObtf4x1uxq_qwrA9K(U~FSR*`$~vSmGb^@KSPlb?_b!-LCZ|w{O2-BZ#Bl9feOad$nG}36&bzDLMF9dW);H8R3ZZw{)02?ZP&#YA1o?ciuAVJF_ zjo+Z?AxgMrL73)Ju*;sdrQkZ!uOG4e*OwP+8XC7y-<>#tyU+_VDJ?bD#UnGaMhRhR z3c)bS%&Zbe)8oh8Act??{#;wSDQ7YJjC8FvcE!?C${OsV7}z=1%~kTEv*kow(?Z}L zr4c4sdo5NSMUaSeOD{XgYj9I@GvKEGnB}6%TpHD@748G0nAulXj0KfHyz~A}Dy3OM zM66b(-+_SiQ~<`rb?Ywf-~XCiZyLyOZxbp>+PK2B>cQ1JO8C@Yuu6s3vA;kwW*x;V z&zPs9e<(0BWgdrV(V|cicA_O+55XvkYRxC2qA)FW6qD@GYLsa8XM`obbSFO6ff7Gl zOU@Y^Hsi$a4c%%_Sxd^A5ZigF2fU&V<}ZB{6Q8M5v#^i<_RX5T0|l?czikSA&ONKY zqrFD3O1^nBhz65$*W!xkA&d%zySp(aX_w*qzzV4p&ftj6bak6iXV7YAQi3+0--7nr zSAJw~9^SE`vt#VOkjeYZl{7V3NPyEO9m0UFU1K1ONvc3Ex_in~+vuYgm82n$sG13{ z2I?po3`_VJ#T=!#R|7nDVub+$_ zl1hQ$($(DYlP-4#XSa-ROpXon574=^>(tegE%(=ZAgb6s+$QFg*rHL zMxkJd$0bX~f<2wg1fjn$JWP;*wXnOi`Ij|X2ksI|yy%ZsLxo0Y;kAbxBm^FdXGl;tY4Uk1>@i{8xH((XBlWI@(W`g@(RF zNOu4Jc5^{9+K@UAc4=pFa%p+vIc~M8Qdr#k@lr=CLLv@JJ$n*1!0YC=#CGGsKlWO) zVn7n_5E-TMX?8<_&icHG}Yh_GN9XlqY%50#EP7>B%b}8j_H4*vSJyFc4rTqlmW9DIaTq;Jo z?fQcUN#0>U_xNlzyd?G!<$Z?>gd3t~@1RjfRA7^>pGWLA`K3KFPmv^O>L~T70r)`{ zQWWUBZy-9u4%M|oBGBH;$nw6hJjO9Ae)H!27OOVr#Nua2sHOT=^2ll|lSp?Zyu{Vr zC;QsX?9$rKm!UA^}_E#Wcv+}{VjU3jNIXDtCw8PALn}p1$*5y*0I8YJkG~zV$s%PNsgNoFotd(ssG-N=}9`P$9+5pWh`5zBlikXpS zh*S8tR!iO6mh2K7wG?c^I}p5?)YTa`lzQn&>}z`C#6mG55cn~N*Y)YAX-s8_ue4ujEG+^p>y4T#7Z@F(pGN@>>_-sH zdn4_T9@spel9*WY^((zhOkK(5w3{YEP&qUsK||~uF*D)vbx<+x8w{cG85nCoO%U^{ z-^W6;!~3HTv}dhBcRGVSO-3mfT2=GtivP_v((*WWwDV8|K}r2KJ%yi zB=%S^xydJ_QZGa|PbTAp)1%K;oI7t`r{hBF_}tCUo|cx%tQZJrBNNrVmW(}#f zVZ*0heY`u%lVq<8)CH)!=zQ!WUAgWOFUHomc*Z_gFEh02?j9Txk`lZ!Dse#E2cSGM z3HVINOvK{X6Dig6^G8XJfsX(j9AvRp#KOB+$r%}i5{HN^98TSZdqNI{J^NX8| zO+M-l+M85lmY3VOwOj!pBFI2W52}|}2d!L1aF{gdD`$PXW2HUnQ$QD zo8SV%bQEfD25SM=hyg%wu%Li(48m;0kVf+$(;D?CLSaq$U;fa|z~SvK@D-Aqj_Z_4 zhLM!v5Afp~!Wkn@3G2X^CLOP-)46?vsCA_6+h0*KO@-iAA}kmdQtpeE%$Bpm2`ITaYk#Cy8ZP* z4mM}bz=uJEE*MoJC4xQF@|+ju{LuQ{^)*eer5_os^^+-aJ~V(CE)j%C&POClx`0MT zrX}|kEM1KrkB7C3_#G5PW?*(}{hyXWZc2c*h@(b!GH`Fao*=V-9UTP=I=VWsZ+wck z7K&E5?=#46G*YD?#oulwgr?tQ;sI(Tu0El=DuaJP>xV~-a0s4_fd(d1q1U!h(>gl` zaR~U&kh0+n`{wQ2S7+vJ&(Hr!SB7hAZf?%hE$-Hdh}RjkD0swH)p$4kO))VUV;MGI z5ENAZ{=II3rs>n_9Uq?+YE0`BJgV;f`#rmMQBJ0Ce`uBxMFH$2cMim;;loP;htr*9 zL{{##Y;t+DViQ-7R|7Z6J%W4VZ9_`=TEmC2KaPDszBcHP5s6=A^p~dws|L^8x?RpY ztZjUUmZ7VDG86`f7+V;^?SzK)Ii8HPoI=q{*@4Fd*tMo;G;|`JdomOkdL+iF` zD*2l`9flqYr7HSTlz9mWpV@54$3SeA{KSxV#eY;a=i;SHCNuN7qlpEV|2a0zZ_r{^ z8q9yBj|y*f^9c^L_RjF|viOJ;@o#1p`{TKrGcmG5dB6Y`PT#tAO(;Na1|l-@m^Djq znB`~UbD8n(?=kf^K1cgxWoF_;%CsBn6R0o{0>;F2ohBoW#quIbp=jvv-%gV^CtA_1 zrH7Qdcdzob?hN0}X~AQlaw9kl5W&l$JT;9S=p61mp=%8?2c1EKQjx;_r=vX%2_1$Q z<2Re%dhNWV_&yIrD5CLmR5ENjUpBrjj02F7P(T2qRwI$w8bLLY+hYH{^Xk9=EA!H=)c&N$7OF^1_Zx>W#4fiG2q-BVjOgj$y7E{+e}0dcq^hMcjnIgqZSB%}W!mz^V=rRHo${ZK~RUZV_c$f1}bwbw%RnE-&x*Z2Z9~;D6?9JuDn!pCM})ZorJ}AJ8vx z73*rrf0|kWD_5SSK9}^mw5sc+X1!q*>8-`3rBTs?DTIK*?niG)py#2tOxG5SQZW5p>oK* zpJK5}_@jgi7G|l@W@#pag5d5tHt&=3*Mg4CnYA`10|i zIvzGgMo{1g7iinu@HORd+BgdvPWJ5C9v{y}al5U^g3QK@Nu_F7n~V5@>y5Qetm>CS zrKg>TZ~d-bEtUCTDbbMY@922aD=#vo>wL54JXFvF`UE=7v`_O9fwpYo#35rE1W%xP z9M?py^66hT;}D;hT~!~{m@}^K`lO4_*4EbT$Pd=zWka06O$e%`{X0jN#Ke|I>T`*^C^-0F>jDnANf;As#L0B+G-U0LC z)XjTc=;sH_+jK(4%em}7%x(HD7`0NCu54>cvU4`jhOsI}$V*&OazD_~-^Q>3DHV)r zFh{n-W;0n?`f!K%-UJ+^4)l*)@MEP5q}tsYz`Gs)y@|%Fo7UUct^w7sI7G?1t?vSutejWrKXKzJS;Y?m%aT0kW%0iN zK!BI-@YlC)ZANb|lSZT<&7vl|eB}z2G;A7WMf{A!SIZ(dsI|UNR&ASwG58xQup8H| zfqBvPyeck^*s$^M({{_tV{A)g8>OCh&-4^bk(Lhc7FEpXs;bHCEvL0thJ@Q>RN3$7 zdh_23HRm^T<34>Vr{Ag6-L^|!mLtfKX^hPsEGG>K|u&;hp!11fcqvSp{(nC6SqYE5Mt%tme9F-Ah)jpkQH?*JiFs za=DqQX}IPJbQ65S!X^L2FBJy$*=sAFG_=8?4^WFY14WmW26fEP;bJt*^yFXxxNfY2 zLE$?i&;J_DRs=QY7r$`4xj*jL_P997&>dWGW@}pQ;S!okX*1HRBW3ls3NZ!!pjH4_ zA&Emv&9w(eW1Y_S$EeU&o-qIa0%vB{UloQ`!}$^A%hM{_vI^0bj9dF%Sp5O;NBM*} z2=kJ5r&}JYBjtw;9yw|hGB>3%J5x1~1`I8w784|Z6AmGN7C9s{q-m3!%w>wzu6+{5 z9_dMvQuKNsVROsvg@)lkD2VLbC(-tO*;dp*Lz+a|2vY_}=%4koOv7}yOP=vyAbdC`SZ`1q{U)BnRMSm^ zYG<>~0uBW8y8T8iV!$Pe#dnM}z$Q{rdyMbT{@1pjC#amTc4WN<+fgp_=t<4AlO;p3 z!<=;Q&K(}uJc`eTZ{LW>jV??=SgvGJaDxI~|Yz#k8fBnk?e;aD)_F zvrnJ?j_wi$8s+!uWy^lEW-)EjFRDZO!poM8S=SN->xP;~Sf$1{78Z=Xlf95}=FAE* zFt(MPifD)xj|omO4-1-l*WemIYkdwBfMQ`0PhZGb2yrCdua>>qy>moZWPC zoBY*2EB0vgpR zKwRwi+rDl7>QBl|&2*fbI?7Baed%wdU!n!y2`MvouD<8xnzRhvGqPPEBBo#`5tz#R zL12Mev)u)~3i4FsZYxiM=y{f9_ACGArWjmxAbUys-KRj{E;`bg14?A%&NTd=4d8cD zVF>2vPV_Nex|wF9#inSpX*tK$;LfEr7KeSWk4HhNt$*^!5q3B1LNbNnc6&WbVGwjm z*A8!qAuM&dA$B>&m@JwpS(S%meSZ8)dcKg6^0u{H?%K9qFa8q8so)}J&g7{AQ>&`t z-lMH&6Q5Ng8xqP~=6>${OAkKMZ3`DCGW}O|+>ZD{{{fFZ+G9)Af@bhzVYz6H>*!>% zjb+iI1}Y*rVBAy$?*YR07`iLW&m^7`EmMrhK}NE2_3F&in-A(3?rHq#FxB39Y{m-o zV|1q?{3FmbGYiZBoLX6NF$)~K4;7nzqezsyE-F1&SZGZ{l1}5PQ`;f)LqZD9p50mO z>p5+jw0qYfhY4Dscdj1i2bU}`90Up)QvnMK(;IJ{%N+fpQk0(6I;_5B>GyDR$cUYh zwf^+csHe^^dWN|IV?i-gB+P5zgCbSlrCA|wah%6o)4o-zG#yI4kD$yJOArAv;SDCyo>O8_(2f(!mwILyKA=-h7ZF7wl)Jq5XfWuqvZFeaA$eP zf!z@RcUUC`?+MvOf!l8g)8B)433!HZ_UYqCDogNG?IAeNViTE^G3IA=kzTNFd8w9ydZ8ye;?$TMU}cZqDu zK@PE*MvrZm@b;Pe>whk);UVVztv45ay1DWHjD9%2!p3=aj}XnsP$0^h=J->=#8Q$nj%#$iamPvt?YOWr z@vmm9jZ#OPXncQr)BkifYc%?ojV{^U)p)?KW*B2gg2@vm*mrmMC@S&yY#`50Nz?Av zRoZI!aQ`Js`m@QJG=*r##o~<5z>KmvbY3gV%U#BgKge7yR1Mq)d+%_8Bm@HRH~I{Yi5>6g^v_^X;VSgTCyaOp=6hT8cRt7(@Ptr#par#F0tM>XQ$C_$6JjUZ4 znh~aK-Klt&SnL=~_~ENk%DKHlyNQoHbyEBas7e-0?&Lec{pRF=Za>!6+Gah#aAe@% z!3tv!@@@EfD^^eqm?xKe(kp)W5KDv~&CSTdSk`rne0eD+)#hR6)Q^4tl<pm+)F z$uAZI@VfYFfx~}u(V>V;FRWg(rjWaFxd*KU24f(|fCRAbP#LgYJhE?ysunH;B~f7d z1q28v*=r^VB8@D?Im(^gwr1a+u&tX?Qd99ksRn{oR_+Wr4vkNjEfLwd^fR(+M8eRS z`9@#?Sd3e@7BHtt4q(kHMOwi-#vM6HD8(I^^k4g3^k`RjxDCL9y&tGjls0#ZJwul! zqsxIZd+ossTBp-`faDC`G7HBoblad1AceroU{Dm?Z1V|kGyVbv*cqN>?oD4ot#}qi{pp6RN-(zs%DKLY2L#r;6vEYdwUDay1xE(`p{@7Uc6v0 z4-N?(xY3h&{r9*17?S(w5tR^UijOjC>1&o#44kzXOFAP18YJ+y6DA}OtZ2ORkUYY$ z&6>e;Vv>S_mly)-<;#t+vO;3H7E>qe5Fnp{$)%0GMvIK_A$kdu!cC(FVHgdb+1uF} zYnj)K5g6Geb~)mClrbPa0J1kLBWkIam{6&!$FF>eikq4iqm2kIWV@P-Oca!MwU>h-Hm_7S7FMbi`cF|kHoO*TKR*_3w!ZByMdo;Rg1^_&VcD7x; zzcDG=sn2Tnsvr!DLC_4`@+kh2+RgnN#X|&9l8XeBPew> zLiX+32lVTQI*R>RGtLGn)w}b`ky*0#XpF;ro(N!@aszkhq1k;&gMtEc_3GH^6-XNB zL5&?dRyzZ&C8{JQcD}J6Ech^$J_O3vR(nyy-Th-0SB`U?68%^D@O)LiCQ?T@It{)eKAPuKg%qO$%6vQ1O0~l9{q4T zdXLfFJ+ZbSQpkJG3k{`o5^9#~6AHSOdVlCtw^5Kv=Xzq3^dS#^0#XsJxI+|0`|sb) z2Cc4eZrpME6NY+v^<==lEz7LdwHPvTGN*U%_7?(V($NJDHK8L|o@DcuyRG@nH@qCyC&550Vrf6QPEhA-bc_U+L>O%@=b*Nta=* zp<$sl$Ml`OIN@+ zzB~yzM&k@4-@ZSDyU!hlO_Jo4?O`I>q|257%Z~SXVPTQ~FxbbQrc$C~dHF|@TA6wK zsp&wr_@ph@j|wkzELE@1=9Ebp;nEEk!I zSQ!|H>cVF~176r8Am&UTS$Z=(DoPhSR4*?w?{rXsw>QHM8kb$gVd$=)KeUa4*fL7=Y zkP)XHp$$*#ohDQ93ReS|0dNv{MaZ}g#bl_R45>TAs1@ljrJ zdXk70mVPucn^G@a_)YmlJOAt!+Otqy;gy0Me)i=8+(;mie65UUoQ`%qh!0LVCffP= zbVN1!_9abb+FhqfK5rhX+stFf5d3WclXT7tOr^$#8R`VWAi4?OA}S6H3Gt#ghnXJ; z5T=5X;Lrns6@^=ek)>C^f5g0Pv>nNJt*xz6A4@B001K^uh=~SwiDQ)&y8nda;XkU`2yr`l^mLk*4O@yoDzo4B?c4F!>Ur_GJF4l-a_f^?N|uc5_~tyU z%!e~Wt<*$B0``K)qc2?>aPe&C3Af0cwHa?^oTA0jwHx9dz{~C0v17rKB^$|Bv}HY( z*yJ3g{stxQXqYl}D#&w;5VELeQEN0DWbBj#%d@0~Dk+8xV?eeD++0T0Iapu+5RVQA z2D&QzA$pMHP_LiN@MG-um#T_#6w6pL9?=fHK%{RNv+eW&$x#r0M8vbXxx#~Goec6h zR1HvTxB*id;i!NDS|X7MOC6X5MdbC9?}oV|S~U^{boSV=4y*7iQfN^Pz6kC|D*5-k zWZdTKct+H?{B{zD>85LrDbBxA>hE?QQ;c!9urG`_SPdlsi3noF5)^Kdj`5|y@*Vt+ zT(9HM=EGBuA3F2|b&R`v_}1T0_iIB!pr_r7yjaeHkZ^}TjW4F!>HdM%A*S;V??XUF zqnAn2>)nYz)N4$gqcTUL3FhEJYAt#s}5!`EJW#Jk<40p2L6n<4+KAwMt18Vp0 z^8RXcD~k66JqG0wcbc2g1dP{G+0rLBwW^oo7-Br|m#B8m!!h)k_@^Q}+S)Wx zo>W(NzP+sm{|R$vTFF;Z!l^be`r`<8cLGi@d=X5CaI`|(aC=-AnI_&CeamMTR}Bfq zgBK_}F?bG}`?hIWb~|^Sa+E0+0y%4XN%nr#1C5P=>BbUC_!h|JG-4$hlA2c(0lg_$ zlU2No{ti6b`nlzPN57J7sWe91^x0no#%*zpUW$aA!C(bBImx+A7%u<)^yLfApLX=F z2sGgvd6~4gNKvp9J=W81C1u)W6y;$X_LTE(Vfnn?N2 z$VhL9;xT4s(7d^H5>*r4e*0Ga_%R0Gm)PwGtwcnDzs?%XmK(khJ_ZCv%&0N#BGHpc zqV9hE@`W-M!|8?Y7U))pNVMTlEgkV>^7j1JtvyoRSS2M@yW9E1Ix-CwnCa!{rp(=M zb;fqLnEwPWi}T+=On_G!wWumw{Jm3Y?XafHax3QNZ18O_xp4z6PGxoV_RDW6Baj0D zL)B^*^4KUwD6&YrpaVEADhz-nT}4JFISFjPS-~&lDnhFf{*8={`S0kGeFjfS4JG?4 zK3AikKpo1(k(J#<%biqCYl9w^Q1mS8slJ?F2TPu_RanY1rBC1YSHfcW8*uMcJ7SMQL^-hW?(WnGEq1+L*_4CdPDOvaxZcibnl@qa;@H01GGKqo+ph;gMWKnawKnDWpn8+-i{2ae73DY562JSWiI1JpkR{Nc9a(Rslh7sc(9sR4jy`N(`Rqo() zo-E23W3cTz>|y zDRv8>*9Qc`_ZD3-Hg?aJYI<$V5)V8(E5wnpr`(tlIT@!4LS=6&ldAE>4R@=n2gD2r zJnuiB7HoKXutfQ0U%cjE_n8jC2@h_nf147CVTs$kh+&%?bOE zkM^V0mWitu(=tB{#FPF;u(((8AyyE{SWU=qhc;~At>nIdfevmXyyu!la$-oN#;w3M zmfOTFm6%56gJop{Ci51;Cre#Kz{W2^s*syf)Gkob3@7R7sVFOF(E%YRp`Jno`|9%A zsnlGC2_2)yDuuB)^dbw7c|gP<(4X*jRokEHN?D$c>1It(qJ4DOtjHVOee81LY~4xEgmNB_`fkh?->m5w7nPI7zvanb2R;-m5}?XeFd-z>g}rJ9qItla>O ztVRbvsc^pj-I{!RZx~nMjD1I?y#e@ZgFsOVlhybGz zXi~%%s473MyhxxWIP;v)dQ!B~;;3M#z|fEdp!_PvE{jlv6Wj> zv})a@&QA;0C)J@H6ffVva7AveDRB|_K#Ae+61f1Tb`ROm;a@%qUy2i6)qpI4o;XZe z#9kUwJyK@Qp56b_JcStcTsdYvkP2mgd9mm#QZZ60gEGSYNxg(k)ken?r%Raijv|cx zfMtu?LHwJ_h%sxP2`!usKs63-sOE48#~o#~mDNks`!+T;R1}~aj8+*4JDr=$#8xWZ z7u?Yz-6%5odC>Frl!WzpvC^3@tL`RR z8uQnc-iRp)#oL-F|lmoWqiTXlqrUMy~;xsIxqIF7*ZYgL{u zJ08}L43SkHtvaBEx|?`q7zUMU)08%L{pwk>Uh-~{iZng|PoYXAF~X%|xy?Z_#Mh*a zm2FqqpTb8+hz2;o%eJ(mf6IjWc3@s6f!5kOj0lmK_SfQEu1UT8YwZ zAgEt<_H5e5lt#d(7`q5PwX2#WEDCWIu^2Ens9?Qhoum(qXJ>guJeB4UlyjWY5nC>dR5JnMp7To{g@$zT#S0UAzP3oL^Q z4V}fOV%j--Dy+%z<9B>b;bBb^b;A0ZS#*o)A{{^|r*Fj`OBP`9H{wZm7_P`F8`=ox z3g`Wo;L1lPi^X^S>eFpxd-t6?9CQSb%&UUj0mm_0AA9G(hR;7wwV8Y57a3$$IJO!T zC9Oi4L+j#PIERbq|M25V27M4?@P}X{Idn8CsdXSHIpy3DmKNf|3uxBZs&8O`*+ynY zhHYf-H+tnX)9in1)gPqIFTNfRTp-0@K}ivAM;F)g3t^Y)7qfzk{ zvkA#b%q(psa`_XeC@D>7Ytr}7R{R$%P70@gefcu$9?%ELsH)x^PPX9}FvL@O=PaiA zUa!;7t_KzIoL^K`m0iD{8M(D7!PIH?>A-lp2GmiI>Dr62NgJq8WBRY|=93M}!bOK; zUoBO;`a0U~ARbs&qwOLQmMiFqZlc-xm$^l5smBP^LKtu`E@Z6aj(%)s>ilz>0(+!5lr87nFFb=x}%4;r=oIYGOlLwuQI z6jk^6GRoSz6Z8^h$c^|)k)KKV;t>H=~a=BFi7i%x22DxWur2vuZEXAn zN)7RllxWQq(Ezo>E~a(sPWyF+zzX;Zy$lHMn$n+ z{L|O@@loi$6DP3bWyKVe?Syt!u^4Ji@B+o=>C&Y(6yCO$3T zZ9&fzXqe3KjH6ziK_uwtCs^Psy`1M>Z;)J0H<0EfJ^u1Qe!GtTr7Kr9Ft|A;5=_eP zgPe}Q6~1d@7a%lMq<5|_W;wTDq!?+EZ#z~PV~(&x$tY$ij?D*;96_0S82>Lnzb3LK z*NI{dz|v@)5zu9K%;K>-%9{4V+R$=?#a=XyxdW;Z%q?MIo-YClo3sY0y1q&25jl!Pw&KpD! zrhd|&v$-+(v&*I{GcdUL2V?dUuMP#d&QIEFYD>CSu7h4I*$d)OqDH^&tX^EEn(z;XQxs7&#HksX-k`IHHAR2(;P712_6bf?S9`^n z=9Cpu6;c>Ag^0wF>7JoOW9)3t@<>gLOq}(4`^Lhy^8n!}34l5QLQetB9jq3bL7akc zICL_OE$F*wqW+JsgcZttxBvhv0Hx*EuU{=KW}`>zR>>_}>hMhd_d9WiT{4@hIDb_y zUd)_6T{cM`GyLJHD=pIS!SnD~28B1=-_wiZgB%wWm>cls{rhjYf3g6KeL~3ti@Yq- zwtZ6DEtkF#Z>7v(z;12RaF|5LEJ$_KtaK&`NU+UW3j;*6kcsiN*Xmb!_-jOD}+_L6q7u`v-|MW zmd;O|Lswatqetf62=MC4~VXUP&))K?;&4)^4T(SxFNHO*sAS73IK z>7dAfhR4x_CV3zbf<`j@ii$oM`7e`OxY_`K$yfN+;v6q{++%);I1Wn+UJg)!{SsPi z1hrW8i4iM4EJO)`nC+EhDAaF#nYF%q7v0s8?AMZ{l(A;LudL~vXeaSVd9`fZtR8uD zvfT|)7f~2?A|ruLWR|we{RFCT=3y_N01M)wr5TbJi)#Mv?c2Jcv-q;|Pm=jYw?f>{ zJqj|T-Ya;4Cy1Pa!aZg>iH}g5US~Ru_w>@$54~J8G3MDv@Cor7f1ES0WnlcNM=ELJ z6Afv{@pR$eAYmx$u$?s>J60=I5x_{GK=3qX%nT=|O_L6@ptYz&u(CyvYsKnB>0uqC z_3iN8A(As6K>vB)HUAUH-BHq3&x2_%~ z4@YQg*MjA#wda~_rr1LY9NF(QdmMId=9S6$*dWvb{n2lwdZ)RmW%+;yOtX#QA^P>%4p6078H zsbUrwIRcmjVy8x~^L)>a!UJ;gbrWde=iA@Bg(mZwPvpVQ=Nq=VJA_+xQR$*u0Sbok zBkK|ILH-yv&aMTP=S&2CtM%z)YXvidjYJ1<1jdSk1#Dy91ne(;z~9%`mnwvvla-Zv zJY{rm_4Q?g1`cGwTUQln4s=*OM%+%@P`8{Q8-Sn@P{P}hf#IqV>DX?2OE@{jEry}z z#AE!!!gYarW?BffF3o+e9Ha@g115(k26&eYd%T^)n?ol-J@2~%gUB?9^PRI{n0Vrd zI|(VmQl>?VxWyblGcz-EkCau60@6`t<5Sa*A54`|o2209Z+8;1vace;a8+DLDifkF zH3269WsE>2y?Haj!a_mT4wi}Iw}^2Pd_qW}^a{{>$@J>s+j)9%D-;{z02BxRB6LBz(z=`21ghsTdk%hl=Z<3TIp8(hI%v9pm5PeO zL8mn%_oahyva^vQW*j+!>G^hFmCVqG$d;I6LJ>_T$iKRIp?v+*QKYQj8zcWk!NA87~x}w7;t8c7*G}Q7jxhazxuo>YUl4%Y0=&duwbc zBog?i%@zI3gEKtY+=sa_laPZEvu0(F|6{dOCUg)`9u8ezUO*tYee1~R)=tsi^g0&r2 zE_j|cmDYZ;ot-PigPJLL_KsY*8`B_mNCO`EhQqo~ay9OF+TgzO6H&Y2yRAobEX-5i z(MMU7LSu_gAu*et=B(3!<-90D-QV_hZk^32ys&usoD(i4g*qRS?*K64K6Caw2cJ85 z@EWI=OE5**A`MIN7DP152$V)MPK(B9@#XlCg8qxX3tPG|@Fc(?iGn}!!uE8Ay?p@t zVjvztNIWJw17+_Vr%ZWBi^l!Z&YeuJ?~jbMv$ZYebP$AdKYwayFk)!H#l`lf4A?Hu zixW=_G*3-P7=i={lZeX7vk2lR9Y%c4{zWHT8V($=X4w@x62O&4sziQFzI$A%p{{~x z;fwJ!(H3!Ui0Ys|{&9-oPN>1$PCu32{rb%W=gz&WF+6qOjvdR9%GB2fat8U#gzeU; z>DO{g$9XlMmeuUN+e~VNtx1^(YKO-7brL_V8BQaTWd2i%|BDwbQs9yv`<2I6W&?Ot z6UOT&C+$aX_=HX0s0jIY>~|QeesQ!}2kcYg*KMdk*=s#{@={u!TmtOh(z1`YPN-JK zQ?{X@Jg0@NJpgha<}8Yf`19rzORb+IPm z+jD90Fr`z2&rwJ%33Dx9kXP(%*E+!OuJ{|7p{%_883_c*4!U}ct)q}QfhhSP+z}QZ zcTyn0F*9oj-tyHWL_hkt@-h-dX+#-!?&xK{Bb4CmMik~K!dx;PM!;8?_leq}%P}|; zMa0pw;;gf$9LXtSai6$tzt0?FfZ`P-;1~;%fyLW5RU_blAmjR?(qleKwrf}3u59Py z=g%*KiWr&qInjl_39~YC>kKUaY`XXyIsptyQ5z+-=Z zLQ58_A<_qKsRl$n_zBIJG3t8u?ZDwMr5S&2bX!KJwb&O@vF3Es`kZ9HrSVEqhIw4{ zni|QvVfymO`M{b&5}b6HJ#m^eza}keFv=(#ZyVONsIgzpiF^CUCZs(SAjtmW*GD?% zKh>W$*ug@iy{=o`HUB)wFE69rkl4!=fC0jaRjl@@1;7Dx0_pm1S079d=C?n1=9{^;a zDB!MO25szp5g_k4YZhMb8xUf^$e5zsOpC@Su;K?^c{Xm7@PxyCRr)in5j5 zN&tr8+ZLy3T7j~YI88y6{+f}7|66E^K_)`^PhY?4CzsQMb?&rjGiA)ank$5&Okx!S z`Np=gRjbg~;qchNfoAw<)`jH*Pqv%%8{4L%C0eGR%szR!TiBcQ+nGysLm{4fFT>ps z;S&Iyyo4Nx*MoYBlOWfFskw^EN>Rg9>>WKPqbifqkT@bBDPHiSQ2tw5i!mtxRG4vo zW}bfx)#}BIf4I|#AK)Hns)8(#j|?#|V43U$og_#YLF)d{t|v-khjr^LhRQDwpxsF@ z=5ry1nLd49U?7>P1w0D@jTD0SMK_-%5Xw&qMAzmLCK1NTekvKKt+aS9X)}( z#cL)EKKoV*xxx1U#PMIUw{SmJ3sH}FHOSCb;ThPc57?}C(%V-1bhXa%HA&@x$akoy z2pjxW6Uq}wsZo~kCtV}=zjVp+=19n;l`B>R3{NE+AxoabaqibIY6Y1CM~49qx)N&` zoRG0>AR)lCBLU*BDBrtM*zN43Tc3+l6J>BAlk^e76n(`rx`$*Nx{?m5p)Os#$S(zP z6!-jpB%KLZ&gmM)+qFq632iDNgj5KX5Gs`|M2naZB{4~RX%m$-LW_`LsO&UasjOuU z(Pjx5MJQU%??18i>nv;u{+dEB`8-i&!YZZ9(4A&Y~FNr1Ky}*F$1uRYMhe{5_1{#_$${7s=r)Da7#+1pPbqyBem6R=c&C`YsH@bzIiE4$b9N?e3nmLGDJ-~} zx%Yu@vSLlQ<>BZzMn#D&t{ne-)B{!Lj0u{#bnDwatX?@yLjCzzNRryTPw3RJmi zoUD$R8CYCFYpT3R>raiOupsx;4ot!x=Md2dER@+andAx3A%39X?Q1c}C#L4OFXz|A zQ{`tl_H_6ZV$U%1SVV{H(rF8xs5fcrdc{T+O)tLn=~?lgo}Q7YSj)@b14fu=(9{2g zh2OvdWmyi{{4X6KS#3j^}d~rJ4-~F5+G|^CqEQK>A z3fsr2-*$?zF^jIzQW{O^v$LBzX<#?&8Xt z?^(T#`nQwSvAN+?05Z@~ss9UD!Bc=&LJ7ot=52RXL4zdp=KxJB@BjtpkB#kFy-5U&abn<-brq(wI(NwO%E&O!Yb17qmQVrF8_t7jTM!QeOeh_VfKAIj9B9j0z zva)R3Q_>g_RUAIR#KM`~edv|2_I^3?dZ=Zp- zfgk~UPfSO{K{*LhOgA!69Pt)=1 zUPGdYUjwW~3M?cV*5SN<~FXA!Da9N#v^HF*!Uz+sK_mLNG@~ z(r|GM?R4S-80)P=5W^A&5wwXp6?D$E+Z|#UJzo@@#qJvYRq;@l3*ISM@CaK1gn=-i zEMC+ycdTvVpb?-k_jx!rm+bdGAn>;iG%Vqntie8x_t7 z6mh>;hF*|L%5iR=b8qOf5MH97pa`>I?_X%>RLum&FOgZoFvfkq$KGdrPMAsLNn`85 z4RH?o=|)mAO+8)4I_W{T|t z?N(=>$BssK@87?5`!1zrY3WL8zLo&vz~-WXYZ+I89?Nn*1@H-MLQe&1KxjgDhq#KB zzpED?&6{?c#W)m>7+3hdytLCLxT~Q@BR#z_m*jzxlO^^lhgLO=h1(4S#oqd?=A@GJ=I zmS`6cF3SX;?mLQl3p3VP`CL7 zmJW!Q`Wh2=(tico2pf3(Yv`+xAY2TzgpA4v#|LfJv-{Y~n$(c}_-No-%9v zE&axOy^>9JYc{WBcm?#T6}7I`@YsOP-_%&RN2%asA!r4*qf+( z#NipFnxi471BuF-_3Lj+4Mk=BZUhtv{Ull`M&qJgSpenuD2*oh+Q8)#Z|)n~+{0$;^MdcsJCQ_VKi26q3N&~5_iYR(ZP_aRsG z!{64Mp-=?qbY}_GRxU1ql+D$vY)C$SEJ=f|AVEym6ZJ7@)f<6LO{eqDjFLJ1&69o2Sl5J#d z9b=-bK~eAD`+7%bza*evM1$7vW$UHagjHS0Xkxh@4??Q>;MueP!Vt-vTkAL_FG^?T zH;UEpzBZYTx#C4jo#BgTt|K_w+#fc!FBO2|e5Kza!fI9X-wYg}xeP?HM8^h~jR5%;-kvgBv2;L0KT^tlVB#PJ8w-ub(}*AQ6&CM+%7k`SaYOXT2rP z>D@}>58j2Hu6+%j1Ej=@0B+HnvYnJiSu|YIzY?fqh`FCTG+X_Vz5Dm0t=@tP8LqNy zvKBiVW?v>{N?&&a2V@GpVa6{17-nlzp63$ogq9Tr!pc`nldydqvQa+MbXdc^eh)^y z0fb#Ne}2@7K1P2K(OR5}CS%2oZ5$A=|K`n1NTBxxzx<7G0`!uyPIw>6$^sgWu9Fc$ zcIjlVVlPZ-37K;abvbC+g$uxcE652z9;80h!8dNKu(pn@^djwo| z8xQG^{T;{EZ+3~+c{6b3=$fRm?;vih960~`f|AOp{gQghA1Gm<3wZn^#*8W7@&^AY zB>drSo!R1(uFgI>1Pv|=>l{w{;^rBWM|}j6DedNSc#NK2nZu*)O(VDc_{k@6c2>3- zDm@ESG6+8y&Z%}tMli>XltDutMo);ngrH<;X>oOQ6fyZ=_Ng0Pc=-KaNzz5pa{X>U z0TIppFmUi-330hwqdQdPZ4eA9_7XOFRxQ(2EMUwp_hQ7vsQqGp$z`vh?*~n79>M|O zU->!SGe|RmcED0z;|4a4NA&pgDPa*cAs)WW;sBYIm6lQ?IJvn!KvID{+>z#92fxL<*(kr-=A6y z9(IQfKD+MY`a>rUUE6l*@`4;~$%K84_e-+mRG0L3a|zyfSu$aNSBLyNAyk+<@=ewaTMMjbg_mGodGV4Wt+>_MvLVW~e#f z7yLea9~85cloa~Ch|s}9hCoo+J2)svOM~l@vojnG`9`_C0KCw)6tuAVJ92WGN!b9t zh~q3xMrCJb(|=urHmkS}ZC3n};TPerkkouk2YELE6onE@sRn_=Tu1+jcnb9rVUB?F z|3NoyqQ$0n^5HYN`pmtBAWY}bx~g$WUsYFcL@kZ6H&GS6ATT!~bZlax%bP$-U_8fA z<^W3{sUY8Mx>{S<^Ghzg;-3dcW;{0Qli)r;qd-o2nQ(8c zGX(@o>Tixu{m!gehO8@=lo5+6(rW7rHcb;+*ki|{ie#lK;2n*nu)uuhf0gPAa7|<{ z{<6dL^?4VHb3VHrNRrrhE!V3=5#^SuhC3#v+1WPqPI>`w+@@)L_YJS=ix!NqqBA7? z)-W%phE<}o4{-L`80fXUH?yDtf_UO!g^YPvgP5a%TYI(sqv*&Bfo-0|fYi-{Y5Kgp z-XM1_I|9HyV8_jCTRv;(D6vlWEP1AA>eAK?)*_iS-2dnoKLW7D32FPQ-@q?t9ZF_g;`*iJ>N{VkuUzBKIKf>}3?KRrEXeL-7 zu72ZHMMX0y1h~Ac6Kn|~=0k6jGi~&_w6jbFm6xBqdUXj0vATM!sp$_WUf>~NKY+6G zi(HQ=q+sM38=EpZhmPP-gsNi-yZkXd0r%IQ*z>MzPSNe2fy(MmL*!S8R9hD&%shrT z<=DNX8#kzLlqX;9*!T9oH0i&7rBAYOpPQig# z7j+N-xSLzQAT_-~I{zmA7P}51T+GYyRLN*LITdgHk;wohIkmxwmxi#be7c!xjuXGn z4hgcafdSjN5GThiiln}xaGRv97!WYWfVJ{$xX#JTswhrH)LhK|DC?9DxJmdfz~ zy*(i&ok(oyT)=1_zJ5I#6NF4hLI8J#hCBAo4G1yn=8CTy%@0!{qnV%uj+~7bCwj;O zM~@161HKSSML|m5uV3A@&2y<*%1TOvxjgM0Qa!3NJp%)5OnEB|FI>BR{a7ik@a%@2 z#pqpTW*e_Gdyj$DJWL9iGMvf2pSyR@fCOVGLnLSD5Oo^p#N`VYYPpd}Iw`lIBp|$a z(~V!ggu&Xg|ARZrl%_aLb}Wh2H3DdkNwO6A=P*ZCuAt^mu0XW|erGtJQoK#+k4@tdel`Xxdn(y(H5Nlq^pgH#%+*P<3?TB6ky|1a zFMTwDehF1@UU8X#m@tSO^m<}_vK9^z%E2q%Lrz6(RR{7Y{;F$u1;Ro4ZrBi0l1X7iqi?cWv;->?>L7{dXY=!w*BM{4x6RI# z6+4>+qixqV%}0gkO?OFItF6ZLigA3@GN}oEg0)tiV>)}}<@)wji>{V?=SBNYSFRm2 z&wPrBKPS@fU1ZpdW-ZyCcp@@-2udmHF{Uej!*G!41p^0T<0=?lg-VzxVHMi5W*zA` zk-oO7( zC@Vfx?kQ5WlasOCTKK+NTf;vznU$N49nc%)nywz0ydN&)(uE7iG4xVwMP^Yno{97^ zU4~jnQbc67fqA8!`3Wo*8_rs4zo=Sw?$jxK=OULT!>i{{cXY~p0`?-T2iHvzhE*?K z1mmVZ^zd2demAM0eCE(HR}(W zM~*6KjvivW{Sz}aou1`3hC276@ zkg&)QRw+mLxw0>i2uPv5V8p%$?@^))4Pl6kz6(DLe2a6#h!EVd?NR%q7E4f--CO-F zP=K9<4OWN(lvLh@5T7})R4}A)-d5G3H{Uw5z+3<*Vs~F;LFJY$moOuNO=3j>AT*Q# zC<>|_(&*y9moh3j^CV)Nbit{9+O$HDUzCj8H8AVq-{QMJ()_zCVkf@Qd@z62PZ0G8 z(>KSR-Qekn^8D#uw`0z>(u=+j04f@ISz-j?Fkac{+6j}lhO7s01-P;SSV~#EdIT5^X3`i14AYx zW-^JoR}@vHwvn9UI<^#bEmE9^tyxLfz*DIlVrNtXBIs}gE^_~V$k;*dBRppuE^1S@ zSZJM@Vx7qoddF2_Qi5|Mt}5m{k9vhNH&>eZr(BEzzt3W9QfiHMET4m1j~~mRL4r(~ z0Bk&__jv*oX$O1?Ig3ha?_U()mwfA9*VR4n%zO7Boqy6RAV-ug$;r-s1F%8Rr?UEv zqmbB8S*a1lCm^YCw>sk9p?d|g(9-+V<;w>wZ+yl|XynLGXfDe(P;w)D;d)=l&9%I7 z|E>QTlS81k(DxW2(E?J=F?mY{99H8N{d(t>D}mTMvJ1DPCsIWaj)D2S_#7xW$OF*u z8FD%^R<6a>NZ5$#J(EN}g zJuu@SMfsxZkS+u_r#M{svv>E2AAhdR4v)`rTg7rBEWe*UW9%;66?_go5YMJ$=DSaQ zK5n}+K4z?hq2+qS>Ug} z0lOO<(JvSE^qGJAr>s-iJ&yEb%uqp@*~bDafi(cnWJX|!;^LONfA&Rsn0Sh1hl^|h z55nA?!CJX3f03yenMF^>JY9pIIT3-I6+G3A^<@PGhwr?leY!+_ zlBWg^i40bvHZYi=eaGFH$Ys2S0(SbRgwh6WPtxHDM?*Mla5hDIuk+8*%TT^B6a>+Q zDh86*8rVh9t3~$PSVLB%;34L~8zQm6@677B+4%9_&QT!)(g)onmXfHMDvj}-kW`K{ z^GzZFC3=m|58zO=KpZIQe#+Mm0YQvA^M3)2g$^}8U-7kP)R-+y)=29^WD8LVc&8yZ zm~{u}km%Bn?GWU4Tqtz#Q~^neiS6y}MoTYLym}Ra>Xwo=H%v#o7wTchON>r9;^ zZVCPU+06DKOOK0K1%wL%T@SIVu(#(oy)V0!#62pBpdeEr#9=5v6n3MqHr~0jo63tn z(y-Z|-a%~jxXeLaKd;}8OfMNJ_@)$P^7D|9b6rb3^r#QWn~+-gu_#ZZeFEd=2UQ*3 z-m-*>zDj~z#;w-wWIgJ)&Mt&IdAg7Fs+hc0CVmd_++kOqS$j}1kA}%%9lJI|!bRyGqu5diMzup-mv1*3mo5)qlgJ%^du{BE6 zR?eY_LH>~==jJZzudFOs{v&QK-opiFrKU(vAt8{4mmh=2>>NZo_@hv!fox9Q*eEgD z-1rv&s`lhvy$>$YwWCmUH#XjHnNuFe46l?%bDCIPuX21;cV}=YupD3MwL*|2I8VN7 z#Y-`%o9U$};Q{eb@cv%bU~l$Z=c=GnvXkG?CtcPzs@Kn3I< zg8x1dJ<(hBPlyVJv+?<^9e4dX}j`#BP6x-R+ zyI-q2dNS3%PU^1DbcB!)xU|5hGq3dtzK61!#dOaxO=ef`w+GW$7wZj*StLOXgm*sY z+6*Q7l9I&6wZ)w7Oc(=nfb23Mg$&Md-+w5(ilVrByp?jT@R-9$^!whi}{Mw zLq%kM=wH+a4~~{x>F!=n=L>iVKTRVn7}IcJtP+>mELZ?yxPX>w>C$WeS>TuKTY&%$ zTk0#p0hi1ND>$k(FOOOt?x-+QqGX`qeyhL5U-?#O)z2xO?f)=)Hsvo_bt)TCar+^s zqyMLOYy^J<@!=BkQz#sw=F40}i9X8aSp5^x#)x)lJ-y^j+j|~-mft*Lfvux@#AuCX z#hP&WvXjyGb#kn)q~wIE_V51#sB_un(Nm{}Qn8>+w|X5vMz?5jZ-#SHp`d?*d5A5o ze)Q-%{8zx3jb6sd;ko4px%1&KcMJ{hvayMyN|BDpe1$~Q+0_+sE{q+pFSQ7nHb(C2 znB)865+%M4iA!h0dNzsz2%;)_Kq@vb!cL%Np|=-7RIG2Wem#oX|k;<^R`pG3C{Mbj6 z`H6eg)o2of$QE#Z*1c(cSDECC^ zaRxLdDpvQXs+hL;CBv~w-79acU^s|{+N|xNS%e%MF>)mJpuXXUujv)8cG@GG9;Vo4 z19GFL6EvkuFXW)K!b*neV2a!KZGl`oJ_axdYSgMJ)S>(N?rh(r*2Wc!+#GFubnK(7 z&V=hR6D7oBS`Xd)Txu7`oSseD-i!>zhwfM0vZi&nKY|2qSj4LK$CQf#R6{<45IZvY z^08wK(G;FK^@0CNE4S)D8$^;O`~;~Vc{IE?%T#9FzzA-cn_JQjKzSj8shYtk*Zs5Y zJq7lLoyg!AkwcCE$OOV9ESAsA6rKH?E5VBvH+VyS!qJ1`DW-)_4`6AzGw{@vV6&sPU-@_5P*98lnnIsb*3=< z{eNUT0RNfQs|^m?v9wq`8dPI|L#XTM1DIH`%cQ8N9?FViaqi3+Y~e(Y^sbYyNctyG zq7S(3m4t-bs2OJKIRl)KdzW)@&YU48Zwm^lx)6282Hu`!D9pXQC=8gyXB;wV44nW6 z68KedJbN2?G?7)~E6ljDcyjcWljG0h5Y_dfyK~!H*E1euL6AU)dGiD#O6qpbyR{GE zz}d4a8DQ&(PPWx!1t(!OjNHp+!p0(?ZMU#)rmYPp zOxSnlEq$WXsmCaUVKDH4VO))^TC|(*zon$XGOb|CsIhVG#f$r99`pFzdv0!p=WPZ9 zzW`(e$XRCW*l`DtC8Dms&FOA?$vv#10!%V6K;SJgY6rc~UXzW5jC48OMqjpR<3`?U zvykczVaQflNrvanp;R{c;-OXHnTM8`Mz%R%Qfw*E``<%nF6p6* zhk^TDXN4wJ86u9g@w~U#NrOeosHN*I{^2E&T{u=w-zVcY|BBw%&MrDm%sV}xL~~@p z{I~F>QDO79`nXkrw?BJ!pmk`p);%0BTfdUz2}+=>^{4P20&?s4^{WS*HAhr<6tqiF zi)B4k_u0lfG|-xK7fQ*;+bmkIyIQa{S2ohJI$B#d+&p-_%q@3g+C#p!xmBzD<^!P8 zLq`>}kHW^CU6LsB5ax=nn1cYyYq?X6$NA?FAvXJD3I{A+#z1eqqjFb8*w#xnjXMr) zVIy+=Nl+~wTXbnH<5Ta`usqbu^ZY+>WK&am@F(_5t8F(j`lTL6nKywbJv40G|> zb$!uUR$K-dK?{#r(GuPF(?_*a)t1dg%vRa`$uWG+@%-m4V27VyU8lKEcDdYo3Y%4) zQoOvd=L#{dTz{kgyz8c_bVUqS5PCm{lYj}riZaK`I+q~y>#l61YMhR%S&1G~TkfEp zuyDIn+QN|$;_N4@A$c-8JABBzu8DE25^H4!={j{$^0k3n3)*kC0Z{;f$kAOx`^Rf& zY*-Q$^ouGFqu$sb(b|%q3oqJhAGR`6d;4#gpJC+W9b%zS#~X)&6>{+Oy#0h&;p%Ev zAj~`1+G^k4EiWZ3P$_LtOzPx?$(8H}Jah^*2`7b++bkD(wT6m+*ns(1F3m$KkW=by zvGJ;F@%y)0-_{MYunC^p%Yh(oFbRZmE7CN&x23+$C`)$#daEn02z1G-=z|FK1Y|5; zw=2A$Uf|2XG!*h?wVf-+{_yxe9Eek+pK(X&9k7E-QpTg|6Wy1XxHxL9geX4ZfYp0e@t+FB045jz8BCBuA7Z=APkL4v~+ugrmH99$6ZfOE1=Q zJULR;t!w*1HYgLyrEB|nV%=`$_Od62gPWp&LKb}kfPwG-t66!xwzb4-$@(a3r4#rZ zonA?RgZx<HONa z?@3LKw24Z+5xsuy3;TKV7-WYRi!4}0!$kfe=&+fSav_JG2H`~zWv4BI7_d9bFVCv0 z5fKT?V}R7MXYbMeE^}1{A&1Fw@F<`!YJAoo#oHiplA5e}$%@zHHc?peVma9;y<0nj zZANkmII!M{99c>hV|95EAxjXlEXdi3*+4Bc4Fqkvd;%S#M#cy4(mGIYy1BW%!I9xP z#Sg@cCA|DAD)3G<~~zDlEXo?&D>^E^abJ6BHO~a1G^_u-5%FsTr-6zy`AbeJf6H6PA(LD6NK$X_; z!g_oojZ*N`Fw{jz0r2J6?Y+c6PFTR=4t zC}1LVmBY$g!;bX8of!-Jb}xlZL#4yST9*WT1k5TAA1dsE!!U_~;XSY|nO)LT#s?Ey z#e%|NH3NTfY{NFts+VWUuE#N^fXE2wJ~%#zoL0V{N@KKAt#mXX%uS&!Mtn&GK~qauERz6Q!>2*o{-(iS3PegaH>&uap`pp4dZNdyWeNU)Jg1Lx$`-o9H$N)GT%M;rBGc0 z%@Y2}cWc(JMg2>E%it}Kl|u$dmvZMVR~LBt`UOa-+sHP!Z5^_DNM3A>7n7SdBR`2G0z)h7}b;JvpLuE~KwsTj1*)KWrPy?1(1Q01F`%Nd2LtsuHf$lq{lCT zmbunW)2Ts$l4pKmIjn?^Ge))vEij14113y(Qc;nE?=y*c<}vs>+DZarsw_P@!XCbO zaNttstKj#I6O5a|@u7Cv4i+G|w=B1^YC!V9*`YvO_J7Y9v~!CnlGeX8WjmPSUd+DO zs*^YowvYapUo?gh=6-0sOiZF3YG{%Fo80TfRv>w{qajN|MF?=saLMfdN9Mj{?jpBza^@$mI6MCOuJIktFfXWHRG#baY{ zNb4L7LCue@66@M3YhsM3cnSs%i2lWyuzFcr%TC^mtSljFpFA1sZy02O4C!-+z z9w5bVLiAvv-QNW#@7(+GMqXmAhs@1N7AkyU@oFtgXmP8e(N5zhaFdRPX}@x+LjWl* z#~yUazU%oaQJ18Oh%VFq^|@}xueQO0aFhM8v9T&&*fp(KX{MlHE7usLIM`!?WKep2 zHF&DkDBY4x?KMQ*HepvGqF~Uw>1P?OUd?y1YH{% zViLR*J1%WSohINpE38zclm)I^VzdZ?YQu)n7RNW9CyLv6J?Qbk*U3MamAj$9|<%J~tb- zhhCE<%s?%CK$JEdL^c5|PafO#AizrU$!^KY?ZjyUx&M|eQ-j)bAOolqdODhtkUKS{ zG<@qs`^9#kbo2|T$Y@z%RIpOGwBZ#MCQ^O*^Bu@I_$VApmLMSp;u}RS<&8 zJtm832bpPt@o`zQWH^u^g{Gt=UY&mCgQ#sE>DB$Dc;iNlO?Ih}{K9cO~eIq7E zQOl$6ty$V{Hn4*E-fE@}K2wkIG+MVZi6$}=(vO%&IGaA4^d&SSP!asg{Q2j}EG1L; zvfyV4N4zd9hAN3&a{q>A}_Z#FAXIWTEQUSwn+cAprJK-py6IMnb^ zChUB1g)DaW?Ps~pIF5=^Kt{0%*VljB(vn#4ZQb?}CaOi3DAz-Kx+XsLm6Y6$l>-f} zl%ynVH9SJ(OumvS=H{p@Kr<;XIqP)=Tc@9V6mgqf7eKn4_43Wqeft9WLe>cyi~VZq zvBVF-FC zdgmKnD&V%aNJDgQN62W4ZY(>#Elh-hq$uW}%oxfBaVsQIbOJJsY3nHK6w?L2$-a5J+5)|rE+2B*$EhDc=E zwjXxLYm}b?C6VK|D>(wKVbXXd%0iQ=ifw*IDCBK|*a?hugYh23QT!9Q8><+)AFC?% z0&<=s{@Wmsm740pKx=Gi-ud%XE{$!0;2r#;KZV7lQ45$fWyAH!cBj%a}6Em55CqOhmZZ{R_MCN8WO6?qngC%`)e4~&)HM7Xt!d3m#GPd%}CIRl)9_Jxbf z(cW#W94}b$5>{}!Ja%ll!5$)Kpv6&62KaOfYk4LUkLhE#TYKcWkjyBjs+Og&PV)YJ zgaYM%-9REjNyGQZBZz2#m3(u%_XmNQRhy%s|xXroANCP;8MdoP^CwIih1L%VQZN;zg z!F#4|1QQ9Gj?xAnT%dqLOB-DAOXo>Y?h(o3=1I;lk%&L-_H2vvnG=Ale)_~u(Eg+y zJA@5@0Y5YMC467OD)2eS|36iNTv$*l9bAH&FIX&MPOIxA1N=p3+yiV-kbo}mc!V=V zxkr~L*cVPQZItg_70l`Z_D8*;pks%}{X-MxlKhE%%%-EaHMD6Czz0?53Ien~by~|q z#1eZ}{ys{3%@@rtv5e@}@$4M|m(K~J5Cx^=Lx@=+P^9_d*z)A~V+`jovF}gEEORr9 ztfuHIBRZ4tJ7OPqR$Q)`tWQ&Kmp*klwi$#QK(^$hIQJQJ9a2MhKYVzhJ0Co_{oA)F zhcC~54(L|qA##wwx;QiFOr{?lWo)b%vQA@X%PEgFw6xDLwM(J{8Ahk3PL~x$__%W-5 zPCGJOFsnv60>i^F_?|)8PdbCACLR>&?maQIsc_+8RaY?N0wg+F_gFg9eh6ubj= z6|{`_KHmU=Mn%G`Qb5!gRe@g3)0q9vc6{H7ZD`6ai~YtjO61PMY9WRHl|*H;+4ZK4BT<>>&Uju|~#>R`W;(b^ia3I3ULX5RfGu{o)JYS!XsFJJC>EHR^=il$^)iskl@ zO=@}@-ZF(tDfcgWuubpma*s)in_4|7mN{)p$@M*$8>CcR@W#;7_eX>;^4390P0yp&4{;C~lRJY*I zp(5I40X8PNl40g!b?S#&f1H5^{#Q|r6|%k4+QrgceLmGaGmeDRSy{dd9#D|MT))oZ zg@SEA70iY_xL>G0X;P+)wX4Ko58f&I5IANq9v~ZF8DUWYtZvbKTHcEQcoY&zgZAF0 zyUg$*iLgY^^*(}tj9liHe;jHn zuF|!v(Y~4&MEP$6%!yeEx@*2BU>E|{v#P38Y!{%cd2-+_h!ap(3)=lxucn|XL5zgz zg9L?)hBreL=l+8;GSG#B4hcSLNHp`b|A7Ovbw`dF^T+w;BvyI{f~V~>x@i%Sh=mvU zPeA5?sUOc)okpkx;GFU31F#@rwSyA*6x2W7Zp2@PpG%l0ghDBM>yP?m0r++RgE`zC zMzvVa1mH@s%MLW&(cOv)nrsS*7}I&2EQX-@K~x6xz*bhGIYSW+a_qu2fu<4d(zWR| zwNA#Uh-F0ZwB=2dij){k*MKUj4;;uwYfEta{}U#j;|`)A30xqp)+=)-vHqW7j)$Zr zEoF(u#Do|5tL9Q`W7h;UM%oHbij=r<^yrF}E1OuSnf}0c{d(#V4`X%u2ivK@Qozcn zL1+_6ZPdqna!MiKbN(G)e2NaJ61l9O0C;)2q4P}~T2Emz();0LIzK|txS~Y5lD-a9 za5?H=bhMlsig_l>KddI;d^ePz@0r!PdYc=L(83UAKD337S2RZnUQg)!zJCwt?U`~* zKB^cwQTPVwYr--Jn#!`do^+CYr&x07+^A-Vn%ou77O#MzRHk_9DL@ zy=hbKe-WKPZ~B8<$2LZQdSBh^M7p3!7Q92B@i#Vf5K~z(@$c$5#ZHHjZfuz<0_Xz8xlgk2tttP83xf`19W( zWg!59zIc0dg9z}Uk-P1uvZq1cy<6=3kW4a_?~4mEy3rKNe1+eB{&WMVW8E!*gFY6f zT)P_)(pQoii%uTTq&-4ZCoBVFN6bXQv4gxo2sLh<9)lNSzsv-p7G=t}&fwhHw7S&G zV$TM3Z6$;T7v%D10c;faX7HS>jt;IZ@H2e;{F2Y}NYI_*)E6e464~_->g;@>USNZU zyo(jz*H-3usSPYmCgLgnrLgzLx@V(}LcuWUTCN0r7Y?CS1xjCS3zJr12!d-hk!M`+ zh_94)%&KJhqjM?465TIPZ3W|v9t&g4 z^fqX<=MX)@RV6D=v1G4MZqSaK2|IVHy@}8_Fi{5^^e;=|b` z?_N^STCNpl>64f`hXb3W(L`Mbg#?L*ewmn*v1_JzgMEcPm6?3SU97$d*b+%5CCA#* zmQlbn#KfV4-!?+>7{Gv^ALJ0fI9rj{H%N=vboBk8epQ>EUPk%O4N-2|NK z!OJiC4Afgl)tREIJVJ1;ag+Pw*pgkl1HmEA3ZBcyjt$-T7tNT&z_x7~t9x~3t%tV2 z#YE{sY1iZnrmf?~4pI!Z3f!WcWEUK4;o%W?a;6{W-??~}_}3Ek!)1F$Hz$D>#+^#4 zAAnv=de;ht*r63VJq9nomOqS98NCMi68e9|Cd4`oh1PXl26rZ6>Ui(a1R_4JG&F9i zmVw3AdGgQx;Hsk1*6kXA6qBroWM)P|!N#T-7N{X=vdbOM`pvjXsAf6sUN(#uoCp0y zG!psXC*QH%Arm&xO86BLjj`4Z+PXF3+Y1KZ);%?<@$>9uoeBD8hWeWNPYY_V;2VLC z?}?ZY-sH8|Kcn;A9gTvT%84BB@hyC4qm ze=BR^OdZP9I*mmkPB2Ko6k;iTFvqE-_w9p+LQG)D7~#EplH*y&c!e?MaYy+OUgHG$!HpY7@4Tfv;1;sL zo$7~WFQ9+{fBp%d1#yi=n`{R$>y8W-anAY6mml1}Z&0>JF?XKGTICz2Wxov#!rz2@ z;Q?@t2{N|eVHySd0v5^T29ASjm_A*6gk24J^uP2+KU6Ceod1;- z`UmY85XNKyB|P}sK18u8wh;W`mH=0bmGWeO9$FUSh0Ky|UO7bINN1v7bPVY>xA z5sy$_YC4cLw-YGO*@b$~QM>EYxk8>EU&eDE9CbF4UKzuNx#h8FACs&<&cN%Q%DuaH zWBm(g26rHSTFQMc=(WqDl1Iu*sUz@sk>6FrEiz0lnWz$?YMV@vAD*#OH5cd;|680Y z_MGE=U_p74tu1eq%?cp zyjgBl#l?8`@-u_DCD6yQo7?z*>U+m6QpJ?kiiY91g zu(f}mpFer=;%1JYF#TX$?)|6CqEQk$dnJ}8HfLoXiWm+U=)>>A{}XgwaS;iw4qqtR zr0EEac!-ra+yX^eR1U>k;eJfVjs;y1?Y|))0JVhMf&~F&8bMx#LyfYw#m8wmt=c^| z*EW3kzyt^F%1Bmp$Av&R&P(NBc!>4`BTbT_x!c z+?wHj#6tPt4ygU$$&qegL#S*KOZ%0xTICsA1s^ks4~k#r5eCPH+8U@D+7pPe^l>XD z-*{I>hE4d-p+|CWVudsf*eD|-lyb+|>7=Dq2*b|_Wsb~u|IGNEr452>$&DNA2fqjq z2}WY;$5&NH`!R4}yun+1xlTA70kOhdTt!Jq5H~$@3*!%e;;4{7*pe8B0l?k61+2+n z0~DzU;Evfn)ZZw9Mipxd(knhFU?Y85{{m_LiK^}p_c( z2RM!tlddiho6qqWp?G3{=p*HRZ3to*EY(~8*fTtOIEuFItmw3<0N zwHY7urGNh_*8SP^87U2i2k9BaPWR`_b%;+2Z%FrM$lB*7E6t{=sY8bh=75_on{x>M zL@E(GbeTi5c&y9wJZC`g@gkWSzZn3bs}ytuuX6ytsC<~J5NvY<=(@_VEgx6H^)0!8UA zg*4W5X2R?Xt05>z-M5t+W4SOd2H0LFYu>otXuP>WQu*!G^^-qNg5E0tj z{paH|5)CsV0*5KTbDzc6r zmt3h~6iPm!M>UhSAD7!B`TMooZI`;7z2 z;FH#WmKGO#1P9~KvV|SjY}>)a{-!5TBQdhi5i-k3M%%=FCFiv08W~jJ-B!8 z2xIjRhteYF^if%0p(<>srg1U|>;vRMXl49B=m3u#xwrcK#*Km)1NAH`KmU^mSxso| z&7FYqgvi8mj9!?m`0{YZMH{ZWT$<^@K(Kl2l+zEt>8>0S%XTiTxp#3HllT;{jRc@t6XPmDeVV-$+ zsI;2i*Yh$Wc}R#r7(kpKmy~1ya7~?h)I|~!ZgilZ#5uz;qP~WDdJCAcVp|icRJYL1 z?fm#r);s-&_`Gaa)zYwNeXVN#_EZ< zcrZ4)WXW>?C^9ZIWa)?3j36j_^ZzKrky0We$nh2P(ZYKPPVwPGWp#CgJp-9qFNr~n zjYg3moR_x|TSHhkMsxU&NG|tOA|4R7?I6tk{vFlRzaKt;q(ppR%cChvmsC{b~t`4o7&QX50;nWvotw14E-MQI%aqFR_4xTUFHxhRzr&z!GLluU}8)jq2;)EGv6M z?8~vryi)%D8@f!i2?=Mu0T|@EfPd+y*zzj8Kkk;Zb3SK@DjVsu$SQ7(`k+D3^SW1K zj{M>aV6h7KJ*!{EH;xwjdZw2T89yc-~%Hy~4za#zotdr(rMi&z8b*v{a9ov7EWNIpRH!Mi!> zYHI3QNqbS5;VV3y^gM%Y|8AO(Uj(0=th<5U5RCzoThwHfZSIPDY_pO0XzV&K|*(K&R=t?YyT!#)}^n!XoaQSIzfi{_>Y+kI}@T|69V=w;=g8|F{ z(zzkh*C$Y*NKDV&sv900nNuL6n6G7bh3^`umU;$J9<-yP<|WP-kP|}9E+z=LLw+G* z2NM|~g4ny3AE{mq$Uy*&7TH(x;zdGyJT)~BXnA312%mutho+9!SHi-IJ164YcmuMZ zbIsah$Q01xAt6JNZD@5#)Pk2?L~l(ef;9)fsiYW;6eS?VX$d~m^d7uf45--L8QU9! z42_`2Bu`g&HxZ)#VPYRs6O+9+J%!o%!xQnZZ%3_Y-iJhwLIZ?2a$fu+;{BksdnhFV zK?tZw3pBK}5S=Ry7y!|;4az*`u=|;BTAuAlGD*Ceii+W`U41I`2Wz&nd~Im(8=?;` z)Y`w%axcNG>g(jaeTc7*yov)Iy!8ym}z zXwU&1Fk14JuAJFX>)tPQbFNp<)A8G#^E~Ug*sLO`RqU11AG~LqUkB6x?}D-&uoTjt zm7*A$f_D=!|Jm3iWip-hf<%FXG>V507oZM6@R0Ysg&w3XtT%)q5qeW{AE}+X;}YB2 zWo4nbfD(m`*7zbYObUX>*`|hb+Q$CXi0o`HmOcwo<-eEBI{63!lN~0k^M1!f_v+QC zY3KXx$3-4fP#Z4}HFz16C|pw7fdl2M&x1O{4A>ucb~@c>#_gLoAu{&e^rWFgWCP3X z-V1+-s+W;@baXh2HP5hA6!u$V=7z7)m@yAsDTJc51186!! zyKIDD;BfWo6F>&p*>4~jK`EQ*DjFK{*yBW}#V_=5sR4O1WL8iH?LQeMAjJEd52mCukpcw0;yT*Mgbc{|15?bx zj#^x0!w|G3MG&8a@;%&Fsc(slVuOs+c%23F%K8Z41*SS|0AQF2oH=7T&Zy;H<1!Dh z8)_&kl2)uZ%hSM{h<53(zbKFmM~pymG)FguU`&bj$B0O-rgttF5Ka=fNW%6mZO$#}FEFhM_?y$|>t9 z`9asVY~IW_*#nUfz5&N1eU!hz;>{IAF&+l@n#zPc$@h`f`-;T_a2ZG+l+ETYT4ZWu z)B?JjVZBmZ?;I5v(|b0$l2THQ7XZdkzwkMy@hxT9^roLq zR^Yel-j{sONCJkd$~K<|Ge@~Ziq%t|jlv6|u=5yxJLfBJ(b-Uy69);3W?}bkK||tSmD3EP8#PhZc;1h8R0>YTiWITJRkoeL5aNJB?U%G`7kdMTPnm*env%(R zRsfxuN%x4%79(;&w+ht6(Sq|JA&^@-DKk;>vQijgn^K^c6VJB2tqr6b(@dqL^2tO@ z;v~HrFgufG*RWouyoGLIISj2$)(fsR&IWkBl)nGgi)fM}LLpW~O>H6F2N5AXtcPuF z?_ssz#UTrM{pf{%;1h~wUKFmd-?ezI4BCq$wixxYM~xJ*r*iPXfuu@es{8rpgBALW zl`PZ1a5j^ud``J>+7nk z`XvJkhPyZqQ0-shHK%rSg{RVaz2K7)kA`z5E|^pmqZ>9hF+rvKn{Sq6LZykif6-G??9mjLi6z+16?wrB`gcrz6k%so+j~XoB1Xtb&pRVy9m}_U6%KXH5~g zrdRB(cV%#Dl8EPS>~C*~e7%iOiwcg2!=bd9`m-rPYbw3t7k2YiISg~ySAW#OQSG4O zqTadYA{sM_CKtVydoQ?vQOnXOo^#0)J2ctSmb@Dj7??53Fz0E6we&`pjuk8`&&bQp z%#1MJ<+!F;>Wsn0SBkCNMVK-akmsMb&_@-Dt67D}s3{ec#YN7{z);_4S+xfJK1SiP z)U9P2fd{z?^3V8(X&QNqxx?&?j9i#x?k{0d@Zg=cga`e)xy(6sJhB*UlT9wpF8gWbt4ZE;qgl*b@Wn&`MIrOrAJ#pu;=#DAwvw z;Q;WN1yOQDWeOb;D|eA`tt*|3W6fDvCeh{Xbncc{!ylRFG)Np7@^i=;o0fU<@>A23 zS|rC0J8JI4+zYkzxPu7itWG`>6^WSnH-=!8c76Kv$;<#mOSIW-XMmF`P=%>rLOcui zYt?vAdN$Kxsk*1~E)lt+;!3~=XT}*rQ<0b1{vzg)eGJ`lpZnw-#%=4bzy9)^SZuP> zL#I7##i~e+i#Fad)k%3fW~8*5&(Mx=C&hQYMG33s$nQIw;#1xnO4d(@L zU5|mT+SJhpIH(`rypd2=W4tLRhgHdB8tk|kD5mWvJFCh{cC0yUanSDQq|JY!5l3ah zT@BYOF;@0@7qaO&%ic1oy@C4xJD)yzqCe?JYCxRUxJ&~JkA?xku~%-Aiz&rjnwY?$ zh+$JK8PII~ivSd69g7%W8Lys5F9I^kHW6!aZ^nropA?JYxW?Qf0B(H$`n|*FlO;gR z!Dj#$7%8mq%7^&8KX?z~9S{C17SwI0DPs6RnSmH?m3ig?0M1El(DH$hhe-1ANv!k& zLPo75o*@zO57abM0iZ&KTfbrL$6i2pRM3{A94FK4!K}hS(xN+ROUjp_c!KSt^G0=E zR1c;|yN5IYxSQVMh`A_jE$^Qe5;JqKk+Uv&){dG;#8@*|Qd%m0^IETs)lP?#FwS^L zP&7(^{5$L)MP&=w1sm8J$kKJfb#x%7h#^DjFV^G0Qf}Nh*2o5Z1x5^2so^%^J9fy$ zmQw7LI8tLql^vpKWIYkD26(Rd5PS~FylpTUS*L$=t(s!6isj#R%L)p2y*IE>CCjGY}8-W&!`iJ7V zcL@0ro0_{|a-2Z(6_-8~bgwculMo#Ae8LD9}*HqYphW=lH+CwmbkZ zcd`wKiOb3$G3S`O4aKk=KLdzLehTCfBo7*+Nnt%*fcA`vFmO%>ifY5}YAu2bnoWRr zG_GPzATQt8B26(s+gMg!&Y&mHkBwS>kyvK{Anr{|n+%7^hXx16yFmIgZwLDy3(YYA zuoRZ1pz#u^yuzx=&2n@fHQ5FmV+=Fsg z319-bn2TESN1h;ixjK<*niy?z2q6+J%avieIjr&Sh00d8&pB^B#GH-iexhZ*I-@Hj zLUr=DSiR7))+yNZrb3Cjn?q6~C%0m2KvVn_sxav}x(WcSa)tk6={&%>?%(!rWhH%$ zq>_lxmQ*TtMv^3{R4NHAZK0B~6+$Tur9x9fXiCX!4>VEP(ncDT@qc}u-@oHIe#iaX zgYWnAdB3l5UgvpU)7E+ffnJHMG^*a@OdYJU{qWwsj239_n#K(Ov2EwdB`WgzzT?RC z>rwPjnggp_7Vmmq4+^}ollRA9;u>uccOD9dCb7f)t~}4i{71#@t&*=lA=AKHG$K+u zn_VkuymC$^OWj!_wVZw%aqM(Ax8$mxPaZ!`Fb~h9nLI!D0&Rd7mR$6M#MF-dw(mHV zw5@pC-NHxoR7uFtJo2TI*NntT06!X%dmh&dB{t?P0vVfhz?haxs&Gl5w*hJhHL;o+ zW5Qfq`q`lNYAme}v{R5WF!#lxfvO^r*gg&IFB7UAnGH~64t`MjPo^IhuAa4jQr3pd z-+8}Q?y|5nOm2f&^xPWpyli`0+x*c{c&-jIGfVUZ`GCf?vS!ep`;e)XG~VLcq4ZM+ z<{1h!?c1Z}uH+6l+BD8=;A;I+*fXve&<`ET|U$L`K|ShcnjaJtU(IFr13r4$C``pN*P{TLgES z)ZbWkMP{Ogsc$h443d;dC-o;~?APjQY*zc(JthLE{nUZ3wFM&;3Nr$F} zRDCctHg<%#Tv6eI)ba9a;7H8OckZ+XU}W?O?~RIv7|#3C=+}?N)pgl2si_huH%KzA z#yFr#%%n=y^RO8nFR8R3#MRC!{gJFz&6^aJuoe$3oh5V@hxYEJBM+WehE0)r@W)9= z0OIx7x`QVoBYG$})#sqz(aX!U1wUO86%oO86vlr2Lh1$2zRGU9Jb>})imLbCt8FIj z7H@OSaB{uOn>cg&bVu|30UQZt55P2{lS*rc`Csuh(vFqVd-3@38u;xXTyIe^BI@Pd zIGBq$#Ii7a%out|=}^4lz%E%)o( z(SIIuc0Sst_wPIKl4DRx&0E;<_juLkH{DLi9hi*>m#~VCpPxtj&x8$KGCm$4zywaZgOXgqP3kDsT&g-cJ#ixJ<0SvdX8svXllIck0K3!|<6+3DymReve zAT^W==zMk^cKC}?&5r+m(LR|4c0w;>juV;X02|}X88}|i2q!Z$TZM3Ay49WMv}cp% z9Wh+)-fyW7^IRr7g3mi)VSn2NSn!fCzGOGQ1XZb3t&lXB&YAt-< zbno#5+3RN+yG^%O)qwOSjGhot^Z>ockHmR@TC zkD5CS`?HEh7Ssp)Z1J_#z0@y-{4C3nl(}oVbF`HXCcFX{lKj4nl~F7!MnUAq&K2fv zyg!N^ukS?t2!lexdQAhUkj zabgJP5Ur0`)863VJOYQdqBEQcNS14KU2U1tgDn%XcKwUGJRrA7SDAILi_o#4is9fg z7lj7NrrdGkfUV(ybSsKTN%4SgVVG~?NDsV-( zgnq*B)5l)pCr$)xg6rJmYYL+tI}HvR=Db=%pcMgmA-y34?tKK-z!IDgn@dSahD+}$ zC`ax;dK7+4j|TyOHlPHAeep)Aw*Iel>oyl_7s$)>nCWh1FRHPO-zje4_)unxhn56YmdmgWAa6P5&a%yn4cUw}KvwHvA7cbzn{by`^ z>f}i@151bzIJz{n;71X?xjN0tDcOH!Q0Et${pM@sE513N)WyB{Ai`e=~ z=BHFW)Y?K!#87qLzULcqI$oER!6u!RK8MG)hG57)N3J$0(F~RnhQ)9kI5q%s1q;9$ zH*s2qsXbHaRy}87ueahG9cg%viQCQI@D0zM+M z!~L?FxPt!*cMc{C9x7ko-Bf9}T&Bt>C;jTx+W>>mjH1Uv+1Dks8=9Ed4BYE^le0GJ zFxX0NY2VIY+)ilAfadTt(N%LX^IBPEpI2<9(2F7c!fezqhaUg>^JmM(ji0`Lr3Q_P zj&^i#5EKgZE6fA#tbR3Q$blnZj~Lp5_Y<~xNPHx>dCN1L?DXOsTo0$8cM~_C9{nIL z-Cp*`bE)Ot`ZksBGOoSl{8#7iS2QG1V8!!`pg{i<|BjbrY2RCGoBR%@>qrnkGiGNv zK6vmdS63}?54||;kkI2&iA6{Zcf`OB?$2h>`DO4IVyq_%D#P%ihJv@(F)q6Ik?Q#| z4^F%PG%;1Vy@xhjZj7vq=%#74p9cmM&tMBdDU0z0GgW{Qhoqf$;y&)PD&Ym77nWKs zuQ7ORLQJP;dwX*DO z<5nD|p=a($r7+`T6UOEMuKMg}M+pSLLyg3=7G@g2MPceRdwbf>Dn4KA>-J+)BsNW_ zsdRI5qkWM$5c(_VaXs&u8go0%9^f4@!*e`N0|^w%br13`gF~YpazUg!;WbbYfDJ|- z9asrN!MIHEav4N|Q^G>#<^%EwO7|JmfVsjTgLet-WZ1B^D_71Z(7t#Ps;1CM zBHuCeERTl`!!Xhd*W%HCW@e&`lM}3mq*vo;bVnS*mQ4`FSbfXw2o8*VWU+2tD%opA zPZF8up^O6PV`OxiAZ-JP3g=mMc>MV8E+&B(IcuMkRZwuGNB{FjW&kdD36{x>-teb? z{>g`@4lYFNhw$+?()cu@9&6)l*JzERTn^c@67-HDwW=!V^ywWX80svGHo;?EN~Ae1>mTXOT)+yg9o#8)`bY)qz4ejz|CYs(gCTJTP>dkxAsm_ z&(E6TaN1p>%3Z>Gi6}(d#&1wlOi(W*C8Tkq!*<+hk3i@*3~ci^Kw_LBP#)jJW~pmv zKxPa{`ww@fPoJpsm;f*m6v&d5ap-@1e3DMQWpmHap>F|Y_6Oqsz!Q$` DA)IjCS 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/ic_brightness_4_white_24dp.xml b/app/src/main/res/drawable/ic_brightness_4_white_24dp.xml new file mode 100644 index 000000000..09f16c1d3 --- /dev/null +++ b/app/src/main/res/drawable/ic_brightness_4_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_brightness_5_black_24dp.xml b/app/src/main/res/drawable/ic_brightness_5_black_24dp.xml new file mode 100644 index 000000000..54301c0aa --- /dev/null +++ b/app/src/main/res/drawable/ic_brightness_5_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/activity_reader.xml b/app/src/main/res/layout/activity_reader.xml index 8812abbbd..0cbec3f42 100644 --- a/app/src/main/res/layout/activity_reader.xml +++ b/app/src/main/res/layout/activity_reader.xml @@ -105,4 +105,10 @@ android:layout_height="match_parent" android:visibility="gone"/> + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_reader_custom_filter.xml b/app/src/main/res/layout/dialog_reader_custom_filter.xml new file mode 100644 index 000000000..4abe82fdd --- /dev/null +++ b/app/src/main/res/layout/dialog_reader_custom_filter.xml @@ -0,0 +1,257 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_reader_settings.xml b/app/src/main/res/layout/dialog_reader_settings.xml index f78b724ae..67244208d 100644 --- a/app/src/main/res/layout/dialog_reader_settings.xml +++ b/app/src/main/res/layout/dialog_reader_settings.xml @@ -1,9 +1,9 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/menu/reader.xml b/app/src/main/res/menu/reader.xml index 72121c053..6abc4e3e0 100644 --- a/app/src/main/res/menu/reader.xml +++ b/app/src/main/res/menu/reader.xml @@ -2,11 +2,18 @@

+ + \ No newline at end of file diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index 6af90d376..0d37df9c3 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -17,6 +17,11 @@ + + + + + diff --git a/app/src/main/res/values/keys.xml b/app/src/main/res/values/keys.xml index b6e36a54a..8d293cef5 100644 --- a/app/src/main/res/values/keys.xml +++ b/app/src/main/res/values/keys.xml @@ -32,6 +32,9 @@ pref_keep_screen_on_key pref_custom_brightness_key custom_brightness_value + pref_color_filter_key + color_filter_value + pref_red_filter_value pref_reader_theme_key pref_image_decoder_key reader_volume_keys diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ab4563c2f..1447866d9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -105,6 +105,7 @@ Enable transitions Show page number Use custom brightness + Use custom color filter Keep screen on Navigation Volume keys @@ -138,6 +139,11 @@ Lock Force portrait Force landscape + R + G + B + A + Downloads directory diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 46f79b995..ebd7a695f 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -91,6 +91,10 @@ 16sp + + @@ -107,6 +111,10 @@ 20sp + + diff --git a/app/src/main/res/xml/pref_reader.xml b/app/src/main/res/xml/pref_reader.xml index 995688876..aefdf7335 100644 --- a/app/src/main/res/xml/pref_reader.xml +++ b/app/src/main/res/xml/pref_reader.xml @@ -69,11 +69,6 @@ android:key="@string/pref_show_page_number_key" android:defaultValue="true" /> - - Date: Thu, 22 Sep 2016 19:49:47 +0200 Subject: [PATCH 44/79] Update kotlin and gradle build tools --- app/build.gradle | 2 +- build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index cca143523..b6344decc 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -177,7 +177,7 @@ dependencies { } buildscript { - ext.kotlin_version = '1.0.3' + ext.kotlin_version = '1.0.4' repositories { mavenCentral() } diff --git a/build.gradle b/build.gradle index 3f3598502..8f054debc 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.1.3' + classpath 'com.android.tools.build:gradle:2.2.0' classpath 'com.github.ben-manes:gradle-versions-plugin:0.13.0' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files From 596a24fce813694bc873dc54f361aeb02b72fa6b Mon Sep 17 00:00:00 2001 From: Bram van de Kerkhof Date: Thu, 22 Sep 2016 21:36:40 +0200 Subject: [PATCH 45/79] Added option to share your favorite manga (#477) --- .../data/source/online/OnlineSource.kt | 2 +- .../ui/manga/info/MangaInfoFragment.kt | 20 +++++++++++++++++++ .../ui/manga/info/MangaInfoPresenter.kt | 1 - .../main/res/drawable/ic_share_white_24dp.xml | 9 +++++++++ app/src/main/res/menu/manga_info.xml | 6 ++++++ app/src/main/res/values/strings.xml | 3 +++ 6 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 app/src/main/res/drawable/ic_share_white_24dp.xml diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/OnlineSource.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/OnlineSource.kt index 8daa87703..4df59bc37 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/OnlineSource.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/OnlineSource.kt @@ -192,7 +192,7 @@ abstract class OnlineSource(context: Context) : Source { * * @param manga the manga to be updated. */ - open protected fun mangaDetailsRequest(manga: Manga): Request { + open fun mangaDetailsRequest(manga: Manga): Request { return GET(baseUrl + manga.url, headers) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoFragment.kt index 9bc40b823..6ab67a923 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoFragment.kt @@ -1,5 +1,6 @@ package eu.kanade.tachiyomi.ui.manga.info +import android.content.Intent import android.net.Uri import android.os.Bundle import android.support.customtabs.CustomTabsIntent @@ -59,6 +60,7 @@ class MangaInfoFragment : BaseRxFragment() { override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { R.id.action_open_in_browser -> openInBrowser() + R.id.action_share -> shareManga() else -> return super.onOptionsItemSelected(item) } return true @@ -158,6 +160,24 @@ class MangaInfoFragment : BaseRxFragment() { } } + /** + * Called to run Intent with [Intent.ACTION_SEND], which show share dialog. + */ + private fun shareManga() { + val source = presenter.source as? OnlineSource ?: return + try { + val url = source.mangaDetailsRequest(presenter.manga).url().toString() + val sharingIntent = Intent(Intent.ACTION_SEND).apply { + type = "text/plain" + putExtra(android.content.Intent.EXTRA_SUBJECT, presenter.manga.title) + putExtra(android.content.Intent.EXTRA_TEXT, resources.getString(R.string.share_text, presenter.manga.title, url)) + } + startActivity(Intent.createChooser(sharingIntent, resources.getText(R.string.share_subject))) + } catch (e: Exception) { + context.toast(e.message) + } + } + /** * Update FAB with correct drawable. * diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoPresenter.kt index 3c54c6ff8..35941b3d0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoPresenter.kt @@ -128,5 +128,4 @@ class MangaInfoPresenter : BasePresenter() { private fun refreshManga() { start(GET_MANGA) } - } diff --git a/app/src/main/res/drawable/ic_share_white_24dp.xml b/app/src/main/res/drawable/ic_share_white_24dp.xml new file mode 100644 index 000000000..c5027c659 --- /dev/null +++ b/app/src/main/res/drawable/ic_share_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/menu/manga_info.xml b/app/src/main/res/menu/manga_info.xml index 8cb84e382..e9aa77c9f 100644 --- a/app/src/main/res/menu/manga_info.xml +++ b/app/src/main/res/menu/manga_info.xml @@ -2,6 +2,12 @@ + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1447866d9..d77e2da59 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -56,6 +56,7 @@ Cancel Sort Install + Share Deleting… @@ -222,6 +223,8 @@ Status Source Genres + Share… + Check out %1$s! at %2$s Chapters From bf05952582807b469e721cf8ca3be36e8db17f28 Mon Sep 17 00:00:00 2001 From: Greg Wright Date: Fri, 23 Sep 2016 13:50:01 -0500 Subject: [PATCH 46/79] Gradle custom script 'app/custom.gradle' (#473) --- app/.gitignore | 2 +- app/build.gradle | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/.gitignore b/app/.gitignore index 8bd2d8bd6..90de2b9c8 100644 --- a/app/.gitignore +++ b/app/.gitignore @@ -1,4 +1,4 @@ /build *iml *.iml -.idea \ No newline at end of file +custom.gradle \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index b6344decc..a87138e69 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -4,6 +4,10 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' +if (file("custom.gradle").exists()) { + apply from: "custom.gradle" +} + ext { // Git is needed in your system PATH for these commands to work. // If it's not installed, you can return a random value as a workaround From a81609fd2c80459185cefeadf7bce3a81cddb818 Mon Sep 17 00:00:00 2001 From: len Date: Sun, 25 Sep 2016 23:04:43 +0200 Subject: [PATCH 47/79] Fix #480 ? --- .../data/source/online/english/Kissmanga.kt | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Kissmanga.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Kissmanga.kt index 565cbefb0..4abe1e820 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Kissmanga.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Kissmanga.kt @@ -51,18 +51,9 @@ class Kissmanga(context: Context, override val id: Int) : ParsedOnlineSource(con add("authorArtist", "") add("mangaName", query) add("status", "") - } - val filterIndexes = filters.map { it.id.toInt() } - val maxFilterIndex = filterIndexes.max() - - if (maxFilterIndex !== null) { - for (i in 0..maxFilterIndex) { - form.add("genres", if (filterIndexes.contains(i)) { - "1" - } else { - "0" - }) + this@Kissmanga.filters.forEach { filter -> + add("genres", if (filter in filters) "1" else "0") } } From d352405ba6a92f5d2fedf3608590788cbb258622 Mon Sep 17 00:00:00 2001 From: Andy Bao Date: Thu, 29 Sep 2016 12:38:29 -0400 Subject: [PATCH 48/79] Open from homescreen/add shortcut to launcher (#435) * Add very basic "Add to homescreen" action in manga info fragment. * Fix open from homescreen opening current manga (if a manga is open). Code cleanup. * Improve fix for "Opening from homescreen opens currently open manga if a manga is currently open" and fix "Going back to the main app via a Manga opened through a shortcut repeats the launcher open animation". * Implement custom icons, add star icon and optimize some things. * Remove Tachiyomi and custom image icon types. * Move icon creation task into an observable. Added some extra error handling. --- app/build.gradle | 2 + app/src/main/AndroidManifest.xml | 4 +- .../tachiyomi/ui/manga/MangaActivity.kt | 6 ++ .../ui/manga/info/MangaInfoFragment.kt | 99 ++++++++++++++++++ .../main/res/drawable/ic_home_white_24dp.xml | 9 ++ app/src/main/res/drawable/mask_star.png | Bin 0 -> 585 bytes app/src/main/res/menu/manga_info.xml | 4 + app/src/main/res/values/strings.xml | 12 +++ 8 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 app/src/main/res/drawable/ic_home_white_24dp.xml create mode 100644 app/src/main/res/drawable/mask_star.png diff --git a/app/build.gradle b/app/build.gradle index a87138e69..96386dc2f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -153,6 +153,8 @@ dependencies { // Image library compile 'com.github.bumptech.glide:glide:3.7.0' compile 'com.github.bumptech.glide:okhttp3-integration:1.4.0@aar' + // Transformations + compile 'jp.wasabeef:glide-transformations:2.0.1' // Logging compile 'com.jakewharton.timber:timber:4.3.0' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d91c3b2d6..91d0788e7 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -8,6 +8,7 @@ + + android:parentActivityName=".ui.main.MainActivity" + android:exported="true"> () { const val FROM_CATALOGUE_EXTRA = "from_catalogue" const val MANGA_EXTRA = "manga" + const val FROM_LAUNCHER_EXTRA = "from_launcher" const val INFO_FRAGMENT = 0 const val CHAPTERS_FRAGMENT = 1 const val MYANIMELIST_FRAGMENT = 2 @@ -47,6 +48,11 @@ class MangaActivity : BaseRxActivity() { super.onCreate(savedState) setContentView(R.layout.activity_manga) + val fromLauncher = intent.getBooleanExtra(FROM_LAUNCHER_EXTRA, false) + + //Remove any current manga if we are launching from launcher + if(fromLauncher) SharedData.remove(MangaEvent::class.java) + presenter.setMangaEvent(SharedData.getOrPut(MangaEvent::class.java) { val id = intent.getLongExtra(MANGA_EXTRA, 0) MangaEvent(presenter.db.getManga(id).executeAsBlocking()!!) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoFragment.kt index 6ab67a923..6279466ce 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoFragment.kt @@ -1,21 +1,44 @@ package eu.kanade.tachiyomi.ui.manga.info +import android.app.Activity import android.content.Intent +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.graphics.drawable.BitmapDrawable import android.net.Uri import android.os.Bundle import android.support.customtabs.CustomTabsIntent +import android.support.design.widget.Snackbar +import android.util.SparseArray import android.view.* +import com.afollestad.materialdialogs.MaterialDialog +import com.bumptech.glide.BitmapRequestBuilder +import com.bumptech.glide.BitmapTypeRequest import com.bumptech.glide.Glide import com.bumptech.glide.load.engine.DiskCacheStrategy +import com.bumptech.glide.load.resource.bitmap.CenterCrop import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.source.Source import eu.kanade.tachiyomi.data.source.online.OnlineSource import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment +import eu.kanade.tachiyomi.ui.library.LibraryFragment +import eu.kanade.tachiyomi.ui.manga.MangaActivity import eu.kanade.tachiyomi.util.getResourceColor import eu.kanade.tachiyomi.util.toast +import jp.wasabeef.glide.transformations.CropCircleTransformation +import jp.wasabeef.glide.transformations.CropSquareTransformation +import jp.wasabeef.glide.transformations.MaskTransformation +import jp.wasabeef.glide.transformations.RoundedCornersTransformation import kotlinx.android.synthetic.main.fragment_manga_info.* +import kotlinx.android.synthetic.main.item_download.* import nucleus.factory.RequiresPresenter +import rx.Observable +import rx.android.schedulers.AndroidSchedulers +import rx.schedulers.Schedulers +import timber.log.Timber +import java.io.IOException +import kotlin.concurrent.thread /** * Fragment that shows manga information. @@ -34,6 +57,7 @@ class MangaInfoFragment : BaseRxFragment() { fun newInstance(): MangaInfoFragment { return MangaInfoFragment() } + } override fun onCreate(savedState: Bundle?) { @@ -61,6 +85,7 @@ class MangaInfoFragment : BaseRxFragment() { when (item.itemId) { R.id.action_open_in_browser -> openInBrowser() R.id.action_share -> shareManga() + R.id.action_add_to_home_screen -> addToHomeScreen() else -> return super.onOptionsItemSelected(item) } return true @@ -178,6 +203,80 @@ class MangaInfoFragment : BaseRxFragment() { } } + /** + * Add the manga to the home screen + */ + fun addToHomeScreen() { + val shortcutIntent = activity.intent + shortcutIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + .putExtra(MangaActivity.FROM_LAUNCHER_EXTRA, true) + + val addIntent = Intent() + addIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent) + .action = "com.android.launcher.action.INSTALL_SHORTCUT" + + //Set shortcut title + MaterialDialog.Builder(activity) + .title(R.string.shortcut_title) + .input("", presenter.manga.title, { md, text -> + //Set shortcut title + addIntent.putExtra(Intent.EXTRA_SHORTCUT_NAME, text.toString()) + + reshapeIconBitmap(addIntent, + Glide.with(context).load(presenter.manga).asBitmap()) + }) + .negativeText(android.R.string.cancel) + .onNegative { materialDialog, dialogAction -> materialDialog.cancel() } + .show() + } + + fun reshapeIconBitmap(addIntent: Intent, request: BitmapTypeRequest) { + val modes = intArrayOf(R.string.circular_icon, + R.string.rounded_icon, + R.string.square_icon, + R.string.star_icon) + + fun BitmapRequestBuilder.toIcon(): Bitmap { + return this.into(96, 96).get() + } + + MaterialDialog.Builder(activity) + .title(R.string.icon_shape) + .negativeText(android.R.string.cancel) + .items(modes.map { getString(it) }) + .itemsCallback { dialog, view, i, charSequence -> + Observable.fromCallable { + // i = 0: Circular icon + // i = 1: Rounded icon + // i = 2: Square icon + // i = 3: Star icon (because boredom) + when (i) { + 0 -> request.transform(CropCircleTransformation(context)).toIcon() + 1 -> request.transform(RoundedCornersTransformation(context, 5, 0)).toIcon() + 2 -> request.transform(CropSquareTransformation(context)).toIcon() + 3 -> request.transform(CenterCrop(context), MaskTransformation(context, R.drawable.mask_star)).toIcon() + else -> null + } + }.subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ if (it != null) createShortcut(addIntent, it) }, + { context.toast(R.string.icon_creation_fail) }) + }.show() + } + + fun createShortcut(addIntent: Intent, icon: Bitmap) { + //Send shortcut intent + addIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON, icon) + context.sendBroadcast(addIntent) + //Go to launcher to show this shiny new shortcut! + val startMain = Intent(Intent.ACTION_MAIN) + startMain.addCategory(Intent.CATEGORY_HOME) + .flags = Intent.FLAG_ACTIVITY_NEW_TASK + activity.runOnUiThread { + startActivity(startMain) + } + } + /** * Update FAB with correct drawable. * diff --git a/app/src/main/res/drawable/ic_home_white_24dp.xml b/app/src/main/res/drawable/ic_home_white_24dp.xml new file mode 100644 index 000000000..fafc05e0c --- /dev/null +++ b/app/src/main/res/drawable/ic_home_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/mask_star.png b/app/src/main/res/drawable/mask_star.png new file mode 100644 index 0000000000000000000000000000000000000000..5d3c119538568147cb14b47f1978d4a76bbd8a13 GIT binary patch literal 585 zcmV-P0=E5$P) zfLQG3lZR74Sm5Q;r-MLv=;Nx#vlHWE0d$0*G=nsRpmYN@0ipB*8beT-K@B0O+`vW< zRCe&Jd$9r#j{7k%KURD(3jnLi_9{Dv}wHQUg(O}&l-zc<~15Eh?J3ZH8MVBB#nmVr|O32H)OKJLD+mp z=hc^CE?0Bo!|w|^+|CaFS+lf;!OD-F>xJ>Lk+l0o?W*P + + \ 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 d77e2da59..cb2d60fef 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -51,6 +51,7 @@ Remove Resume Open in browser + Add to home screen Change display mode Set filter Cancel @@ -225,6 +226,16 @@ Genres Share… Check out %1$s! at %2$s + Manga added to home screen + Icon type + Tachiyomi icon + Circular icon + Rounded icon + Square icon + Star icon + Shortcut title + Icon shape + Failed to create shortcut! Chapters @@ -305,6 +316,7 @@ Select cover image Select backup file + Select shortcut icon New update available! From 11ac4df5d7d0d332c6ceedd006af2fe2d18de6be Mon Sep 17 00:00:00 2001 From: len Date: Thu, 29 Sep 2016 19:53:59 +0200 Subject: [PATCH 49/79] Bump dependencies, remove unused resources --- app/build.gradle | 16 ++++++++-------- .../data/network/CloudflareInterceptor.kt | 3 +-- .../kanade/tachiyomi/ui/backup/BackupFragment.kt | 2 ++ .../tachiyomi/ui/manga/info/MangaInfoFragment.kt | 10 ---------- .../tachiyomi/ui/setting/SettingsFragment.kt | 7 ++++--- .../eu/kanade/tachiyomi/util/ThemeExtensions.kt | 16 ++++++++++++---- app/src/main/res/values/strings.xml | 3 --- app/src/main/res/values/themes.xml | 4 ++++ 8 files changed, 31 insertions(+), 30 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 96386dc2f..44e353fe3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -91,7 +91,7 @@ dependencies { compile 'com.github.inorichi:ReactiveNetwork:69092ed' // Android support library - final support_library_version = '24.1.1' + final support_library_version = '24.2.1' compile "com.android.support:support-v4:$support_library_version" compile "com.android.support:appcompat-v7:$support_library_version" compile "com.android.support:cardview-v7:$support_library_version" @@ -104,7 +104,7 @@ dependencies { // ReactiveX compile 'io.reactivex:rxandroid:1.2.1' - compile 'io.reactivex:rxjava:1.1.10' + compile 'io.reactivex:rxjava:1.2.0' compile 'com.f2prateek.rx.preferences:rx-preferences:1.0.2' // Network client @@ -121,13 +121,13 @@ dependencies { // JSON compile 'com.google.code.gson:gson:2.7' - compile 'com.github.salomonbrys.kotson:kotson:2.3.0' + compile 'com.github.salomonbrys.kotson:kotson:2.4.0' // YAML compile 'com.github.bmoliveira:snake-yaml:v1.18-android' // JavaScript engine - compile 'com.squareup.duktape:duktape-android:0.9.5' + compile 'com.squareup.duktape:duktape-android:1.0.0' // Disk cache compile 'com.jakewharton:disklrucache:2.0.2' @@ -157,19 +157,19 @@ dependencies { compile 'jp.wasabeef:glide-transformations:2.0.1' // Logging - compile 'com.jakewharton.timber:timber:4.3.0' + compile 'com.jakewharton.timber:timber:4.3.1' // Crash reports compile 'ch.acra:acra:4.9.0' // UI - compile 'com.dmitrymalkovich.android:material-design-dimens:1.2' + compile 'com.dmitrymalkovich.android:material-design-dimens:1.4' compile 'com.github.dmytrodanylyk.android-process-button:library:1.0.4' compile 'eu.davidea:flexible-adapter:4.2.0' compile 'com.nononsenseapps:filepicker:2.5.2' compile 'com.github.amulyakhare:TextDrawable:558677e' - compile 'com.afollestad.material-dialogs:core:0.8.6.2' - compile 'net.xpece.android:support-preference:0.8.1' + compile 'com.afollestad.material-dialogs:core:0.9.0.2' + compile 'net.xpece.android:support-preference:1.0.3' compile 'me.zhanghai.android.systemuihelper:library:1.0.0' compile 'de.hdodenhof:circleimageview:2.1.0' diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/network/CloudflareInterceptor.kt b/app/src/main/java/eu/kanade/tachiyomi/data/network/CloudflareInterceptor.kt index 41b8a5aaf..503b779a3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/network/CloudflareInterceptor.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/network/CloudflareInterceptor.kt @@ -60,8 +60,7 @@ class CloudflareInterceptor(private val cookies: PersistentCookieStore) : Interc .replace(Regex("""\s{3,}[a-z](?: = |\.).+"""), "") .replace("\n", "") - // Duktape can only return strings, so the result has to be converted to string first - val result = duktape.evaluate("$js.toString()").toInt() + val result = (duktape.evaluate(js) as Double).toInt() val answer = "${result + domain.length}" diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/backup/BackupFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/backup/BackupFragment.kt index 28d4c01c7..d17235478 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/backup/BackupFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/backup/BackupFragment.kt @@ -41,6 +41,8 @@ class BackupFragment : BaseRxFragment() { } override fun onViewCreated(view: View, savedState: Bundle?) { + setToolbarTitle(getString(R.string.label_backup)) + (activity as ActivityMixin).requestPermissionsOnMarshmallow() subscriptions = SubscriptionList() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoFragment.kt index 6279466ce..675841c6d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoFragment.kt @@ -1,15 +1,10 @@ package eu.kanade.tachiyomi.ui.manga.info -import android.app.Activity import android.content.Intent import android.graphics.Bitmap -import android.graphics.BitmapFactory -import android.graphics.drawable.BitmapDrawable import android.net.Uri import android.os.Bundle import android.support.customtabs.CustomTabsIntent -import android.support.design.widget.Snackbar -import android.util.SparseArray import android.view.* import com.afollestad.materialdialogs.MaterialDialog import com.bumptech.glide.BitmapRequestBuilder @@ -22,7 +17,6 @@ import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.source.Source import eu.kanade.tachiyomi.data.source.online.OnlineSource import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment -import eu.kanade.tachiyomi.ui.library.LibraryFragment import eu.kanade.tachiyomi.ui.manga.MangaActivity import eu.kanade.tachiyomi.util.getResourceColor import eu.kanade.tachiyomi.util.toast @@ -31,14 +25,10 @@ import jp.wasabeef.glide.transformations.CropSquareTransformation import jp.wasabeef.glide.transformations.MaskTransformation import jp.wasabeef.glide.transformations.RoundedCornersTransformation import kotlinx.android.synthetic.main.fragment_manga_info.* -import kotlinx.android.synthetic.main.item_download.* import nucleus.factory.RequiresPresenter import rx.Observable import rx.android.schedulers.AndroidSchedulers import rx.schedulers.Schedulers -import timber.log.Timber -import java.io.IOException -import kotlin.concurrent.thread /** * Fragment that shows manga information. diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsFragment.kt index 8c74375c6..9fc116b76 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsFragment.kt @@ -7,9 +7,9 @@ import android.support.v4.content.ContextCompat import android.support.v7.preference.XpPreferenceFragment import android.view.View import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.util.getResourceId import net.xpece.android.support.preference.PreferenceIconHelper import net.xpece.android.support.preference.PreferenceScreenNavigationStrategy -import net.xpece.android.support.preference.Util import rx.subscriptions.CompositeSubscription open class SettingsFragment : XpPreferenceFragment() { @@ -24,8 +24,8 @@ open class SettingsFragment : XpPreferenceFragment() { lateinit var subscriptions: CompositeSubscription - private val iconTint by lazy { ContextCompat.getColorStateList( - context, Util.resolveResourceId(context, R.attr.colorAccent, 0)) + private val iconTint by lazy { ContextCompat.getColorStateList(context, + context.theme.getResourceId(R.attr.colorAccent, 0)) } override final fun onCreatePreferences2(savedState: Bundle?, rootKey: String?) { @@ -59,6 +59,7 @@ open class SettingsFragment : XpPreferenceFragment() { @CallSuper override fun onViewCreated(view: View, savedState: Bundle?) { + super.onViewCreated(view, savedState) listView.isFocusable = false } diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/ThemeExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/ThemeExtensions.kt index 81c1988fe..da0f90282 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/ThemeExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/ThemeExtensions.kt @@ -2,18 +2,26 @@ package eu.kanade.tachiyomi.util import android.content.res.Resources import android.graphics.drawable.Drawable +import android.support.annotation.AttrRes import android.support.annotation.StringRes -fun Resources.Theme.getResourceColor(@StringRes resource: Int) : Int { - val typedArray = this.obtainStyledAttributes(intArrayOf(resource)) +fun Resources.Theme.getResourceColor(@StringRes resource: Int): Int { + val typedArray = obtainStyledAttributes(intArrayOf(resource)) val attrValue = typedArray.getColor(0, 0) typedArray.recycle() return attrValue } -fun Resources.Theme.getResourceDrawable(@StringRes resource: Int) : Drawable { - val typedArray = this.obtainStyledAttributes(intArrayOf(resource)) +fun Resources.Theme.getResourceDrawable(@StringRes resource: Int): Drawable { + val typedArray = obtainStyledAttributes(intArrayOf(resource)) val attrValue = typedArray.getDrawable(0) typedArray.recycle() return attrValue +} + +fun Resources.Theme.getResourceId(@AttrRes resource: Int, fallback: Int): Int { + val typedArray = obtainStyledAttributes(intArrayOf(resource)) + val attrValue = typedArray.getResourceId(0, fallback) + typedArray.recycle() + return attrValue } \ 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 cb2d60fef..f87bb505f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -226,9 +226,6 @@ Genres Share… Check out %1$s! at %2$s - Manga added to home screen - Icon type - Tachiyomi icon Circular icon Rounded icon Square icon diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index a38ec375f..bbf56979d 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -36,6 +36,8 @@ @drawable/line_divider_light @color/textColorPrimaryLight @color/dialogLight + ?colorAccent + ?colorAccent