WIP Search
This commit is contained in:
parent
095a9f5be3
commit
c2396dd1a3
5 changed files with 139 additions and 11 deletions
|
|
@ -9,12 +9,15 @@ import android.text.Html
|
|||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.widget.EditText
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
|
|
@ -92,6 +95,10 @@ class DetailActivity : AppCompatActivity(), NotificationFragment.NotificationSet
|
|||
private lateinit var messageBarPublishButton: FloatingActionButton
|
||||
private lateinit var messageBarExpandButton: ImageButton
|
||||
|
||||
// Search state
|
||||
private var searchView: SearchView? = null
|
||||
private var isSearchActive: Boolean = false
|
||||
|
||||
// Action mode stuff
|
||||
private var actionMode: ActionMode? = null
|
||||
private val actionModeCallback = object : ActionMode.Callback {
|
||||
|
|
@ -307,20 +314,39 @@ class DetailActivity : AppCompatActivity(), NotificationFragment.NotificationSet
|
|||
insets
|
||||
}
|
||||
|
||||
viewModel.list(subscriptionId).observe(this) {
|
||||
it?.let {
|
||||
// Observe filtered notifications (filtered by search query)
|
||||
val noSearchResultsText: TextView = findViewById(R.id.detail_no_notifications_text)
|
||||
val howToIntro: View = findViewById(R.id.detail_how_to_intro)
|
||||
val howToExampleView: View = findViewById(R.id.detail_how_to_example)
|
||||
val howToLinkView: View = findViewById(R.id.detail_how_to_link)
|
||||
viewModel.listFiltered(subscriptionId).observe(this) {
|
||||
it?.let { notifications ->
|
||||
// Show list view
|
||||
adapter.submitList(it as MutableList<Notification>)
|
||||
if (it.isEmpty()) {
|
||||
adapter.submitList(notifications.toMutableList())
|
||||
if (notifications.isEmpty()) {
|
||||
mainListContainer.visibility = View.GONE
|
||||
noEntriesText.visibility = View.VISIBLE
|
||||
// Show different text based on whether we're searching or not
|
||||
if (isSearchActive && viewModel.searchQuery.value?.isNotEmpty() == true) {
|
||||
noSearchResultsText.text = getString(R.string.detail_no_search_results)
|
||||
// Hide "how to" instructions when showing search results
|
||||
howToIntro.visibility = View.GONE
|
||||
howToExampleView.visibility = View.GONE
|
||||
howToLinkView.visibility = View.GONE
|
||||
} else {
|
||||
noSearchResultsText.text = getString(R.string.detail_no_notifications_text)
|
||||
// Show "how to" instructions for empty subscription
|
||||
howToIntro.visibility = View.VISIBLE
|
||||
howToExampleView.visibility = View.VISIBLE
|
||||
howToLinkView.visibility = if (BuildConfig.PAYMENT_LINKS_AVAILABLE) View.VISIBLE else View.GONE
|
||||
}
|
||||
} else {
|
||||
mainListContainer.visibility = View.VISIBLE
|
||||
noEntriesText.visibility = View.GONE
|
||||
}
|
||||
|
||||
// Cancel notifications that still have popups
|
||||
maybeCancelNotificationPopups(it)
|
||||
maybeCancelNotificationPopups(notifications)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -553,6 +579,57 @@ class DetailActivity : AppCompatActivity(), NotificationFragment.NotificationSet
|
|||
menu[i].icon?.setTint(toolbarTextColor)
|
||||
}
|
||||
|
||||
// Setup SearchView
|
||||
val searchItem = menu.findItem(R.id.detail_menu_search)
|
||||
searchView = searchItem?.actionView as? SearchView
|
||||
searchView?.let { sv ->
|
||||
sv.queryHint = getString(R.string.detail_search_hint)
|
||||
|
||||
// Tint SearchView icons and text
|
||||
val searchIcon = sv.findViewById<ImageView>(androidx.appcompat.R.id.search_button)
|
||||
searchIcon?.setColorFilter(toolbarTextColor)
|
||||
val closeIcon = sv.findViewById<ImageView>(androidx.appcompat.R.id.search_close_btn)
|
||||
closeIcon?.setColorFilter(toolbarTextColor)
|
||||
val searchEditText = sv.findViewById<EditText>(androidx.appcompat.R.id.search_src_text)
|
||||
searchEditText?.setTextColor(toolbarTextColor)
|
||||
searchEditText?.setHintTextColor(toolbarTextColor.and(0x80FFFFFF.toInt())) // 50% alpha
|
||||
|
||||
// Handle query text changes
|
||||
sv.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
||||
override fun onQueryTextSubmit(query: String?): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onQueryTextChange(newText: String?): Boolean {
|
||||
viewModel.setSearchQuery(newText ?: "")
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
||||
// Handle expand/collapse
|
||||
searchItem.setOnActionExpandListener(object : MenuItem.OnActionExpandListener {
|
||||
override fun onMenuItemActionExpand(item: MenuItem): Boolean {
|
||||
isSearchActive = true
|
||||
// Hide other menu items when search is expanded
|
||||
setMenuItemsVisibility(false)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onMenuItemActionCollapse(item: MenuItem): Boolean {
|
||||
isSearchActive = false
|
||||
// Clear search query when collapsed
|
||||
viewModel.setSearchQuery("")
|
||||
// Restore menu items visibility
|
||||
setMenuItemsVisibility(true)
|
||||
showHideInstantMenuItems(subscriptionInstant)
|
||||
showHideMutedUntilMenuItems(subscriptionMutedUntil)
|
||||
showHideCopyMenuItems(subscriptionBaseUrl)
|
||||
showHideConnectionErrorMenuItem(repository.getConnectionDetails())
|
||||
return true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Show and hide buttons
|
||||
showHideInstantMenuItems(subscriptionInstant)
|
||||
showHideMutedUntilMenuItems(subscriptionMutedUntil)
|
||||
|
|
@ -566,6 +643,18 @@ class DetailActivity : AppCompatActivity(), NotificationFragment.NotificationSet
|
|||
return true
|
||||
}
|
||||
|
||||
private fun setMenuItemsVisibility(visible: Boolean) {
|
||||
if (!this::menu.isInitialized) return
|
||||
|
||||
// Hide/show all menu items except search
|
||||
for (i in 0 until menu.size) {
|
||||
val item = menu[i]
|
||||
if (item.itemId != R.id.detail_menu_search) {
|
||||
item.isVisible = visible
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun startNotificationMutedChecker() {
|
||||
// FIXME This is awful and has to go.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,19 +1,44 @@
|
|||
package io.heckel.ntfy.ui
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import io.heckel.ntfy.db.Notification
|
||||
import io.heckel.ntfy.db.Repository
|
||||
import io.heckel.ntfy.db.combineWith
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class DetailViewModel(private val repository: Repository) : ViewModel() {
|
||||
private val _searchQuery = MutableLiveData("")
|
||||
val searchQuery: LiveData<String> = _searchQuery
|
||||
|
||||
fun setSearchQuery(query: String) {
|
||||
_searchQuery.value = query
|
||||
}
|
||||
|
||||
fun list(subscriptionId: Long): LiveData<List<Notification>> {
|
||||
return repository.getNotificationsLiveData(subscriptionId)
|
||||
}
|
||||
|
||||
fun listFiltered(subscriptionId: Long): LiveData<List<Notification>> {
|
||||
return repository.getNotificationsLiveData(subscriptionId)
|
||||
.combineWith(_searchQuery) { notifications, query ->
|
||||
if (query.isNullOrBlank()) {
|
||||
notifications.orEmpty()
|
||||
} else {
|
||||
val q = query.lowercase()
|
||||
notifications.orEmpty().filter { n ->
|
||||
n.title.lowercase().contains(q) ||
|
||||
n.message.lowercase().contains(q) ||
|
||||
n.tags.lowercase().contains(q)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun markAsDeleted(notificationId: String) = viewModelScope.launch(Dispatchers.IO) {
|
||||
repository.markAsDeleted(notificationId)
|
||||
}
|
||||
|
|
|
|||
9
app/src/main/res/drawable/ic_search_white_24dp.xml
Normal file
9
app/src/main/res/drawable/ic_search_white_24dp.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="24dp"
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24"
|
||||
android:width="24dp">
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z"/>
|
||||
</vector>
|
||||
|
|
@ -1,6 +1,12 @@
|
|||
<menu
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:id="@+id/detail_menu_search"
|
||||
android:icon="@drawable/ic_search_white_24dp"
|
||||
android:title="@string/detail_menu_search"
|
||||
app:showAsAction="always|collapseActionView"
|
||||
app:actionViewClass="androidx.appcompat.widget.SearchView" />
|
||||
<item
|
||||
android:id="@+id/detail_menu_connection_error"
|
||||
android:icon="@drawable/ic_warning_white_24dp"
|
||||
|
|
@ -24,14 +30,10 @@
|
|||
app:showAsAction="ifRoom" />
|
||||
<item
|
||||
android:id="@+id/detail_menu_enable_instant"
|
||||
android:icon="@drawable/ic_bolt_outline_white_24dp"
|
||||
android:title="@string/detail_menu_enable_instant"
|
||||
app:showAsAction="ifRoom" />
|
||||
android:title="@string/detail_menu_enable_instant" />
|
||||
<item
|
||||
android:id="@+id/detail_menu_disable_instant"
|
||||
android:icon="@drawable/ic_bolt_white_24dp"
|
||||
android:title="@string/detail_menu_disable_instant"
|
||||
app:showAsAction="ifRoom" />
|
||||
android:title="@string/detail_menu_disable_instant" />
|
||||
<item
|
||||
android:id="@+id/detail_menu_settings"
|
||||
android:title="@string/detail_menu_settings" />
|
||||
|
|
|
|||
|
|
@ -203,6 +203,9 @@
|
|||
<string name="detail_menu_clear">Clear all notifications</string>
|
||||
<string name="detail_menu_settings">Subscription settings</string>
|
||||
<string name="detail_menu_unsubscribe">Unsubscribe</string>
|
||||
<string name="detail_menu_search">Search notifications</string>
|
||||
<string name="detail_search_hint">Search in notifications</string>
|
||||
<string name="detail_no_search_results">No notifications match your search</string>
|
||||
|
||||
<!-- Detail activity: Action mode -->
|
||||
<string name="detail_action_mode_menu_delete">Delete</string>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue