From e68a1ae48d30f5823b2b9d303b43dbfe0e653a24 Mon Sep 17 00:00:00 2001 From: Jay Date: Wed, 11 Mar 2020 00:28:47 -0700 Subject: [PATCH] Extensions now in a bottom sheet on the browse tab Boy I love bottom sheets guys --- .../ui/catalogue/CatalogueController.kt | 113 +++++++-- .../ui/catalogue/CataloguePresenter.kt | 4 +- .../ui/extension/ExtensionAdapter.kt | 12 +- .../ui/extension/ExtensionBottomPresenter.kt | 153 ++++++++++++ .../ui/extension/ExtensionBottomSheet.kt | 224 ++++++++++++++++++ .../ui/extension/ExtensionPresenter.kt | 2 +- .../ui/extension/ExtensionTrustDialog.kt | 10 +- .../ui/setting/SettingsMainController.kt | 8 - .../tachiyomi/util/view/ViewExtensions.kt | 10 +- .../widget/preference/ExtensionPreference.kt | 2 +- .../res/color/btn_bg_primary_selector.xml | 6 + .../main/res/drawable/ic_sync_black_24dp.xml | 1 + app/src/main/res/layout/auto_ext_checkbox.xml | 9 + .../res/layout/catalogue_main_controller.xml | 47 +++- .../catalouge_more_extensions_card_item.xml | 43 ++++ .../main/res/layout/extension_controller.xml | 10 +- .../res/layout/extensions_bottom_sheet.xml | 64 +++++ app/src/main/res/menu/extension_main.xml | 12 +- app/src/main/res/values/strings.xml | 8 +- 19 files changed, 675 insertions(+), 63 deletions(-) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionBottomPresenter.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionBottomSheet.kt create mode 100644 app/src/main/res/color/btn_bg_primary_selector.xml create mode 100644 app/src/main/res/layout/auto_ext_checkbox.xml create mode 100644 app/src/main/res/layout/catalouge_more_extensions_card_item.xml create mode 100644 app/src/main/res/layout/extensions_bottom_sheet.xml diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueController.kt index 787c43a758..40ba5cd54a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueController.kt @@ -13,7 +13,7 @@ import com.bluelinelabs.conductor.ControllerChangeHandler import com.bluelinelabs.conductor.ControllerChangeType import com.bluelinelabs.conductor.RouterTransaction import com.bluelinelabs.conductor.changehandler.FadeChangeHandler -import com.jakewharton.rxbinding.support.v7.widget.queryTextChangeEvents +import com.google.android.material.bottomsheet.BottomSheetBehavior import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.items.IFlexible import eu.kanade.tachiyomi.R @@ -26,17 +26,20 @@ import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction import eu.kanade.tachiyomi.ui.catalogue.browse.BrowseCatalogueController import eu.kanade.tachiyomi.ui.catalogue.global_search.CatalogueSearchController import eu.kanade.tachiyomi.ui.catalogue.latest.LatestUpdatesController +import eu.kanade.tachiyomi.ui.extension.SettingsExtensionsController import eu.kanade.tachiyomi.ui.main.RootSearchInterface import eu.kanade.tachiyomi.ui.setting.SettingsSourcesController -import eu.kanade.tachiyomi.util.view.RecyclerWindowInsetsListener import eu.kanade.tachiyomi.util.view.applyWindowInsetsForRootController import eu.kanade.tachiyomi.util.view.scrollViewWith +import eu.kanade.tachiyomi.util.view.setOnQueryTextChangeListener import eu.kanade.tachiyomi.widget.preference.SourceLoginDialog import kotlinx.android.parcel.Parcelize import kotlinx.android.synthetic.main.catalogue_main_controller.* +import kotlinx.android.synthetic.main.extensions_bottom_sheet.* import kotlinx.android.synthetic.main.main_activity.* import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import kotlin.math.max /** * This controller shows and manages the different catalogues enabled by the user. @@ -62,6 +65,13 @@ class CatalogueController : NucleusController(), */ private var adapter: CatalogueAdapter? = null + var extQuery = "" + private set + + var headerHeight = 0 + + var customTitle = "" + /** * Called when controller is initialized. */ @@ -76,7 +86,9 @@ class CatalogueController : NucleusController(), * @return title. */ override fun getTitle(): String? { - return applicationContext?.getString(R.string.label_catalogues) + return if (ext_bottom_sheet.sheetBehavior?.state == BottomSheetBehavior.STATE_EXPANDED) + applicationContext?.getString(R.string.label_extensions) + else applicationContext?.getString(R.string.label_catalogues) } /** @@ -114,11 +126,41 @@ class CatalogueController : NucleusController(), recycler.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(view.context) recycler.adapter = adapter recycler.addItemDecoration(SourceDividerItemDecoration(view.context)) - recycler.setOnApplyWindowInsetsListener(RecyclerWindowInsetsListener) - - scrollViewWith(recycler) + val attrsArray = intArrayOf(android.R.attr.actionBarSize) + val array = view.context.obtainStyledAttributes(attrsArray) + val appBarHeight = array.getDimensionPixelSize(0, 0) + array.recycle() + scrollViewWith(recycler) { + headerHeight = it.systemWindowInsetTop + appBarHeight + } requestPermissionsSafe(arrayOf(WRITE_EXTERNAL_STORAGE), 301) + ext_bottom_sheet.onCreate(this) + + ext_bottom_sheet.sheetBehavior?.addBottomSheetCallback(object : BottomSheetBehavior + .BottomSheetCallback() { + override fun onSlide(bottomSheet: View, progress: Float) { + shadow2.alpha = (1 - max(0f, progress)) * 0.25f + sheet_layout.alpha = 1 - progress + activity?.appbar?.y = max(activity!!.appbar.y, -headerHeight * (1 - progress)) + } + + override fun onStateChanged(p0: View, state: Int) { + if (state == BottomSheetBehavior.STATE_EXPANDED) activity?.appbar?.y = 0f + if (state == BottomSheetBehavior.STATE_EXPANDED || + state == BottomSheetBehavior.STATE_COLLAPSED) + sheet_layout.alpha = + if (state == BottomSheetBehavior.STATE_COLLAPSED) 1f else 0f + + retainViewMode = if (state == BottomSheetBehavior.STATE_EXPANDED) + RetainViewMode.RETAIN_DETACH else RetainViewMode.RELEASE_DETACH + activity?.invalidateOptionsMenu() + setTitle() + sheet_layout.isClickable = state == BottomSheetBehavior.STATE_COLLAPSED + sheet_layout.isFocusable = state == BottomSheetBehavior.STATE_COLLAPSED + } + }) + } override fun onDestroyView(view: View) { @@ -129,6 +171,7 @@ class CatalogueController : NucleusController(), override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) { super.onChangeStarted(handler, type) if (!type.isPush && handler is SettingsSourcesFadeChangeHandler) { + ext_bottom_sheet.updateExtTitle() presenter.updateSources() } } @@ -192,20 +235,41 @@ class CatalogueController : NucleusController(), * @param inflater used to load the menu xml. */ override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { - // Inflate menu - inflater.inflate(R.menu.catalogue_main, menu) + if (ext_bottom_sheet.sheetBehavior?.state == BottomSheetBehavior.STATE_EXPANDED) { + // Inflate menu + inflater.inflate(R.menu.extension_main, menu) - // Initialize search option. - val searchItem = menu.findItem(R.id.action_search) - val searchView = searchItem.actionView as SearchView + // Initialize search option. + val searchItem = menu.findItem(R.id.action_search) + val searchView = searchItem.actionView as SearchView - // Change hint to show global search. - searchView.queryHint = applicationContext?.getString(R.string.action_global_search_hint) + // Change hint to show global search. + searchView.queryHint = applicationContext?.getString(R.string.search_extensions) - // Create query listener which opens the global search view. - searchView.queryTextChangeEvents() - .filter { it.isSubmitted } - .subscribeUntilDestroy { performGlobalSearch(it.queryText().toString()) } + // Create query listener which opens the global search view. + setOnQueryTextChangeListener(searchView) { + extQuery = it ?: "" + ext_bottom_sheet.drawExtensions() + true + } + } + else { + // Inflate menu + inflater.inflate(R.menu.catalogue_main, menu) + + // Initialize search option. + val searchItem = menu.findItem(R.id.action_search) + val searchView = searchItem.actionView as SearchView + + // Change hint to show global search. + searchView.queryHint = applicationContext?.getString(R.string.action_global_search_hint) + + // Create query listener which opens the global search view. + setOnQueryTextChangeListener(searchView, true) { + if (!it.isNullOrBlank()) performGlobalSearch(it) + true + } + } } private fun performGlobalSearch(query: String){ @@ -222,9 +286,18 @@ class CatalogueController : NucleusController(), when (item.itemId) { // Initialize option to open catalogue settings. R.id.action_filter -> { - router.pushController((RouterTransaction.with(SettingsSourcesController())) - .popChangeHandler(SettingsSourcesFadeChangeHandler()) - .pushChangeHandler(FadeChangeHandler())) + val controller = + if (ext_bottom_sheet.sheetBehavior?.state == BottomSheetBehavior.STATE_EXPANDED) + SettingsExtensionsController() + else SettingsSourcesController() + router.pushController( + (RouterTransaction.with(controller)).popChangeHandler( + SettingsSourcesFadeChangeHandler() + ).pushChangeHandler(FadeChangeHandler()) + ) + } + R.id.action_dismiss -> { + ext_bottom_sheet.sheetBehavior?.state = BottomSheetBehavior.STATE_COLLAPSED } else -> return super.onOptionsItemSelected(item) } 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 096c5b19e7..ae5611e70a 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,7 +12,7 @@ import rx.Subscription import rx.android.schedulers.AndroidSchedulers import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get -import java.util.* +import java.util.TreeMap import java.util.concurrent.TimeUnit /** @@ -101,4 +101,4 @@ class CataloguePresenter( .sortedBy { "(${it.lang}) ${it.name}" } + sourceManager.get(LocalSource.ID) as LocalSource } -} +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionAdapter.kt index f8b1f56715..d40c17c7e5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionAdapter.kt @@ -3,17 +3,19 @@ package eu.kanade.tachiyomi.ui.extension import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.items.IFlexible import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.ui.extension.ExtensionAdapter.OnButtonClickListener import eu.kanade.tachiyomi.util.system.getResourceColor /** * Adapter that holds the catalogue cards. * - * @param controller instance of [ExtensionController]. + * @param listener instance of [OnButtonClickListener]. */ -class ExtensionAdapter(val controller: ExtensionController) : - FlexibleAdapter>(null, controller, true) { +class ExtensionAdapter(val listener: OnButtonClickListener) : + FlexibleAdapter>(null, listener, true) { - val cardBackground = controller.activity!!.getResourceColor(R.attr.background_card) + val cardBackground = (listener as ExtensionBottomSheet).context.getResourceColor(R.attr + .background_card) init { setDisplayHeadersAtStartUp(true) @@ -22,7 +24,7 @@ class ExtensionAdapter(val controller: ExtensionController) : /** * Listener for browse item clicks. */ - val buttonClickListener: ExtensionAdapter.OnButtonClickListener = controller + val buttonClickListener: ExtensionAdapter.OnButtonClickListener = listener interface OnButtonClickListener { fun onButtonClick(position: Int) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionBottomPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionBottomPresenter.kt new file mode 100644 index 0000000000..2c1793ae6a --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionBottomPresenter.kt @@ -0,0 +1,153 @@ +package eu.kanade.tachiyomi.ui.extension + +import android.app.Application +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.preference.PreferencesHelper +import eu.kanade.tachiyomi.data.preference.getOrDefault +import eu.kanade.tachiyomi.extension.ExtensionManager +import eu.kanade.tachiyomi.extension.model.Extension +import eu.kanade.tachiyomi.extension.model.InstallStep +import eu.kanade.tachiyomi.util.system.LocaleHelper +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import rx.Observable +import rx.Subscription +import rx.android.schedulers.AndroidSchedulers +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get +import java.util.concurrent.TimeUnit +import kotlin.coroutines.CoroutineContext + +/** + * Presenter of [ExtensionController]. + */ +open class ExtensionBottomPresenter( + private val bottomSheet: ExtensionBottomSheet, + private val extensionManager: ExtensionManager = Injekt.get(), + private val preferences: PreferencesHelper = Injekt.get() +) : CoroutineScope { + + override var coroutineContext: CoroutineContext = Job() + Dispatchers.Default + + private var extensions = emptyList() + + private var currentDownloads = hashMapOf() + + fun onCreate() { + extensionManager.findAvailableExtensions() + bindToExtensionsObservable() + } + + private fun bindToExtensionsObservable(): Subscription { + val installedObservable = extensionManager.getInstalledExtensionsObservable() + val untrustedObservable = extensionManager.getUntrustedExtensionsObservable() + val availableObservable = extensionManager.getAvailableExtensionsObservable() + .startWith(emptyList()) + + return Observable.combineLatest(installedObservable, untrustedObservable, availableObservable) + { installed, untrusted, available -> Triple(installed, untrusted, available) } + .debounce(100, TimeUnit.MILLISECONDS) + .map(::toItems) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { + bottomSheet.setExtensions(extensions) + } + } + + @Synchronized + private fun toItems(tuple: ExtensionTuple): List { + val context = Injekt.get() + val activeLangs = preferences.enabledLanguages().getOrDefault() + + val (installed, untrusted, available) = tuple + + val items = mutableListOf() + + val installedSorted = installed.sortedWith(compareBy({ !it.hasUpdate }, { !it.isObsolete }, { it.pkgName })) + val untrustedSorted = untrusted.sortedBy { it.pkgName } + val availableSorted = available + // Filter out already installed extensions and disabled languages + .filter { avail -> installed.none { it.pkgName == avail.pkgName } + && untrusted.none { it.pkgName == avail.pkgName } + && (avail.lang in activeLangs || avail.lang == "all")} + .sortedBy { it.pkgName } + + if (installedSorted.isNotEmpty() || untrustedSorted.isNotEmpty()) { + val header = ExtensionGroupItem(context.getString(R.string.ext_installed), installedSorted.size + untrustedSorted.size) + items += installedSorted.map { extension -> + ExtensionItem(extension, header, currentDownloads[extension.pkgName]) + } + items += untrustedSorted.map { extension -> + ExtensionItem(extension, header) + } + } + if (availableSorted.isNotEmpty()) { + val availableGroupedByLang = availableSorted + .groupBy { LocaleHelper.getDisplayName(it.lang, context) } + .toSortedMap() + + availableGroupedByLang + .forEach { + val header = ExtensionGroupItem(it.key, it.value.size) + items += it.value.map { extension -> + ExtensionItem(extension, header, currentDownloads[extension.pkgName]) + } + } + } + + this.extensions = items + return items + } + + fun getExtensionUpdateCount():Int = preferences.extensionUpdatesCount().getOrDefault() + fun getAutoCheckPref() = preferences.automaticExtUpdates() + + @Synchronized + private fun updateInstallStep(extension: Extension, state: InstallStep): ExtensionItem? { + val extensions = extensions.toMutableList() + val position = extensions.indexOfFirst { it.extension.pkgName == extension.pkgName } + + return if (position != -1) { + val item = extensions[position].copy(installStep = state) + extensions[position] = item + + this.extensions = extensions + item + } else { + null + } + } + + fun installExtension(extension: Extension.Available) { + extensionManager.installExtension(extension).subscribeToInstallUpdate(extension) + } + + fun updateExtension(extension: Extension.Installed) { + extensionManager.updateExtension(extension).subscribeToInstallUpdate(extension) + } + + private fun Observable.subscribeToInstallUpdate(extension: Extension) { + this.doOnNext { currentDownloads[extension.pkgName] = it } + .doOnUnsubscribe { currentDownloads.remove(extension.pkgName) } + .map { state -> updateInstallStep(extension, state) } + .subscribe { item -> + if (item != null) { + bottomSheet.downloadUpdate(item) + } + } + } + + fun uninstallExtension(pkgName: String) { + extensionManager.uninstallExtension(pkgName) + } + + fun findAvailableExtensions() { + extensionManager.findAvailableExtensions() + } + + fun trustSignature(signatureHash: String) { + extensionManager.trustSignature(signatureHash) + } + +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionBottomSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionBottomSheet.kt new file mode 100644 index 0000000000..714df3bf7f --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionBottomSheet.kt @@ -0,0 +1,224 @@ +package eu.kanade.tachiyomi.ui.extension + +import android.content.Context +import android.util.AttributeSet +import android.view.View +import android.widget.CheckBox +import android.widget.CompoundButton +import android.widget.LinearLayout +import com.f2prateek.rx.preferences.Preference +import com.google.android.material.bottomsheet.BottomSheetBehavior +import eu.davidea.flexibleadapter.FlexibleAdapter +import eu.davidea.flexibleadapter.items.AbstractHeaderItem +import eu.davidea.flexibleadapter.items.IFlexible +import eu.davidea.viewholders.FlexibleViewHolder +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.preference.getOrDefault +import eu.kanade.tachiyomi.extension.model.Extension +import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction +import eu.kanade.tachiyomi.ui.catalogue.CatalogueController +import eu.kanade.tachiyomi.util.view.RecyclerWindowInsetsListener +import eu.kanade.tachiyomi.util.view.doOnApplyWindowInsets +import eu.kanade.tachiyomi.util.view.updateLayoutParams +import kotlinx.android.synthetic.main.extensions_bottom_sheet.view.* + +class ExtensionBottomSheet @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) +: LinearLayout(context, attrs), +ExtensionAdapter.OnButtonClickListener, + FlexibleAdapter.OnItemClickListener, + FlexibleAdapter.OnItemLongClickListener, + ExtensionTrustDialog.Listener { + + var sheetBehavior: BottomSheetBehavior<*>? = null + lateinit var autoCheckItem:AutoCheckItem + + /** + * Adapter containing the list of manga from the catalogue. + */ + private var adapter: FlexibleAdapter>? = null + + val presenter = ExtensionBottomPresenter(this) + + private var extensions: List = emptyList() + + lateinit var controller: CatalogueController + + fun onCreate(controller: CatalogueController) { + // Initialize adapter, scroll listener and recycler views + autoCheckItem = AutoCheckItem(presenter.getAutoCheckPref()) + adapter = ExtensionAdapter(this) + sheetBehavior = BottomSheetBehavior.from(this) + // Create recycler and set adapter. + ext_recycler.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(context) + ext_recycler.adapter = adapter + ext_recycler.addItemDecoration(ExtensionDividerItemDecoration(context)) + ext_recycler.setOnApplyWindowInsetsListener(RecyclerWindowInsetsListener) + // scrollViewWith(ext_recycler, true, ext_swipe_refresh) + this.controller = controller + //ext_swipe_refresh.refreshes().subscribeUntilDestroy { + // presenter.findAvailableExtensions() + presenter.onCreate() + updateExtTitle() + + val attrsArray = intArrayOf(android.R.attr.actionBarSize) + val array = context.obtainStyledAttributes(attrsArray) + val headerHeight = array.getDimensionPixelSize(0, 0) + array.recycle() + ext_recycler.doOnApplyWindowInsets { _, windowInsets, _ -> + ext_recycler.updateLayoutParams { + topMargin = windowInsets.systemWindowInsetTop + headerHeight - + (sheet_layout.height) + } + } + sheet_layout.setOnClickListener { + if (sheetBehavior?.state != BottomSheetBehavior.STATE_EXPANDED) { + sheetBehavior?.state = BottomSheetBehavior.STATE_EXPANDED + } else { + sheetBehavior?.state = BottomSheetBehavior.STATE_COLLAPSED + } + } + presenter.getExtensionUpdateCount() + } + + fun updateExtTitle() { + val extCount = presenter.getExtensionUpdateCount() + title_text.text = if (extCount == 0) context.getString(R.string.label_extensions) + else resources.getQuantityString(R.plurals.extensions_updates_available, extCount, + extCount) + } + + override fun onButtonClick(position: Int) { + val extension = (adapter?.getItem(position) as? ExtensionItem)?.extension ?: return + when (extension) { + is Extension.Installed -> { + if (!extension.hasUpdate) { + openDetails(extension) + } else { + presenter.updateExtension(extension) + } + } + is Extension.Available -> { + presenter.installExtension(extension) + } + is Extension.Untrusted -> { + openTrustDialog(extension) + } + } + } + + override fun onItemClick(view: View?, position: Int): Boolean { + val extension = (adapter?.getItem(position) as? ExtensionItem)?.extension ?: return false + if (extension is Extension.Installed) { + openDetails(extension) + } else if (extension is Extension.Untrusted) { + openTrustDialog(extension) + } + + return false + } + + override fun onItemLongClick(position: Int) { + val extension = (adapter?.getItem(position) as? ExtensionItem)?.extension ?: return + if (extension is Extension.Installed || extension is Extension.Untrusted) { + uninstallExtension(extension.pkgName) + } + } + + private fun openDetails(extension: Extension.Installed) { + val controller = ExtensionDetailsController(extension.pkgName) + this.controller.router.pushController(controller.withFadeTransaction()) + } + + private fun openTrustDialog(extension: Extension.Untrusted) { + ExtensionTrustDialog(this, extension.signatureHash, extension.pkgName) + .showDialog(controller.router) + } + + fun setExtensions(extensions: List) { + //ext_swipe_refresh?.isRefreshing = false + this.extensions = extensions + controller.presenter.updateSources() + drawExtensions() + } + + fun drawExtensions() { + if (!controller.extQuery.isBlank()) { + adapter?.updateDataSet( + extensions.filter { + it.extension.name.contains(controller.extQuery, ignoreCase = true) + }) + } else { + adapter?.updateDataSet(extensions) + } + updateExtTitle() + setLastUsedSource() + } + + /** + * Called to set the last used catalogue at the top of the view. + */ + private fun setLastUsedSource() { + adapter?.removeAllScrollableHeaders() + adapter?.addScrollableHeader(autoCheckItem) + } + + fun downloadUpdate(item: ExtensionItem) { + adapter?.updateItem(item, item.installStep) + } + + override fun trustSignature(signatureHash: String) { + presenter.trustSignature(signatureHash) + } + + override fun uninstallExtension(pkgName: String) { + presenter.uninstallExtension(pkgName) + } +} + +class AutoCheckItem(private val autoCheck: Preference) : AbstractHeaderItem() { + + override fun getLayoutRes(): Int { + return R.layout.auto_ext_checkbox + } + + override fun createViewHolder( + view: View, adapter: FlexibleAdapter> + ): AutoCheckHolder { + return AutoCheckHolder(view, adapter, autoCheck) + } + + override fun bindViewHolder( + adapter: FlexibleAdapter>, + holder: AutoCheckHolder, + position: Int, + payloads: MutableList? + ) { + //holder.bind(autoCheck.getOrDefault()) + } + + override fun equals(other: Any?): Boolean { + return (this === other) + } + + override fun hashCode(): Int { + return -1 + } + + class AutoCheckHolder(val view: View, private val adapter: FlexibleAdapter>, + autoCheck: Preference) : + FlexibleViewHolder(view, adapter, true) { + private val autoCheckbox: CheckBox = view.findViewById(R.id.auto_checkbox) + + init { + autoCheckbox.bindToPreference(autoCheck) + } + + /** + * Binds a checkbox or switch view with a boolean preference. + */ + private fun CompoundButton.bindToPreference(pref: Preference) { + isChecked = pref.getOrDefault() + setOnCheckedChangeListener { _, isChecked -> pref.set(isChecked) } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionPresenter.kt index 04d2a8de3a..5830d35569 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionPresenter.kt @@ -17,7 +17,7 @@ import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import java.util.concurrent.TimeUnit -private typealias ExtensionTuple +typealias ExtensionTuple = Triple, List, List> /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionTrustDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionTrustDialog.kt index 577b27fd0a..3b16e4361b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionTrustDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionTrustDialog.kt @@ -3,18 +3,18 @@ package eu.kanade.tachiyomi.ui.extension import android.app.Dialog import android.os.Bundle import com.afollestad.materialdialogs.MaterialDialog -import com.bluelinelabs.conductor.Controller import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.ui.base.controller.DialogController class ExtensionTrustDialog(bundle: Bundle? = null) : DialogController(bundle) - where T : Controller, T: ExtensionTrustDialog.Listener { + where T: ExtensionTrustDialog.Listener { + lateinit var listener: Listener constructor(target: T, signatureHash: String, pkgName: String) : this(Bundle().apply { putString(SIGNATURE_KEY, signatureHash) putString(PKGNAME_KEY, pkgName) }) { - targetController = target + listener = target } override fun onCreateDialog(savedViewState: Bundle?): Dialog { @@ -22,10 +22,10 @@ class ExtensionTrustDialog(bundle: Bundle? = null) : DialogController(bundle) .title(R.string.untrusted_extension) .message(R.string.untrusted_extension_message) .positiveButton(R.string.ext_trust) { - (targetController as? Listener)?.trustSignature(args.getString(SIGNATURE_KEY)!!) + listener.trustSignature(args.getString(SIGNATURE_KEY)!!) } .negativeButton(R.string.ext_uninstall) { - (targetController as? Listener)?.uninstallExtension(args.getString(PKGNAME_KEY)!!) + listener.uninstallExtension(args.getString(PKGNAME_KEY)!!) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsMainController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsMainController.kt index ca1ee029d4..762bcf79de 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsMainController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsMainController.kt @@ -8,7 +8,6 @@ import com.bluelinelabs.conductor.Controller import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction -import eu.kanade.tachiyomi.ui.extension.ExtensionController import eu.kanade.tachiyomi.ui.migration.MigrationController import eu.kanade.tachiyomi.util.system.getResourceColor import eu.kanade.tachiyomi.util.system.openInBrowser @@ -24,13 +23,6 @@ class SettingsMainController : SettingsController() { val tintColor = context.getResourceColor(R.attr.colorAccent) - extensionPreference { - iconRes = R.drawable.ic_extension_black_24dp - iconTint = tintColor - titleRes = R.string.label_extensions - onClick { navigateTo(ExtensionController()) } - } - preference { iconRes = R.drawable.ic_tune_white_24dp iconTint = tintColor diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt index 1fb821f6a3..340e1751d4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt @@ -294,10 +294,12 @@ data class ViewPaddingState( ) -fun Controller.setOnQueryTextChangeListener(searchView: SearchView, f: (text: String?) -> Boolean) { +fun Controller.setOnQueryTextChangeListener(searchView: SearchView, onlyOnSubmit:Boolean = false, + f: (text: String?) -> Boolean) { searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener { override fun onQueryTextChange(newText: String?): Boolean { - if (router.backstack.lastOrNull()?.controller() == this@setOnQueryTextChangeListener) { + if (!onlyOnSubmit && router.backstack.lastOrNull()?.controller() == + this@setOnQueryTextChangeListener) { return f(newText) } return false @@ -324,10 +326,10 @@ fun Controller.scrollViewWith(recycler: RecyclerView, val headerHeight = insets.systemWindowInsetTop + array.getDimensionPixelSize(0, 0) view.updatePaddingRelative( top = headerHeight, - bottom = if (padBottom) insets.systemWindowInsetBottom else 0 + bottom = if (padBottom) insets.systemWindowInsetBottom else view.paddingBottom ) swipeRefreshLayout?.setProgressViewOffset(false, headerHeight + (-60).dpToPx, - headerHeight + 10.dpToPx) + headerHeight) statusBarHeight = insets.systemWindowInsetTop array.recycle() f?.invoke(insets) diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/ExtensionPreference.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/ExtensionPreference.kt index 3e64e84269..b57e4528df 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/ExtensionPreference.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/ExtensionPreference.kt @@ -28,7 +28,7 @@ class ExtensionPreference @JvmOverloads constructor(context: Context, attrs: Att val updates = Injekt.get().extensionUpdatesCount().getOrDefault() if (updates > 0) { extUpdateText.text = context.resources.getQuantityString(R.plurals - .extensions_updates_available, updates, updates) + .updates_available, updates, updates) extUpdateText.visible() } else { diff --git a/app/src/main/res/color/btn_bg_primary_selector.xml b/app/src/main/res/color/btn_bg_primary_selector.xml new file mode 100644 index 0000000000..15e0485f55 --- /dev/null +++ b/app/src/main/res/color/btn_bg_primary_selector.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_sync_black_24dp.xml b/app/src/main/res/drawable/ic_sync_black_24dp.xml index ce8796cb79..885909db8c 100644 --- a/app/src/main/res/drawable/ic_sync_black_24dp.xml +++ b/app/src/main/res/drawable/ic_sync_black_24dp.xml @@ -2,6 +2,7 @@ android:width="24dp" android:height="24dp" android:viewportWidth="24.0" + android:tint="?actionBarTintColor" android:viewportHeight="24.0"> + + + \ No newline at end of file diff --git a/app/src/main/res/layout/catalogue_main_controller.xml b/app/src/main/res/layout/catalogue_main_controller.xml index fbdbe9b2d1..79157f0c36 100644 --- a/app/src/main/res/layout/catalogue_main_controller.xml +++ b/app/src/main/res/layout/catalogue_main_controller.xml @@ -1,15 +1,44 @@ - + android:layout_height="match_parent" + xmlns:app="http://schemas.android.com/apk/res-auto"> - + android:layout_height="match_parent" + android:background="?android:attr/colorBackground"> - + + + + + + + + + + diff --git a/app/src/main/res/layout/catalouge_more_extensions_card_item.xml b/app/src/main/res/layout/catalouge_more_extensions_card_item.xml new file mode 100644 index 0000000000..855ef67e13 --- /dev/null +++ b/app/src/main/res/layout/catalouge_more_extensions_card_item.xml @@ -0,0 +1,43 @@ + + + + + + + +