mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-12-23 20:31:51 +01:00
Extensions now in a bottom sheet on the browse tab
Boy I love bottom sheets guys
This commit is contained in:
parent
1f91ad0a07
commit
e68a1ae48d
@ -13,7 +13,7 @@ import com.bluelinelabs.conductor.ControllerChangeHandler
|
|||||||
import com.bluelinelabs.conductor.ControllerChangeType
|
import com.bluelinelabs.conductor.ControllerChangeType
|
||||||
import com.bluelinelabs.conductor.RouterTransaction
|
import com.bluelinelabs.conductor.RouterTransaction
|
||||||
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler
|
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.FlexibleAdapter
|
||||||
import eu.davidea.flexibleadapter.items.IFlexible
|
import eu.davidea.flexibleadapter.items.IFlexible
|
||||||
import eu.kanade.tachiyomi.R
|
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.browse.BrowseCatalogueController
|
||||||
import eu.kanade.tachiyomi.ui.catalogue.global_search.CatalogueSearchController
|
import eu.kanade.tachiyomi.ui.catalogue.global_search.CatalogueSearchController
|
||||||
import eu.kanade.tachiyomi.ui.catalogue.latest.LatestUpdatesController
|
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.main.RootSearchInterface
|
||||||
import eu.kanade.tachiyomi.ui.setting.SettingsSourcesController
|
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.applyWindowInsetsForRootController
|
||||||
import eu.kanade.tachiyomi.util.view.scrollViewWith
|
import eu.kanade.tachiyomi.util.view.scrollViewWith
|
||||||
|
import eu.kanade.tachiyomi.util.view.setOnQueryTextChangeListener
|
||||||
import eu.kanade.tachiyomi.widget.preference.SourceLoginDialog
|
import eu.kanade.tachiyomi.widget.preference.SourceLoginDialog
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.android.parcel.Parcelize
|
||||||
import kotlinx.android.synthetic.main.catalogue_main_controller.*
|
import kotlinx.android.synthetic.main.catalogue_main_controller.*
|
||||||
|
import kotlinx.android.synthetic.main.extensions_bottom_sheet.*
|
||||||
import kotlinx.android.synthetic.main.main_activity.*
|
import kotlinx.android.synthetic.main.main_activity.*
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
import kotlin.math.max
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This controller shows and manages the different catalogues enabled by the user.
|
* This controller shows and manages the different catalogues enabled by the user.
|
||||||
@ -62,6 +65,13 @@ class CatalogueController : NucleusController<CataloguePresenter>(),
|
|||||||
*/
|
*/
|
||||||
private var adapter: CatalogueAdapter? = null
|
private var adapter: CatalogueAdapter? = null
|
||||||
|
|
||||||
|
var extQuery = ""
|
||||||
|
private set
|
||||||
|
|
||||||
|
var headerHeight = 0
|
||||||
|
|
||||||
|
var customTitle = ""
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when controller is initialized.
|
* Called when controller is initialized.
|
||||||
*/
|
*/
|
||||||
@ -76,7 +86,9 @@ class CatalogueController : NucleusController<CataloguePresenter>(),
|
|||||||
* @return title.
|
* @return title.
|
||||||
*/
|
*/
|
||||||
override fun getTitle(): String? {
|
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<CataloguePresenter>(),
|
|||||||
recycler.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(view.context)
|
recycler.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(view.context)
|
||||||
recycler.adapter = adapter
|
recycler.adapter = adapter
|
||||||
recycler.addItemDecoration(SourceDividerItemDecoration(view.context))
|
recycler.addItemDecoration(SourceDividerItemDecoration(view.context))
|
||||||
recycler.setOnApplyWindowInsetsListener(RecyclerWindowInsetsListener)
|
val attrsArray = intArrayOf(android.R.attr.actionBarSize)
|
||||||
|
val array = view.context.obtainStyledAttributes(attrsArray)
|
||||||
scrollViewWith(recycler)
|
val appBarHeight = array.getDimensionPixelSize(0, 0)
|
||||||
|
array.recycle()
|
||||||
|
scrollViewWith(recycler) {
|
||||||
|
headerHeight = it.systemWindowInsetTop + appBarHeight
|
||||||
|
}
|
||||||
|
|
||||||
requestPermissionsSafe(arrayOf(WRITE_EXTERNAL_STORAGE), 301)
|
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) {
|
override fun onDestroyView(view: View) {
|
||||||
@ -129,6 +171,7 @@ class CatalogueController : NucleusController<CataloguePresenter>(),
|
|||||||
override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) {
|
override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) {
|
||||||
super.onChangeStarted(handler, type)
|
super.onChangeStarted(handler, type)
|
||||||
if (!type.isPush && handler is SettingsSourcesFadeChangeHandler) {
|
if (!type.isPush && handler is SettingsSourcesFadeChangeHandler) {
|
||||||
|
ext_bottom_sheet.updateExtTitle()
|
||||||
presenter.updateSources()
|
presenter.updateSources()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -192,20 +235,41 @@ class CatalogueController : NucleusController<CataloguePresenter>(),
|
|||||||
* @param inflater used to load the menu xml.
|
* @param inflater used to load the menu xml.
|
||||||
*/
|
*/
|
||||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||||
// Inflate menu
|
if (ext_bottom_sheet.sheetBehavior?.state == BottomSheetBehavior.STATE_EXPANDED) {
|
||||||
inflater.inflate(R.menu.catalogue_main, menu)
|
// Inflate menu
|
||||||
|
inflater.inflate(R.menu.extension_main, menu)
|
||||||
|
|
||||||
// Initialize search option.
|
// Initialize search option.
|
||||||
val searchItem = menu.findItem(R.id.action_search)
|
val searchItem = menu.findItem(R.id.action_search)
|
||||||
val searchView = searchItem.actionView as SearchView
|
val searchView = searchItem.actionView as SearchView
|
||||||
|
|
||||||
// Change hint to show global search.
|
// Change hint to show global search.
|
||||||
searchView.queryHint = applicationContext?.getString(R.string.action_global_search_hint)
|
searchView.queryHint = applicationContext?.getString(R.string.search_extensions)
|
||||||
|
|
||||||
// Create query listener which opens the global search view.
|
// Create query listener which opens the global search view.
|
||||||
searchView.queryTextChangeEvents()
|
setOnQueryTextChangeListener(searchView) {
|
||||||
.filter { it.isSubmitted }
|
extQuery = it ?: ""
|
||||||
.subscribeUntilDestroy { performGlobalSearch(it.queryText().toString()) }
|
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){
|
private fun performGlobalSearch(query: String){
|
||||||
@ -222,9 +286,18 @@ class CatalogueController : NucleusController<CataloguePresenter>(),
|
|||||||
when (item.itemId) {
|
when (item.itemId) {
|
||||||
// Initialize option to open catalogue settings.
|
// Initialize option to open catalogue settings.
|
||||||
R.id.action_filter -> {
|
R.id.action_filter -> {
|
||||||
router.pushController((RouterTransaction.with(SettingsSourcesController()))
|
val controller =
|
||||||
.popChangeHandler(SettingsSourcesFadeChangeHandler())
|
if (ext_bottom_sheet.sheetBehavior?.state == BottomSheetBehavior.STATE_EXPANDED)
|
||||||
.pushChangeHandler(FadeChangeHandler()))
|
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)
|
else -> return super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ import rx.Subscription
|
|||||||
import rx.android.schedulers.AndroidSchedulers
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.util.*
|
import java.util.TreeMap
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -101,4 +101,4 @@ class CataloguePresenter(
|
|||||||
.sortedBy { "(${it.lang}) ${it.name}" } +
|
.sortedBy { "(${it.lang}) ${it.name}" } +
|
||||||
sourceManager.get(LocalSource.ID) as LocalSource
|
sourceManager.get(LocalSource.ID) as LocalSource
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -3,17 +3,19 @@ package eu.kanade.tachiyomi.ui.extension
|
|||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
import eu.davidea.flexibleadapter.items.IFlexible
|
import eu.davidea.flexibleadapter.items.IFlexible
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.ui.extension.ExtensionAdapter.OnButtonClickListener
|
||||||
import eu.kanade.tachiyomi.util.system.getResourceColor
|
import eu.kanade.tachiyomi.util.system.getResourceColor
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adapter that holds the catalogue cards.
|
* Adapter that holds the catalogue cards.
|
||||||
*
|
*
|
||||||
* @param controller instance of [ExtensionController].
|
* @param listener instance of [OnButtonClickListener].
|
||||||
*/
|
*/
|
||||||
class ExtensionAdapter(val controller: ExtensionController) :
|
class ExtensionAdapter(val listener: OnButtonClickListener) :
|
||||||
FlexibleAdapter<IFlexible<*>>(null, controller, true) {
|
FlexibleAdapter<IFlexible<*>>(null, listener, true) {
|
||||||
|
|
||||||
val cardBackground = controller.activity!!.getResourceColor(R.attr.background_card)
|
val cardBackground = (listener as ExtensionBottomSheet).context.getResourceColor(R.attr
|
||||||
|
.background_card)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
setDisplayHeadersAtStartUp(true)
|
setDisplayHeadersAtStartUp(true)
|
||||||
@ -22,7 +24,7 @@ class ExtensionAdapter(val controller: ExtensionController) :
|
|||||||
/**
|
/**
|
||||||
* Listener for browse item clicks.
|
* Listener for browse item clicks.
|
||||||
*/
|
*/
|
||||||
val buttonClickListener: ExtensionAdapter.OnButtonClickListener = controller
|
val buttonClickListener: ExtensionAdapter.OnButtonClickListener = listener
|
||||||
|
|
||||||
interface OnButtonClickListener {
|
interface OnButtonClickListener {
|
||||||
fun onButtonClick(position: Int)
|
fun onButtonClick(position: Int)
|
||||||
|
@ -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<ExtensionItem>()
|
||||||
|
|
||||||
|
private var currentDownloads = hashMapOf<String, InstallStep>()
|
||||||
|
|
||||||
|
fun onCreate() {
|
||||||
|
extensionManager.findAvailableExtensions()
|
||||||
|
bindToExtensionsObservable()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bindToExtensionsObservable(): Subscription {
|
||||||
|
val installedObservable = extensionManager.getInstalledExtensionsObservable()
|
||||||
|
val untrustedObservable = extensionManager.getUntrustedExtensionsObservable()
|
||||||
|
val availableObservable = extensionManager.getAvailableExtensionsObservable()
|
||||||
|
.startWith(emptyList<Extension.Available>())
|
||||||
|
|
||||||
|
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<ExtensionItem> {
|
||||||
|
val context = Injekt.get<Application>()
|
||||||
|
val activeLangs = preferences.enabledLanguages().getOrDefault()
|
||||||
|
|
||||||
|
val (installed, untrusted, available) = tuple
|
||||||
|
|
||||||
|
val items = mutableListOf<ExtensionItem>()
|
||||||
|
|
||||||
|
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<InstallStep>.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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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<IFlexible<*>>? = null
|
||||||
|
|
||||||
|
val presenter = ExtensionBottomPresenter(this)
|
||||||
|
|
||||||
|
private var extensions: List<ExtensionItem> = 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<LayoutParams> {
|
||||||
|
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<ExtensionItem>) {
|
||||||
|
//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<Boolean>) : AbstractHeaderItem<AutoCheckItem.AutoCheckHolder>() {
|
||||||
|
|
||||||
|
override fun getLayoutRes(): Int {
|
||||||
|
return R.layout.auto_ext_checkbox
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createViewHolder(
|
||||||
|
view: View, adapter: FlexibleAdapter<IFlexible<*>>
|
||||||
|
): AutoCheckHolder {
|
||||||
|
return AutoCheckHolder(view, adapter, autoCheck)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun bindViewHolder(
|
||||||
|
adapter: FlexibleAdapter<IFlexible<*>>,
|
||||||
|
holder: AutoCheckHolder,
|
||||||
|
position: Int,
|
||||||
|
payloads: MutableList<Any?>?
|
||||||
|
) {
|
||||||
|
//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<IFlexible<*>>,
|
||||||
|
autoCheck: Preference<Boolean>) :
|
||||||
|
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<Boolean>) {
|
||||||
|
isChecked = pref.getOrDefault()
|
||||||
|
setOnCheckedChangeListener { _, isChecked -> pref.set(isChecked) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -17,7 +17,7 @@ import uy.kohesive.injekt.Injekt
|
|||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
private typealias ExtensionTuple
|
typealias ExtensionTuple
|
||||||
= Triple<List<Extension.Installed>, List<Extension.Untrusted>, List<Extension.Available>>
|
= Triple<List<Extension.Installed>, List<Extension.Untrusted>, List<Extension.Available>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -3,18 +3,18 @@ package eu.kanade.tachiyomi.ui.extension
|
|||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import com.afollestad.materialdialogs.MaterialDialog
|
import com.afollestad.materialdialogs.MaterialDialog
|
||||||
import com.bluelinelabs.conductor.Controller
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
||||||
|
|
||||||
class ExtensionTrustDialog<T>(bundle: Bundle? = null) : DialogController(bundle)
|
class ExtensionTrustDialog<T>(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 {
|
constructor(target: T, signatureHash: String, pkgName: String) : this(Bundle().apply {
|
||||||
putString(SIGNATURE_KEY, signatureHash)
|
putString(SIGNATURE_KEY, signatureHash)
|
||||||
putString(PKGNAME_KEY, pkgName)
|
putString(PKGNAME_KEY, pkgName)
|
||||||
}) {
|
}) {
|
||||||
targetController = target
|
listener = target
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
|
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
|
||||||
@ -22,10 +22,10 @@ class ExtensionTrustDialog<T>(bundle: Bundle? = null) : DialogController(bundle)
|
|||||||
.title(R.string.untrusted_extension)
|
.title(R.string.untrusted_extension)
|
||||||
.message(R.string.untrusted_extension_message)
|
.message(R.string.untrusted_extension_message)
|
||||||
.positiveButton(R.string.ext_trust) {
|
.positiveButton(R.string.ext_trust) {
|
||||||
(targetController as? Listener)?.trustSignature(args.getString(SIGNATURE_KEY)!!)
|
listener.trustSignature(args.getString(SIGNATURE_KEY)!!)
|
||||||
}
|
}
|
||||||
.negativeButton(R.string.ext_uninstall) {
|
.negativeButton(R.string.ext_uninstall) {
|
||||||
(targetController as? Listener)?.uninstallExtension(args.getString(PKGNAME_KEY)!!)
|
listener.uninstallExtension(args.getString(PKGNAME_KEY)!!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,7 +8,6 @@ import com.bluelinelabs.conductor.Controller
|
|||||||
import eu.kanade.tachiyomi.BuildConfig
|
import eu.kanade.tachiyomi.BuildConfig
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
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.ui.migration.MigrationController
|
||||||
import eu.kanade.tachiyomi.util.system.getResourceColor
|
import eu.kanade.tachiyomi.util.system.getResourceColor
|
||||||
import eu.kanade.tachiyomi.util.system.openInBrowser
|
import eu.kanade.tachiyomi.util.system.openInBrowser
|
||||||
@ -24,13 +23,6 @@ class SettingsMainController : SettingsController() {
|
|||||||
|
|
||||||
val tintColor = context.getResourceColor(R.attr.colorAccent)
|
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 {
|
preference {
|
||||||
iconRes = R.drawable.ic_tune_white_24dp
|
iconRes = R.drawable.ic_tune_white_24dp
|
||||||
iconTint = tintColor
|
iconTint = tintColor
|
||||||
|
@ -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 {
|
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
||||||
override fun onQueryTextChange(newText: String?): Boolean {
|
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 f(newText)
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
@ -324,10 +326,10 @@ fun Controller.scrollViewWith(recycler: RecyclerView,
|
|||||||
val headerHeight = insets.systemWindowInsetTop + array.getDimensionPixelSize(0, 0)
|
val headerHeight = insets.systemWindowInsetTop + array.getDimensionPixelSize(0, 0)
|
||||||
view.updatePaddingRelative(
|
view.updatePaddingRelative(
|
||||||
top = headerHeight,
|
top = headerHeight,
|
||||||
bottom = if (padBottom) insets.systemWindowInsetBottom else 0
|
bottom = if (padBottom) insets.systemWindowInsetBottom else view.paddingBottom
|
||||||
)
|
)
|
||||||
swipeRefreshLayout?.setProgressViewOffset(false, headerHeight + (-60).dpToPx,
|
swipeRefreshLayout?.setProgressViewOffset(false, headerHeight + (-60).dpToPx,
|
||||||
headerHeight + 10.dpToPx)
|
headerHeight)
|
||||||
statusBarHeight = insets.systemWindowInsetTop
|
statusBarHeight = insets.systemWindowInsetTop
|
||||||
array.recycle()
|
array.recycle()
|
||||||
f?.invoke(insets)
|
f?.invoke(insets)
|
||||||
|
@ -28,7 +28,7 @@ class ExtensionPreference @JvmOverloads constructor(context: Context, attrs: Att
|
|||||||
val updates = Injekt.get<PreferencesHelper>().extensionUpdatesCount().getOrDefault()
|
val updates = Injekt.get<PreferencesHelper>().extensionUpdatesCount().getOrDefault()
|
||||||
if (updates > 0) {
|
if (updates > 0) {
|
||||||
extUpdateText.text = context.resources.getQuantityString(R.plurals
|
extUpdateText.text = context.resources.getQuantityString(R.plurals
|
||||||
.extensions_updates_available, updates, updates)
|
.updates_available, updates, updates)
|
||||||
extUpdateText.visible()
|
extUpdateText.visible()
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
6
app/src/main/res/color/btn_bg_primary_selector.xml
Normal file
6
app/src/main/res/color/btn_bg_primary_selector.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:state_enabled="false"
|
||||||
|
android:alpha="0.12" android:color="?attr/colorOnSurface" />
|
||||||
|
<item android:color="?colorPrimary" />
|
||||||
|
</selector>
|
@ -2,6 +2,7 @@
|
|||||||
android:width="24dp"
|
android:width="24dp"
|
||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
android:viewportWidth="24.0"
|
android:viewportWidth="24.0"
|
||||||
|
android:tint="?actionBarTintColor"
|
||||||
android:viewportHeight="24.0">
|
android:viewportHeight="24.0">
|
||||||
<path
|
<path
|
||||||
android:fillColor="#FF000000"
|
android:fillColor="#FF000000"
|
||||||
|
9
app/src/main/res/layout/auto_ext_checkbox.xml
Normal file
9
app/src/main/res/layout/auto_ext_checkbox.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<com.google.android.material.checkbox.MaterialCheckBox
|
||||||
|
android:id="@+id/auto_checkbox"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/action_auto_check_extensions">
|
||||||
|
|
||||||
|
</com.google.android.material.checkbox.MaterialCheckBox>
|
@ -1,15 +1,44 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<FrameLayout
|
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="match_parent"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<FrameLayout
|
||||||
android:id="@+id/recycler"
|
android:id="@+id/frame_layout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="match_parent"
|
||||||
android:clipToPadding="false"
|
android:background="?android:attr/colorBackground">
|
||||||
tools:listitem="@layout/catalogue_main_controller_card" />
|
|
||||||
|
|
||||||
</FrameLayout>
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/recycler"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:layout_marginBottom="30dp"
|
||||||
|
android:paddingBottom="20dp"
|
||||||
|
tools:listitem="@layout/catalogue_main_controller_card" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/shadow"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:alpha="0.5"
|
||||||
|
android:background="@drawable/shape_gradient_top_shadow"
|
||||||
|
android:paddingBottom="10dp"
|
||||||
|
app:layout_anchorGravity="top"
|
||||||
|
app:layout_anchor="@id/ext_bottom_sheet" />
|
||||||
|
<!-- Adding bottom sheet after main content -->
|
||||||
|
<include layout="@layout/extensions_bottom_sheet"/>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/shadow2"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="8dp"
|
||||||
|
android:layout_gravity="bottom"
|
||||||
|
android:alpha="0.25"
|
||||||
|
android:background="@drawable/shape_gradient_top_shadow" />
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
@ -0,0 +1,43 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<FrameLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:id="@+id/card"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/material_component_lists_two_line_height"
|
||||||
|
android:background="?attr/selectable_list_drawable">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/title"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:paddingStart="0dp"
|
||||||
|
android:paddingEnd="8dp"
|
||||||
|
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/source_browse"
|
||||||
|
tools:text="Source title" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/source_browse"
|
||||||
|
style="@style/Theme.Widget.Button.Borderless.Small"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/update_check_look_for_updates"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"/>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</FrameLayout>
|
@ -7,10 +7,10 @@
|
|||||||
android:id="@+id/ext_swipe_refresh">
|
android:id="@+id/ext_swipe_refresh">
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/ext_recycler"
|
android:id="@+id/ext_recycler"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:clipToPadding="false"
|
android:clipToPadding="false"
|
||||||
tools:listitem="@layout/extension_card_header"/>
|
tools:listitem="@layout/extension_card_header"/>
|
||||||
|
|
||||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||||
|
64
app/src/main/res/layout/extensions_bottom_sheet.xml
Normal file
64
app/src/main/res/layout/extensions_bottom_sheet.xml
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<eu.kanade.tachiyomi.ui.extension.ExtensionBottomSheet xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/ext_bottom_sheet"
|
||||||
|
style="@style/BottomSheetDialogTheme"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@drawable/bg_bottom_sheet_dialog_fragment"
|
||||||
|
android:backgroundTint="?android:attr/colorBackground"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:behavior_peekHeight="48sp"
|
||||||
|
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/sheet_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="@drawable/bg_bottom_sheet_dialog_fragment"
|
||||||
|
android:backgroundTint="?android:attr/colorPrimary"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/pill"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:alpha="0.25"
|
||||||
|
android:contentDescription="@string/drag_handle"
|
||||||
|
android:src="@drawable/draggable_pill"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
android:id="@+id/title_text"
|
||||||
|
style="@style/TextAppearance.AppCompat.Widget.ActionBar.Title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginBottom="10dp"
|
||||||
|
android:layout_marginTop="6dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:gravity="center"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textAlignment="center"
|
||||||
|
android:textColor="?actionBarTintColor"
|
||||||
|
android:textSize="18sp"
|
||||||
|
tools:text="Extensions" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/ext_recycler"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="?android:attr/colorBackground"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
tools:listitem="@layout/extension_card_header" />
|
||||||
|
</eu.kanade.tachiyomi.ui.extension.ExtensionBottomSheet>
|
@ -4,6 +4,7 @@
|
|||||||
<item
|
<item
|
||||||
android:id="@+id/action_search"
|
android:id="@+id/action_search"
|
||||||
android:icon="@drawable/ic_search_white_24dp"
|
android:icon="@drawable/ic_search_white_24dp"
|
||||||
|
android:visible="false"
|
||||||
android:title="@string/action_search"
|
android:title="@string/action_search"
|
||||||
app:actionViewClass="androidx.appcompat.widget.SearchView"
|
app:actionViewClass="androidx.appcompat.widget.SearchView"
|
||||||
app:showAsAction="collapseActionView|ifRoom" />
|
app:showAsAction="collapseActionView|ifRoom" />
|
||||||
@ -12,13 +13,20 @@
|
|||||||
android:id="@+id/action_filter"
|
android:id="@+id/action_filter"
|
||||||
android:title="@string/action_filter"
|
android:title="@string/action_filter"
|
||||||
android:icon="@drawable/ic_filter_list_white_24dp"
|
android:icon="@drawable/ic_filter_list_white_24dp"
|
||||||
app:showAsAction="ifRoom"/>
|
app:showAsAction="always"/>
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/action_auto_check"
|
android:id="@+id/action_auto_check"
|
||||||
android:title="@string/action_auto_check_extensions"
|
android:title="@string/action_auto_check_extensions"
|
||||||
android:icon="@drawable/ic_filter_list_white_24dp"
|
android:icon="@drawable/ic_sync_black_24dp"
|
||||||
|
android:visible="false"
|
||||||
android:checkable="true"
|
android:checkable="true"
|
||||||
app:showAsAction="never"/>
|
app:showAsAction="never"/>
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_dismiss"
|
||||||
|
android:title="@string/action_dismiss"
|
||||||
|
android:icon="@drawable/ic_close_white_24dp"
|
||||||
|
app:showAsAction="always"/>
|
||||||
|
|
||||||
</menu>
|
</menu>
|
||||||
|
@ -27,10 +27,14 @@
|
|||||||
<string name="label_help">Help</string>
|
<string name="label_help">Help</string>
|
||||||
<string name="unlock">Unlock</string>
|
<string name="unlock">Unlock</string>
|
||||||
<string name="unlock_library">Unlock to access Library</string>
|
<string name="unlock_library">Unlock to access Library</string>
|
||||||
<plurals name="extensions_updates_available">
|
<plurals name="updates_available">
|
||||||
<item quantity="one">Update available</item>
|
<item quantity="one">Update available</item>
|
||||||
<item quantity="other">%d updates available</item>
|
<item quantity="other">%d updates available</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
|
<plurals name="extensions_updates_available">
|
||||||
|
<item quantity="one">Extension update available</item>
|
||||||
|
<item quantity="other">%d extension updates available</item>
|
||||||
|
</plurals>
|
||||||
<plurals name="downloads_pending">
|
<plurals name="downloads_pending">
|
||||||
<item quantity="one">1 in queue</item>
|
<item quantity="one">1 in queue</item>
|
||||||
<item quantity="other">%d in queue</item>
|
<item quantity="other">%d in queue</item>
|
||||||
@ -139,6 +143,7 @@
|
|||||||
<string name="action_sort_by">Sort category by…</string>
|
<string name="action_sort_by">Sort category by…</string>
|
||||||
<string name="action_switch">Switch</string>
|
<string name="action_switch">Switch</string>
|
||||||
<string name="action_bug_report">Report a Bug</string>
|
<string name="action_bug_report">Report a Bug</string>
|
||||||
|
<string name="action_dismiss">Dismiss</string>
|
||||||
|
|
||||||
<!-- Operations -->
|
<!-- Operations -->
|
||||||
<string name="loading">Loading…</string>
|
<string name="loading">Loading…</string>
|
||||||
@ -474,6 +479,7 @@
|
|||||||
<string name="latest">Latest</string>
|
<string name="latest">Latest</string>
|
||||||
<string name="browse">Browse</string>
|
<string name="browse">Browse</string>
|
||||||
<string name="in_library">In Library</string>
|
<string name="in_library">In Library</string>
|
||||||
|
<string name="search_extensions">Search extensions…</string>
|
||||||
|
|
||||||
<!-- Manga activity -->
|
<!-- Manga activity -->
|
||||||
<string name="manga_not_in_db">This manga has been removed from the database.</string>
|
<string name="manga_not_in_db">This manga has been removed from the database.</string>
|
||||||
|
Loading…
Reference in New Issue
Block a user