diff --git a/app/src/main/java/io/heckel/ntfy/db/Database.kt b/app/src/main/java/io/heckel/ntfy/db/Database.kt index 57fa984f..952b6c0e 100644 --- a/app/src/main/java/io/heckel/ntfy/db/Database.kt +++ b/app/src/main/java/io/heckel/ntfy/db/Database.kt @@ -574,6 +574,19 @@ interface NotificationDao { @Query("SELECT * FROM notification WHERE subscriptionId = :subscriptionId AND deleted != 1 ORDER BY timestamp DESC") fun listFlow(subscriptionId: Long): Flow> + @Query(""" + SELECT * FROM notification + WHERE subscriptionId = :subscriptionId + AND deleted != 1 + AND ( + title LIKE '%' || :query || '%' COLLATE NOCASE + OR message LIKE '%' || :query || '%' COLLATE NOCASE + OR tags LIKE '%' || :query || '%' COLLATE NOCASE + ) + ORDER BY timestamp DESC + """) + fun listFlowFiltered(subscriptionId: Long, query: String): Flow> + @Query("SELECT * FROM notification WHERE deleted = 1 AND attachment_contentUri <> ''") fun listDeletedWithAttachments(): List diff --git a/app/src/main/java/io/heckel/ntfy/db/Repository.kt b/app/src/main/java/io/heckel/ntfy/db/Repository.kt index 1780944d..51504bca 100644 --- a/app/src/main/java/io/heckel/ntfy/db/Repository.kt +++ b/app/src/main/java/io/heckel/ntfy/db/Repository.kt @@ -120,6 +120,10 @@ class Repository(private val sharedPrefs: SharedPreferences, database: Database) return notificationDao.listFlow(subscriptionId).asLiveData() } + fun getNotificationsFilteredLiveData(subscriptionId: Long, query: String): LiveData> { + return notificationDao.listFlowFiltered(subscriptionId, query).asLiveData() + } + fun getNotification(notificationId: String): Notification? { return notificationDao.get(notificationId) } diff --git a/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt b/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt index 0b55d3a9..2947c5de 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt @@ -98,6 +98,8 @@ class DetailActivity : AppCompatActivity(), NotificationFragment.NotificationSet // Search state private var searchView: SearchView? = null private var isSearchActive: Boolean = false + private lateinit var toolbar: com.google.android.material.appbar.MaterialToolbar + private var toolbarTextColor: Int = 0 // Action mode stuff private var actionMode: ActionMode? = null @@ -147,13 +149,17 @@ class DetailActivity : AppCompatActivity(), NotificationFragment.NotificationSet val dynamicColors = repository.getDynamicColorsEnabled() val darkMode = isDarkThemeOn(this) val statusBarColor = Colors.statusBarNormal(this, dynamicColors, darkMode) - val toolbarTextColor = Colors.toolbarTextColor(this, dynamicColors, darkMode) + toolbarTextColor = Colors.toolbarTextColor(this, dynamicColors, darkMode) toolbarLayout.setBackgroundColor(statusBarColor) - val toolbar = toolbarLayout.findViewById(R.id.toolbar) + toolbar = toolbarLayout.findViewById(R.id.toolbar) toolbar.setTitleTextColor(toolbarTextColor) toolbar.setNavigationIconTint(toolbarTextColor) toolbar.overflowIcon?.setTint(toolbarTextColor) + // Set collapse icon (back arrow when search is expanded) with proper tint + val collapseIcon = ContextCompat.getDrawable(this, R.drawable.ic_arrow_back_white_24dp)?.mutate() + collapseIcon?.setTint(toolbarTextColor) + toolbar.collapseIcon = collapseIcon setSupportActionBar(toolbar) // Set system status bar appearance @@ -537,7 +543,6 @@ class DetailActivity : AppCompatActivity(), NotificationFragment.NotificationSet subscriptionMutedUntil = subscription.mutedUntil subscriptionDisplayName = displayName(appBaseUrl, subscription) - showHideInstantMenuItems(subscriptionInstant) showHideMutedUntilMenuItems(subscriptionMutedUntil) showHideCopyMenuItems(subscription.baseUrl) showHideConnectionErrorMenuItem(repository.getConnectionDetails()) @@ -574,7 +579,6 @@ class DetailActivity : AppCompatActivity(), NotificationFragment.NotificationSet this.menu = menu // Tint menu icons based on theme - val toolbarTextColor = Colors.toolbarTextColor(this, repository.getDynamicColorsEnabled(), isDarkThemeOn(this)) for (i in 0 until menu.size) { menu[i].icon?.setTint(toolbarTextColor) } @@ -584,6 +588,7 @@ class DetailActivity : AppCompatActivity(), NotificationFragment.NotificationSet searchView = searchItem?.actionView as? SearchView searchView?.let { sv -> sv.queryHint = getString(R.string.detail_search_hint) + sv.maxWidth = Integer.MAX_VALUE // Make SearchView expand fully // Tint SearchView icons and text val searchIcon = sv.findViewById(androidx.appcompat.R.id.search_button) @@ -612,6 +617,8 @@ class DetailActivity : AppCompatActivity(), NotificationFragment.NotificationSet isSearchActive = true // Hide other menu items when search is expanded setMenuItemsVisibility(false) + // Ensure collapse icon is tinted (back arrow when search is expanded) + toolbar.collapseIcon?.setTint(toolbarTextColor) return true } @@ -621,7 +628,6 @@ class DetailActivity : AppCompatActivity(), NotificationFragment.NotificationSet viewModel.setSearchQuery("") // Restore menu items visibility setMenuItemsVisibility(true) - showHideInstantMenuItems(subscriptionInstant) showHideMutedUntilMenuItems(subscriptionMutedUntil) showHideCopyMenuItems(subscriptionBaseUrl) showHideConnectionErrorMenuItem(repository.getConnectionDetails()) @@ -631,7 +637,6 @@ class DetailActivity : AppCompatActivity(), NotificationFragment.NotificationSet } // Show and hide buttons - showHideInstantMenuItems(subscriptionInstant) showHideMutedUntilMenuItems(subscriptionMutedUntil) showHideCopyMenuItems(subscriptionBaseUrl) showHideConnectionErrorMenuItem(repository.getConnectionDetails()) @@ -692,14 +697,6 @@ class DetailActivity : AppCompatActivity(), NotificationFragment.NotificationSet onMutedUntilClick(enable = true) true } - R.id.detail_menu_enable_instant -> { - onInstantEnableClick(enable = true) - true - } - R.id.detail_menu_disable_instant -> { - onInstantEnableClick(enable = false) - true - } R.id.detail_menu_connection_error -> { onConnectionErrorClick() true @@ -833,46 +830,6 @@ class DetailActivity : AppCompatActivity(), NotificationFragment.NotificationSet } } - private fun onInstantEnableClick(enable: Boolean) { - Log.d(TAG, "Toggling instant delivery setting for ${topicShortUrl(subscriptionBaseUrl, subscriptionTopic)}") - - lifecycleScope.launch(Dispatchers.IO) { - val subscription = repository.getSubscription(subscriptionId) - val newSubscription = subscription?.copy(instant = enable) - newSubscription?.let { repository.updateSubscription(newSubscription) } - showHideInstantMenuItems(enable) - runOnUiThread { - if (enable) { - Toast.makeText(this@DetailActivity, getString(R.string.detail_instant_delivery_enabled), Toast.LENGTH_SHORT) - .show() - } else { - Toast.makeText(this@DetailActivity, getString(R.string.detail_instant_delivery_disabled), Toast.LENGTH_SHORT) - .show() - } - } - } - } - - private fun showHideInstantMenuItems(enable: Boolean) { - if (!this::menu.isInitialized) { - return - } - subscriptionInstant = enable - runOnUiThread { - val appBaseUrl = getString(R.string.app_base_url) - val enableInstantItem = menu.findItem(R.id.detail_menu_enable_instant) - val disableInstantItem = menu.findItem(R.id.detail_menu_disable_instant) - val allowToggleInstant = BuildConfig.FIREBASE_AVAILABLE && subscriptionBaseUrl == appBaseUrl - if (allowToggleInstant) { - enableInstantItem?.isVisible = !subscriptionInstant - disableInstantItem?.isVisible = subscriptionInstant - } else { - enableInstantItem?.isVisible = false - disableInstantItem?.isVisible = false - } - } - } - private fun showHideMutedUntilMenuItems(mutedUntilTimestamp: Long) { if (!this::menu.isInitialized) { return diff --git a/app/src/main/java/io/heckel/ntfy/ui/DetailViewModel.kt b/app/src/main/java/io/heckel/ntfy/ui/DetailViewModel.kt index d8aa793b..f9293841 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/DetailViewModel.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/DetailViewModel.kt @@ -4,10 +4,10 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.switchMap 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 @@ -15,6 +15,8 @@ class DetailViewModel(private val repository: Repository) : ViewModel() { private val _searchQuery = MutableLiveData("") val searchQuery: LiveData = _searchQuery + private var currentSubscriptionId: Long = 0 + fun setSearchQuery(query: String) { _searchQuery.value = query } @@ -24,19 +26,14 @@ class DetailViewModel(private val repository: Repository) : ViewModel() { } fun listFiltered(subscriptionId: Long): LiveData> { - 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) - } - } + currentSubscriptionId = subscriptionId + return _searchQuery.switchMap { query -> + if (query.isNullOrBlank()) { + repository.getNotificationsLiveData(subscriptionId) + } else { + repository.getNotificationsFilteredLiveData(subscriptionId, query) } + } } fun markAsDeleted(notificationId: String) = viewModelScope.launch(Dispatchers.IO) { diff --git a/app/src/main/res/menu/menu_detail_action_bar.xml b/app/src/main/res/menu/menu_detail_action_bar.xml index 7f18561e..746c20fa 100644 --- a/app/src/main/res/menu/menu_detail_action_bar.xml +++ b/app/src/main/res/menu/menu_detail_action_bar.xml @@ -28,12 +28,6 @@ android:icon="@drawable/ic_notifications_off_white_outline_24dp" android:title="@string/detail_menu_notifications_disabled_forever" app:showAsAction="ifRoom" /> - -