Added new search functionality to module screen

This commit is contained in:
Viktor De Pasquale 2019-11-14 18:56:03 +01:00
parent c69dcf3e20
commit 9d1d1710eb
14 changed files with 395 additions and 56 deletions

View File

@ -8,7 +8,12 @@ import com.topjohnwu.magisk.model.entity.module.Repo
interface RepoBase { interface RepoBase {
fun getRepos(offset: Int, limit: Int = 10): List<Repo> fun getRepos(offset: Int, limit: Int = LIMIT): List<Repo>
fun searchRepos(query: String, offset: Int, limit: Int = LIMIT): List<Repo>
companion object {
const val LIMIT = 10
}
} }
@ -18,6 +23,19 @@ interface RepoByUpdatedDao : RepoBase {
@Query("SELECT * FROM repos ORDER BY last_update DESC LIMIT :limit OFFSET :offset") @Query("SELECT * FROM repos ORDER BY last_update DESC LIMIT :limit OFFSET :offset")
override fun getRepos(offset: Int, limit: Int): List<Repo> override fun getRepos(offset: Int, limit: Int): List<Repo>
@Query(
"""SELECT *
FROM repos
WHERE
(author LIKE '%' || :query || '%') ||
(name LIKE '%' || :query || '%') ||
(description LIKE '%' || :query || '%')
ORDER BY last_update DESC
LIMIT :limit
OFFSET :offset"""
)
override fun searchRepos(query: String, offset: Int, limit: Int): List<Repo>
} }
@Dao @Dao
@ -26,4 +44,18 @@ interface RepoByNameDao : RepoBase {
@Query("SELECT * FROM repos ORDER BY name COLLATE NOCASE LIMIT :limit OFFSET :offset") @Query("SELECT * FROM repos ORDER BY name COLLATE NOCASE LIMIT :limit OFFSET :offset")
override fun getRepos(offset: Int, limit: Int): List<Repo> override fun getRepos(offset: Int, limit: Int): List<Repo>
@Query(
"""SELECT *
FROM repos
WHERE
(author LIKE '%' || :query || '%') ||
(name LIKE '%' || :query || '%') ||
(description LIKE '%' || :query || '%')
ORDER BY name COLLATE NOCASE
LIMIT :limit
OFFSET :offset"""
)
override fun searchRepos(query: String, offset: Int, limit: Int): List<Repo>
} }

View File

@ -116,19 +116,7 @@ open class MainActivity : CompatActivity<MainViewModel, ActivityMainMd2Binding>(
transactionType: FragNavController.TransactionType transactionType: FragNavController.TransactionType
) { ) {
setDisplayHomeAsUpEnabled(!navigation.isRoot) setDisplayHomeAsUpEnabled(!navigation.isRoot)
requestNavigationHidden(!navigation.isRoot)
val lapam = binding.mainBottomBar.layoutParams as ViewGroup.MarginLayoutParams
val height = binding.mainBottomBar.measuredHeight
val verticalMargin = lapam.let { it.topMargin + it.bottomMargin }
val maxTranslation = height + verticalMargin
val translation = if (navigation.isRoot) 0 else maxTranslation
binding.mainBottomBar.animate()
.translationY(translation.toFloat())
.setInterpolator(FastOutSlowInInterpolator())
.withStartAction { if (navigation.isRoot) binding.mainBottomBar.isVisible = true }
.withEndAction { if (!navigation.isRoot) binding.mainBottomBar.isVisible = false }
.start()
} }
override fun peekSystemWindowInsets(insets: Insets) { override fun peekSystemWindowInsets(insets: Insets) {
@ -143,4 +131,19 @@ open class MainActivity : CompatActivity<MainViewModel, ActivityMainMd2Binding>(
} }
} }
internal fun requestNavigationHidden(hide: Boolean = true) {
val lapam = binding.mainBottomBar.layoutParams as ViewGroup.MarginLayoutParams
val height = binding.mainBottomBar.measuredHeight
val verticalMargin = lapam.let { it.topMargin + it.bottomMargin }
val maxTranslation = height + verticalMargin
val translation = if (!hide) 0 else maxTranslation
binding.mainBottomBar.animate()
.translationY(translation.toFloat())
.setInterpolator(FastOutSlowInInterpolator())
.withStartAction { if (!hide) binding.mainBottomBar.isVisible = true }
.withEndAction { if (hide) binding.mainBottomBar.isVisible = false }
.start()
}
} }

View File

@ -18,7 +18,7 @@ abstract class CompatFragment<ViewModel : CompatViewModel, Binding : ViewDataBin
private val delegate by lazy { CompatDelegate(this) } private val delegate by lazy { CompatDelegate(this) }
private val compatActivity get() = requireActivity() as CompatActivity<*, *> protected val compatActivity get() = requireActivity() as CompatActivity<*, *>
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)

View File

@ -0,0 +1,26 @@
package com.topjohnwu.magisk.redesign.compat
import android.os.Handler
import android.os.Looper
interface Queryable {
val queryDelay: Long
val queryHandler: Handler
val queryRunnable: Runnable
fun submitQuery()
companion object {
fun impl(delay: Long = 1000L) = object : Queryable {
override val queryDelay = delay
override val queryHandler = Handler(Looper.getMainLooper())
override val queryRunnable = Runnable { TODO() }
override fun submitQuery() {
queryHandler.removeCallbacks(queryRunnable)
queryHandler.postDelayed(queryRunnable, queryDelay)
}
}
}
}

View File

@ -1,8 +1,6 @@
package com.topjohnwu.magisk.redesign.hide package com.topjohnwu.magisk.redesign.hide
import android.content.pm.ApplicationInfo import android.content.pm.ApplicationInfo
import android.os.Handler
import android.os.Looper
import androidx.databinding.Bindable import androidx.databinding.Bindable
import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.data.repository.MagiskRepository import com.topjohnwu.magisk.data.repository.MagiskRepository
@ -16,20 +14,18 @@ import com.topjohnwu.magisk.model.entity.StatefulProcess
import com.topjohnwu.magisk.model.entity.recycler.HideItem import com.topjohnwu.magisk.model.entity.recycler.HideItem
import com.topjohnwu.magisk.model.entity.recycler.HideProcessItem import com.topjohnwu.magisk.model.entity.recycler.HideProcessItem
import com.topjohnwu.magisk.redesign.compat.CompatViewModel import com.topjohnwu.magisk.redesign.compat.CompatViewModel
import com.topjohnwu.magisk.redesign.compat.Queryable
import com.topjohnwu.magisk.redesign.home.itemBindingOf import com.topjohnwu.magisk.redesign.home.itemBindingOf
import com.topjohnwu.magisk.utils.DiffObservableList import com.topjohnwu.magisk.utils.DiffObservableList
import com.topjohnwu.magisk.utils.FilterableDiffObservableList import com.topjohnwu.magisk.utils.FilterableDiffObservableList
import com.topjohnwu.magisk.utils.KObservableField import com.topjohnwu.magisk.utils.KObservableField
import com.topjohnwu.magisk.utils.currentLocale import com.topjohnwu.magisk.utils.currentLocale
import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers
class HideViewModel( class HideViewModel(
private val magiskRepo: MagiskRepository private val magiskRepo: MagiskRepository
) : CompatViewModel() { ) : CompatViewModel(), Queryable by Queryable.impl(1000) {
private val queryHandler = Handler(Looper.getMainLooper()) override val queryRunnable = Runnable { query() }
private val queryRunnable = Runnable { query() }
var isShowSystem = false var isShowSystem = false
@Bindable get @Bindable get
@ -67,7 +63,7 @@ class HideViewModel(
.applyViewModel(this) .applyViewModel(this)
.subscribeK { .subscribeK {
items.update(it.first, it.second) items.update(it.first, it.second)
query() submitQuery()
} }
// --- // ---
@ -87,9 +83,9 @@ class HideViewModel(
// --- // ---
private fun submitQuery() { override fun submitQuery() {
queryHandler.removeCallbacks(queryRunnable) queryHandler.removeCallbacks(queryRunnable)
queryHandler.postDelayed(queryRunnable, 1000) queryHandler.postDelayed(queryRunnable, queryDelay)
} }
private fun query( private fun query(
@ -130,7 +126,7 @@ class HideViewModel(
} }
inline fun <T : ComparableRvItem<T>> filterableListOf( inline fun <T : ComparableRvItem<*>> filterableListOf(
vararg newItems: T vararg newItems: T
) = FilterableDiffObservableList(object : DiffObservableList.Callback<T> { ) = FilterableDiffObservableList(object : DiffObservableList.Callback<T> {
override fun areItemsTheSame(oldItem: T, newItem: T) = oldItem.genericItemSameAs(newItem) override fun areItemsTheSame(oldItem: T, newItem: T) = oldItem.genericItemSameAs(newItem)

View File

@ -5,7 +5,10 @@ import android.os.Bundle
import android.view.View import android.view.View
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.databinding.FragmentModuleMd2Binding import com.topjohnwu.magisk.databinding.FragmentModuleMd2Binding
import com.topjohnwu.magisk.redesign.MainActivity
import com.topjohnwu.magisk.redesign.compat.CompatFragment import com.topjohnwu.magisk.redesign.compat.CompatFragment
import com.topjohnwu.magisk.redesign.compat.hideKeyboard
import com.topjohnwu.magisk.redesign.hide.MotionRevealHelper
import com.topjohnwu.magisk.utils.EndlessRecyclerScrollListener import com.topjohnwu.magisk.utils.EndlessRecyclerScrollListener
import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel
@ -14,7 +17,7 @@ class ModuleFragment : CompatFragment<ModuleViewModel, FragmentModuleMd2Binding>
override val layoutRes = R.layout.fragment_module_md2 override val layoutRes = R.layout.fragment_module_md2
override val viewModel by viewModel<ModuleViewModel>() override val viewModel by viewModel<ModuleViewModel>()
private lateinit var listener: EndlessRecyclerScrollListener private val listeners = hashSetOf<EndlessRecyclerScrollListener>()
override fun consumeSystemWindowInsets(insets: Insets) = insets override fun consumeSystemWindowInsets(insets: Insets) = insets
@ -26,21 +29,45 @@ class ModuleFragment : CompatFragment<ModuleViewModel, FragmentModuleMd2Binding>
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
setEndlessScroller() setEndlessScroller()
setEndlessSearch()
binding.moduleFilterToggle.setOnClickListener {
(activity as? MainActivity)?.requestNavigationHidden()
MotionRevealHelper.withViews(binding.moduleFilter, binding.moduleFilterToggle, true)
}
binding.moduleFilterInclude.moduleFilterDone.setOnClickListener {
(activity as? MainActivity)?.requestNavigationHidden(false)
hideKeyboard()
MotionRevealHelper.withViews(binding.moduleFilter, binding.moduleFilterToggle, false)
}
} }
override fun onDestroyView() { override fun onDestroyView() {
if (this::listener.isInitialized) { listeners.forEach {
binding.moduleRemote.removeOnScrollListener(listener) binding.moduleRemote.removeOnScrollListener(it)
binding.moduleFilterInclude.moduleFilterList.removeOnScrollListener(it)
} }
super.onDestroyView() super.onDestroyView()
} }
override fun onPreBind(binding: FragmentModuleMd2Binding) = Unit
private fun setEndlessScroller() { private fun setEndlessScroller() {
val lama = binding.moduleRemote.layoutManager ?: return val lama = binding.moduleRemote.layoutManager ?: return
lama.isAutoMeasureEnabled = false lama.isAutoMeasureEnabled = false
listener = EndlessRecyclerScrollListener(lama, viewModel::loadRemote) val listener = EndlessRecyclerScrollListener(lama, viewModel::loadRemote)
binding.moduleRemote.addOnScrollListener(listener) binding.moduleRemote.addOnScrollListener(listener)
listeners.add(listener)
}
private fun setEndlessSearch() {
val lama = binding.moduleFilterInclude.moduleFilterList.layoutManager ?: return
lama.isAutoMeasureEnabled = false
val listener = EndlessRecyclerScrollListener(lama, viewModel::loadMoreQuery)
binding.moduleFilterInclude.moduleFilterList.addOnScrollListener(listener)
listeners.add(listener)
} }
} }

View File

@ -1,6 +1,7 @@
package com.topjohnwu.magisk.redesign.module package com.topjohnwu.magisk.redesign.module
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import androidx.databinding.Bindable
import androidx.databinding.ViewDataBinding import androidx.databinding.ViewDataBinding
import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.Config import com.topjohnwu.magisk.Config
@ -19,11 +20,14 @@ import com.topjohnwu.magisk.model.entity.recycler.RepoItem
import com.topjohnwu.magisk.model.entity.recycler.SectionTitle import com.topjohnwu.magisk.model.entity.recycler.SectionTitle
import com.topjohnwu.magisk.model.events.dialog.ModuleInstallDialog import com.topjohnwu.magisk.model.events.dialog.ModuleInstallDialog
import com.topjohnwu.magisk.redesign.compat.CompatViewModel import com.topjohnwu.magisk.redesign.compat.CompatViewModel
import com.topjohnwu.magisk.redesign.compat.Queryable
import com.topjohnwu.magisk.redesign.home.itemBindingOf import com.topjohnwu.magisk.redesign.home.itemBindingOf
import com.topjohnwu.magisk.redesign.superuser.diffListOf import com.topjohnwu.magisk.redesign.superuser.diffListOf
import com.topjohnwu.magisk.tasks.RepoUpdater import com.topjohnwu.magisk.tasks.RepoUpdater
import com.topjohnwu.magisk.utils.KObservableField
import com.topjohnwu.magisk.utils.currentLocale import com.topjohnwu.magisk.utils.currentLocale
import io.reactivex.Single import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposable
import me.tatarka.bindingcollectionadapter2.BindingRecyclerViewAdapter import me.tatarka.bindingcollectionadapter2.BindingRecyclerViewAdapter
import timber.log.Timber import timber.log.Timber
@ -33,7 +37,27 @@ class ModuleViewModel(
private val repoName: RepoByNameDao, private val repoName: RepoByNameDao,
private val repoUpdated: RepoByUpdatedDao, private val repoUpdated: RepoByUpdatedDao,
private val repoUpdater: RepoUpdater private val repoUpdater: RepoUpdater
) : CompatViewModel() { ) : CompatViewModel(), Queryable by Queryable.impl(1000) {
override val queryRunnable = Runnable { query() }
var query = ""
@Bindable get
set(value) {
if (field == value) return
field = value
notifyPropertyChanged(BR.query)
submitQuery()
// Yes we do lie about the search being loaded
searchLoading.value = true
}
private var queryJob: Disposable? = null
val searchLoading = KObservableField(false)
val itemsSearch = diffListOf<RepoItem>()
val itemSearchBinding = itemBindingOf<RepoItem> {
it.bindExtra(BR.viewModel, this)
}
val adapter = adapterOf<ComparableRvItem<*>>() val adapter = adapterOf<ComparableRvItem<*>>()
val items = diffListOf<ComparableRvItem<*>>() val items = diffListOf<ComparableRvItem<*>>()
@ -102,7 +126,7 @@ class ModuleViewModel(
return return
} }
remoteJob = Single.fromCallable { itemsRemote.size } remoteJob = Single.fromCallable { itemsRemote.size }
.flatMap { loadRepos(offset = it) } .flatMap { loadRemoteInternal(offset = it) }
.map { it.map { RepoItem(it) } } .map { it.map { RepoItem(it) } }
.subscribeK(onError = { .subscribeK(onError = {
Timber.e(it) Timber.e(it)
@ -114,14 +138,49 @@ class ModuleViewModel(
} }
} }
private fun loadRepos( // ---
override fun submitQuery() {
queryHandler.removeCallbacks(queryRunnable)
queryHandler.postDelayed(queryRunnable, queryDelay)
}
private fun queryInternal(query: String, offset: Int): Single<List<RepoItem>> {
if (query.isBlank()) {
return Single.just(listOf<RepoItem>())
.doOnSubscribe { itemsSearch.clear() }
.subscribeOn(AndroidSchedulers.mainThread())
}
return Single.fromCallable { dao.searchRepos(query, offset) }
.map { it.map { RepoItem(it) } }
}
private fun query(query: String = this.query, offset: Int = 0) {
queryJob?.dispose()
queryJob = queryInternal(query, offset)
.map { it to itemsSearch.calculateDiff(it) }
.observeOn(AndroidSchedulers.mainThread())
.doOnSuccess { searchLoading.value = false }
.subscribeK { itemsSearch.update(it.first, it.second) }
}
@Synchronized
fun loadMoreQuery() {
if (queryJob?.isDisposed == false) return
queryJob = queryInternal(query, itemsSearch.size)
.subscribeK { itemsSearch.addAll(it) }
}
// ---
private fun loadRemoteInternal(
offset: Int = 0, offset: Int = 0,
downloadRepos: Boolean = offset == 0 downloadRepos: Boolean = offset == 0
): Single<List<Repo>> = Single.fromCallable { dao.getRepos(offset) }.flatMap { ): Single<List<Repo>> = Single.fromCallable { dao.getRepos(offset) }.flatMap {
when { when {
// in case we find result empty and offset is initial we need to refresh the repos. // in case we find result empty and offset is initial we need to refresh the repos.
downloadRepos && it.isEmpty() && offset == 0 -> downloadRepos() downloadRepos && it.isEmpty() && offset == 0 -> downloadRepos()
.andThen(loadRepos(downloadRepos = false)) .andThen(loadRemoteInternal(downloadRepos = false))
else -> Single.just(it) else -> Single.just(it)
} }
} }
@ -137,7 +196,8 @@ class ModuleViewModel(
.sortedBy { it.item.name.toLowerCase(currentLocale) } .sortedBy { it.item.name.toLowerCase(currentLocale) }
.toList() .toList()
private fun update(repo: Repo, progress: Int) = Single.fromCallable { itemsRemote } private fun update(repo: Repo, progress: Int) =
Single.fromCallable { itemsRemote + itemsSearch }
.map { it.first { it.item.id == repo.id } } .map { it.first { it.item.id == repo.id } }
.subscribeK { it.progress.value = progress } .subscribeK { it.progress.value = progress }
.add() .add()

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:angle="270"
android:endColor="?colorSurfaceVariant"
android:startColor="@android:color/transparent" />
</shape>

View File

@ -15,6 +15,10 @@
</data> </data>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/module_remote" android:id="@+id/module_remote"
adapter="@{viewModel.adapter}" adapter="@{viewModel.adapter}"
@ -34,4 +38,38 @@
app:spanCount="2" app:spanCount="2"
tools:listitem="@layout/item_module_md2" /> tools:listitem="@layout/item_module_md2" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/module_filter_toggle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginStart="@dimen/l1"
android:layout_marginEnd="@dimen/l1"
android:layout_marginBottom="@{viewModel.insets.bottom + (int) @dimen/l1}"
app:backgroundTint="?colorSurface"
app:srcCompat="@drawable/ic_filter"
app:tint="?colorPrimary"
tools:layout_marginBottom="64dp" />
<com.google.android.material.circularreveal.cardview.CircularRevealCardView
android:id="@+id/module_filter"
style="?styleCardVariant"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="bottom"
android:visibility="invisible"
app:cardCornerRadius="0dp">
<include
android:id="@+id/module_filter_include"
layout="@layout/include_module_filter"
viewModel="@{viewModel}"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</com.google.android.material.circularreveal.cardview.CircularRevealCardView>
</FrameLayout>
</layout> </layout>

View File

@ -0,0 +1,131 @@
<?xml version="1.0" encoding="utf-8"?>
<layout 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">
<data>
<import type="com.topjohnwu.magisk.R" />
<variable
name="viewModel"
type="com.topjohnwu.magisk.redesign.module.ModuleViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:orientation="vertical"
android:paddingBottom="@{viewModel.insets.bottom + (int) @dimen/l1}"
tools:layout_gravity="bottom"
tools:paddingBottom="64dp">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/module_filter_list"
dividerHorizontal="@{R.drawable.divider_l1}"
dividerVertical="@{R.drawable.divider_l1}"
itemBinding="@{viewModel.itemSearchBinding}"
items="@{viewModel.itemsSearch}"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginBottom="@dimen/l1"
android:clipToPadding="false"
android:orientation="vertical"
android:paddingStart="@dimen/l1"
android:paddingTop="@{viewModel.insets.top + (int) @dimen/internal_action_bar_size + (int) @dimen/l1}"
app:layoutManager="androidx.recyclerview.widget.StaggeredGridLayoutManager"
app:layout_constrainedHeight="true"
app:layout_constraintBottom_toTopOf="@+id/module_filter_title_search"
app:layout_constraintTop_toTopOf="parent"
app:reverseLayout="false"
app:spanCount="2"
tools:listitem="@layout/item_repo_md2" />
<View
android:layout_width="match_parent"
android:layout_height="@dimen/l_50"
android:background="@drawable/bg_shadow"
app:layout_constraintBottom_toBottomOf="@+id/module_filter_list" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/module_filter_title_search"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/l1"
android:layout_marginEnd="@dimen/l1"
android:layout_marginBottom="@dimen/l1"
android:text="@string/hide_search"
android:textAllCaps="true"
android:textAppearance="?appearanceTextCaptionNormal"
android:textColor="?colorPrimary"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/module_filter_search" />
<com.google.android.material.card.MaterialCardView
android:id="@+id/module_filter_search"
style="?styleCardNormal"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/l1"
android:layout_marginEnd="@dimen/l1"
android:layout_marginBottom="@dimen/l_50"
app:cardCornerRadius="18dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/module_filter_done"
app:layout_constraintStart_toStartOf="parent">
<androidx.appcompat.widget.AppCompatImageView
style="?styleIconNormal"
android:layout_width="48dp"
android:layout_height="36dp"
android:layout_gravity="center_vertical|start"
android:padding="6dp"
app:srcCompat="@drawable/ic_search_md2"
app:tint="?colorDisabled" />
<androidx.appcompat.widget.AppCompatEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="48dp"
android:background="@null"
android:hint="@string/hide_filter_hint"
android:inputType="textUri"
android:minHeight="36dp"
android:paddingStart="0dp"
android:paddingEnd="@dimen/l1"
android:singleLine="true"
android:text="@={viewModel.query}"
android:textAppearance="?appearanceTextBodyNormal"
android:textColor="?colorTextTransient"
android:textColorHint="?colorOnSurfaceVariant" />
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/module_filter_done"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/l1"
app:backgroundTint="?colorPrimary"
app:elevation="0dp"
app:fabSize="mini"
app:layout_constraintBottom_toBottomOf="@+id/module_filter_search"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/module_filter_search"
app:srcCompat="@drawable/ic_check_md2" />
<ProgressBar
style="?styleProgressIndeterminateCircular"
goneUnless="@{viewModel.searchLoading}"
android:layout_width="56dp"
android:layout_height="56dp"
app:layout_constraintBottom_toBottomOf="@+id/module_filter_done"
app:layout_constraintEnd_toEndOf="@+id/module_filter_done"
app:layout_constraintStart_toStartOf="@+id/module_filter_done"
app:layout_constraintTop_toTopOf="@+id/module_filter_done" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View File

@ -8,4 +8,11 @@
<item name="android:indeterminateTint">?colorPrimary</item> <item name="android:indeterminateTint">?colorPrimary</item>
</style> </style>
<style name="WidgetFoundation.ProgressBar.Indeterminate.Circular" parent="Widget.AppCompat.ProgressBar">
<item name="android:indeterminate">true</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_width">wrap_content</item>
<item name="android:indeterminateTint">?colorPrimary</item>
</style>
</resources> </resources>

View File

@ -50,6 +50,7 @@
<!--Progress--> <!--Progress-->
<attr name="styleProgressDeterminate" format="reference" /> <attr name="styleProgressDeterminate" format="reference" />
<attr name="styleProgressIndeterminate" format="reference" /> <attr name="styleProgressIndeterminate" format="reference" />
<attr name="styleProgressIndeterminateCircular" format="reference" />
<!--endregion--> <!--endregion-->

View File

@ -56,7 +56,11 @@
<item name="styleRadioNormal">@style/WidgetFoundation.RadioButton</item> <item name="styleRadioNormal">@style/WidgetFoundation.RadioButton</item>
<item name="styleProgressDeterminate">@style/WidgetFoundation.ProgressBar</item> <item name="styleProgressDeterminate">@style/WidgetFoundation.ProgressBar</item>
<item name="styleProgressIndeterminate">@style/WidgetFoundation.ProgressBar.Indeterminate <item name="styleProgressIndeterminate">
@style/WidgetFoundation.ProgressBar.Indeterminate
</item>
<item name="styleProgressIndeterminateCircular">
@style/WidgetFoundation.ProgressBar.Indeterminate.Circular
</item> </item>
<!--///--> <!--///-->

View File

@ -162,4 +162,10 @@ variant. Make sure to use style referenced by attribute defined it attrs.xml.
<item name="android:layout_width">100dp</item> <item name="android:layout_width">100dp</item>
</style> </style>
<style name="WidgetFoundation.ProgressBar.Indeterminate.Circular" parent="Widget.AppCompat.ProgressBar">
<item name="android:indeterminate">true</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_width">wrap_content</item>
</style>
</resources> </resources>