diff --git a/android/app/src/main/java/com/github/quarck/calnotify/Settings.kt b/android/app/src/main/java/com/github/quarck/calnotify/Settings.kt index 971b32ca..1df40d91 100644 --- a/android/app/src/main/java/com/github/quarck/calnotify/Settings.kt +++ b/android/app/src/main/java/com/github/quarck/calnotify/Settings.kt @@ -501,6 +501,21 @@ class Settings(context: Context) : PersistentStorageBase(context), SettingsInter .toLongArray() } + /** Raw configurable snoozed-until filter presets string (e.g., "12h, 1d, 3d, 7d, 4w") */ + val snoozedUntilPresetsRaw: String + get() = getString(SNOOZED_UNTIL_PRESETS_KEY, DEFAULT_SNOOZED_UNTIL_PRESETS) + + /** Parsed snoozed-until filter presets in milliseconds. Filters out negative values. */ + val snoozedUntilPresets: LongArray + get() { + val ret = PreferenceUtils.parseSnoozePresets(snoozedUntilPresetsRaw) + ?: PreferenceUtils.parseSnoozePresets(DEFAULT_SNOOZED_UNTIL_PRESETS) + ?: return longArrayOf() + return ret.filter { it > 0 } + .take(MAX_SNOOZED_UNTIL_PRESETS) + .toLongArray() + } + /** Max calendars to show in calendar filter. 0 = no limit (show all). */ val calendarFilterMaxItems: Int get() = getString(CALENDAR_FILTER_MAX_ITEMS_KEY, DEFAULT_CALENDAR_FILTER_MAX_ITEMS.toString()) @@ -613,6 +628,7 @@ class Settings(context: Context) : PersistentStorageBase(context), SettingsInter private const val UPCOMING_EVENTS_FIXED_HOURS_KEY = "upcoming_events_fixed_hours" private const val UPCOMING_FIXED_LOOKAHEAD_MILLIS_KEY = "upcoming_fixed_lookahead_millis" private const val UPCOMING_TIME_PRESETS_KEY = "pref_upcoming_time_presets" + private const val SNOOZED_UNTIL_PRESETS_KEY = "pref_snoozed_until_presets" private const val CALENDAR_FILTER_MAX_ITEMS_KEY = "calendar_filter_max_items" private const val CALENDAR_FILTER_SHOW_SEARCH_KEY = "calendar_filter_show_search" private const val CALENDAR_FILTER_SHOW_IDS_KEY = "calendar_filter_show_ids" @@ -638,6 +654,8 @@ class Settings(context: Context) : PersistentStorageBase(context), SettingsInter internal const val MAX_UPCOMING_TIME_PRESETS = 10 internal const val MAX_LOOKAHEAD_DAYS = 30L internal const val MAX_LOOKAHEAD_MILLIS = MAX_LOOKAHEAD_DAYS * Consts.DAY_IN_MILLISECONDS + internal const val DEFAULT_SNOOZED_UNTIL_PRESETS = "12h, 1d, 3d, 7d, 4w" + internal const val MAX_SNOOZED_UNTIL_PRESETS = 10 /** Default max calendars in filter (0 = no limit) */ internal const val DEFAULT_CALENDAR_FILTER_MAX_ITEMS = 20 } diff --git a/android/app/src/main/java/com/github/quarck/calnotify/prefs/NavigationSettingsFragmentX.kt b/android/app/src/main/java/com/github/quarck/calnotify/prefs/NavigationSettingsFragmentX.kt index 6134738b..bd354b11 100644 --- a/android/app/src/main/java/com/github/quarck/calnotify/prefs/NavigationSettingsFragmentX.kt +++ b/android/app/src/main/java/com/github/quarck/calnotify/prefs/NavigationSettingsFragmentX.kt @@ -60,13 +60,20 @@ class NavigationSettingsFragmentX : PreferenceFragmentCompat() { } override fun onDisplayPreferenceDialog(preference: Preference) { - if (preference is UpcomingTimePresetPreferenceX) { - val dialogFragment = UpcomingTimePresetPreferenceX.Dialog.newInstance(preference.key) - @Suppress("DEPRECATION") - dialogFragment.setTargetFragment(this, 0) - dialogFragment.show(parentFragmentManager, DIALOG_FRAGMENT_TAG) - } else { - super.onDisplayPreferenceDialog(preference) + when (preference) { + is UpcomingTimePresetPreferenceX -> { + val dialogFragment = UpcomingTimePresetPreferenceX.Dialog.newInstance(preference.key) + @Suppress("DEPRECATION") + dialogFragment.setTargetFragment(this, 0) + dialogFragment.show(parentFragmentManager, DIALOG_FRAGMENT_TAG) + } + is SnoozedUntilPresetPreferenceX -> { + val dialogFragment = SnoozedUntilPresetPreferenceX.Dialog.newInstance(preference.key) + @Suppress("DEPRECATION") + dialogFragment.setTargetFragment(this, 0) + dialogFragment.show(parentFragmentManager, DIALOG_FRAGMENT_TAG) + } + else -> super.onDisplayPreferenceDialog(preference) } } diff --git a/android/app/src/main/java/com/github/quarck/calnotify/prefs/SnoozedUntilPresetPreferenceX.kt b/android/app/src/main/java/com/github/quarck/calnotify/prefs/SnoozedUntilPresetPreferenceX.kt new file mode 100644 index 00000000..c5c17eaa --- /dev/null +++ b/android/app/src/main/java/com/github/quarck/calnotify/prefs/SnoozedUntilPresetPreferenceX.kt @@ -0,0 +1,136 @@ +// +// Calendar Notifications Plus +// Copyright (C) 2025 William Harris (wharris+cnplus@upscalews.com) +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +package com.github.quarck.calnotify.prefs + +import android.app.AlertDialog +import android.content.Context +import android.os.Bundle +import android.util.AttributeSet +import android.view.View +import android.widget.EditText +import android.widget.TextView +import androidx.preference.DialogPreference +import androidx.preference.PreferenceDialogFragmentCompat +import com.github.quarck.calnotify.R +import com.github.quarck.calnotify.Settings + +class SnoozedUntilPresetPreferenceX @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = androidx.preference.R.attr.dialogPreferenceStyle, + defStyleRes: Int = 0 +) : DialogPreference(context, attrs, defStyleAttr, defStyleRes) { + + var presetValue: String = Settings.DEFAULT_SNOOZED_UNTIL_PRESETS + private set + + init { + dialogLayoutResource = R.layout.dialog_snoozed_until_presets + positiveButtonText = context.getString(android.R.string.ok) + negativeButtonText = context.getString(android.R.string.cancel) + } + + fun persistPreset(value: String) { + presetValue = value + persistString(value) + notifyChanged() + } + + override fun onSetInitialValue(defaultValue: Any?) { + presetValue = getPersistedString((defaultValue as? String) ?: Settings.DEFAULT_SNOOZED_UNTIL_PRESETS) + } + + override fun onGetDefaultValue(a: android.content.res.TypedArray, index: Int): Any? { + return a.getString(index) + } + + class Dialog : PreferenceDialogFragmentCompat() { + private var edit: EditText? = null + + override fun onBindDialogView(view: View) { + super.onBindDialogView(view) + + val pref = preference as SnoozedUntilPresetPreferenceX + + val label = view.findViewById(R.id.text_label_snoozed_until_presets) + label?.text = getString(R.string.dialog_snoozed_until_presets_label, Settings.MAX_SNOOZED_UNTIL_PRESETS) + + edit = view.findViewById(R.id.edit_text_snoozed_until_presets) + edit?.setText(pref.presetValue) + } + + override fun onDialogClosed(positiveResult: Boolean) { + if (!positiveResult) return + val value = edit?.text?.toString() ?: return + + val result = PreferenceUtils.normalizePresetInput( + value, Settings.DEFAULT_SNOOZED_UNTIL_PRESETS + ) { it > 0 } + + if (result != null) { + val pref = preference as SnoozedUntilPresetPreferenceX + if (pref.callChangeListener(result.value)) { + pref.persistPreset(result.value) + } + + if (result.droppedCount > 0) { + showMessage(R.string.warning_presets_invalid_removed) + } + + val parsed = PreferenceUtils.parseSnoozePresets(result.value) + if (parsed != null && parsed.size > Settings.MAX_SNOOZED_UNTIL_PRESETS) { + showFormattedMessage(R.string.error_too_many_snoozed_until_presets, Settings.MAX_SNOOZED_UNTIL_PRESETS) + } + } else { + showMessage(R.string.error_cannot_parse_preset) + } + } + + private fun showMessage(id: Int) { + val context = requireContext() + AlertDialog.Builder(context) + .setMessage(context.getString(id)) + .setCancelable(false) + .setPositiveButton(android.R.string.ok) { _, _ -> } + .create() + .show() + } + + private fun showFormattedMessage(id: Int, vararg args: Any) { + val context = requireContext() + AlertDialog.Builder(context) + .setMessage(context.getString(id, *args)) + .setCancelable(false) + .setPositiveButton(android.R.string.ok) { _, _ -> } + .create() + .show() + } + + companion object { + fun newInstance(key: String): Dialog { + val fragment = Dialog() + val args = Bundle(1) + args.putString(ARG_KEY, key) + fragment.arguments = args + return fragment + } + } + } +} diff --git a/android/app/src/main/java/com/github/quarck/calnotify/ui/FilterState.kt b/android/app/src/main/java/com/github/quarck/calnotify/ui/FilterState.kt index fda65b5c..71980cd4 100644 --- a/android/app/src/main/java/com/github/quarck/calnotify/ui/FilterState.kt +++ b/android/app/src/main/java/com/github/quarck/calnotify/ui/FilterState.kt @@ -30,7 +30,53 @@ import com.github.quarck.calnotify.utils.DateTimeUtils * Which filters to apply when filtering events. */ enum class FilterType { - CALENDAR, STATUS, TIME + CALENDAR, STATUS, TIME, SNOOZED_UNTIL +} + +/** + * Direction for the snoozed-until filter threshold. + */ +enum class FilterDirection { + BEFORE, // snoozedUntil <= threshold + AFTER // snoozedUntil > threshold +} + +/** + * Mode for the snoozed-until filter. + */ +enum class SnoozedUntilFilterMode { + ALL, // no filter (default) + PRESET, // from configurable interval presets; valueMillis = duration + CUSTOM_PERIOD, // user-entered duration; valueMillis = duration + SPECIFIC_TIME // user-picked date+time; valueMillis = absolute timestamp +} + +/** + * Configuration for the snoozed-until filter. Unlike the simple TimeFilter enum, + * this supports multiple modes (presets, custom duration, specific time) and a + * before/after direction toggle. + */ +data class SnoozedUntilFilterConfig( + val mode: SnoozedUntilFilterMode = SnoozedUntilFilterMode.ALL, + val direction: FilterDirection = FilterDirection.BEFORE, + val valueMillis: Long = 0L, + val includeUnsnoozed: Boolean = false +) { + fun matches(event: EventAlertRecord, now: Long): Boolean { + if (mode == SnoozedUntilFilterMode.ALL) return true + if (event.snoozedUntil == 0L) return includeUnsnoozed + + val threshold = when (mode) { + SnoozedUntilFilterMode.ALL -> return true + SnoozedUntilFilterMode.PRESET, SnoozedUntilFilterMode.CUSTOM_PERIOD -> now + valueMillis + SnoozedUntilFilterMode.SPECIFIC_TIME -> valueMillis + } + + return when (direction) { + FilterDirection.BEFORE -> event.snoozedUntil <= threshold + FilterDirection.AFTER -> event.snoozedUntil > threshold + } + } } /** @@ -39,7 +85,8 @@ enum class FilterType { data class FilterState( val selectedCalendarIds: Set? = null, // null = no filter (all), empty = none, set = specific val statusFilters: Set = emptySet(), // empty = show all (no filter) - val timeFilter: TimeFilter = TimeFilter.ALL + val timeFilter: TimeFilter = TimeFilter.ALL, + val snoozedUntilFilter: SnoozedUntilFilterConfig = SnoozedUntilFilterConfig() ) { companion object { @@ -47,6 +94,10 @@ data class FilterState( private const val BUNDLE_CALENDAR_NULL = "filter_calendar_null" private const val BUNDLE_STATUS_FILTERS = "filter_status" private const val BUNDLE_TIME_FILTER = "filter_time" + private const val BUNDLE_SNOOZED_UNTIL_MODE = "filter_snoozed_until_mode" + private const val BUNDLE_SNOOZED_UNTIL_DIRECTION = "filter_snoozed_until_direction" + private const val BUNDLE_SNOOZED_UNTIL_VALUE = "filter_snoozed_until_value" + private const val BUNDLE_SNOOZED_UNTIL_INCLUDE_UNSNOOZED = "filter_snoozed_until_include_unsnoozed" /** Deserialize FilterState from a Bundle */ fun fromBundle(bundle: Bundle?): FilterState { @@ -67,10 +118,22 @@ data class FilterState( bundle.getInt(BUNDLE_TIME_FILTER, 0) ) ?: TimeFilter.ALL + val snoozedUntilFilter = SnoozedUntilFilterConfig( + mode = SnoozedUntilFilterMode.entries.getOrNull( + bundle.getInt(BUNDLE_SNOOZED_UNTIL_MODE, 0) + ) ?: SnoozedUntilFilterMode.ALL, + direction = FilterDirection.entries.getOrNull( + bundle.getInt(BUNDLE_SNOOZED_UNTIL_DIRECTION, 0) + ) ?: FilterDirection.BEFORE, + valueMillis = bundle.getLong(BUNDLE_SNOOZED_UNTIL_VALUE, 0L), + includeUnsnoozed = bundle.getBoolean(BUNDLE_SNOOZED_UNTIL_INCLUDE_UNSNOOZED, false) + ) + return FilterState( selectedCalendarIds = calendarIds, statusFilters = statusFilters, - timeFilter = timeFilter + timeFilter = timeFilter, + snoozedUntilFilter = snoozedUntilFilter ) } } @@ -83,13 +146,18 @@ data class FilterState( putIntArray(BUNDLE_STATUS_FILTERS, statusFilters.map { it.ordinal }.toIntArray()) putInt(BUNDLE_TIME_FILTER, timeFilter.ordinal) + putInt(BUNDLE_SNOOZED_UNTIL_MODE, snoozedUntilFilter.mode.ordinal) + putInt(BUNDLE_SNOOZED_UNTIL_DIRECTION, snoozedUntilFilter.direction.ordinal) + putLong(BUNDLE_SNOOZED_UNTIL_VALUE, snoozedUntilFilter.valueMillis) + putBoolean(BUNDLE_SNOOZED_UNTIL_INCLUDE_UNSNOOZED, snoozedUntilFilter.includeUnsnoozed) } /** Check if any filters are active */ fun hasActiveFilters(): Boolean { return selectedCalendarIds != null || statusFilters.isNotEmpty() || - timeFilter != TimeFilter.ALL + timeFilter != TimeFilter.ALL || + snoozedUntilFilter.mode != SnoozedUntilFilterMode.ALL } /** @@ -130,6 +198,15 @@ data class FilterState( TimeFilter.STARTED_THIS_MONTH -> parts.add(context.getString(R.string.filter_time_started_this_month)) } + // Snoozed until filter + if (snoozedUntilFilter.mode != SnoozedUntilFilterMode.ALL) { + val symbol = when (snoozedUntilFilter.direction) { + FilterDirection.BEFORE -> "\u2264" // ≤ + FilterDirection.AFTER -> ">" + } + parts.add(context.getString(R.string.filter_snoozed_until_display, symbol)) + } + return if (parts.isEmpty()) null else parts.joinToString(", ") } /** Check if an event matches current status filters (empty set = match all) */ @@ -143,6 +220,11 @@ data class FilterState( return timeFilter.matches(event, now) } + /** Check if an event matches current snoozed-until filter */ + fun matchesSnoozedUntil(event: EventAlertRecord, now: Long): Boolean { + return snoozedUntilFilter.matches(event, now) + } + /** Check if an event matches current calendar filter (null = all, empty = none) */ fun matchesCalendar(event: EventAlertRecord): Boolean { if (selectedCalendarIds == null) return true // No filter = show all @@ -165,7 +247,8 @@ data class FilterState( val event = eventExtractor(item) (FilterType.CALENDAR !in apply || matchesCalendar(event)) && (FilterType.STATUS !in apply || matchesStatus(event)) && - (FilterType.TIME !in apply || matchesTime(event, now)) + (FilterType.TIME !in apply || matchesTime(event, now)) && + (FilterType.SNOOZED_UNTIL !in apply || matchesSnoozedUntil(event, now)) }.toTypedArray() } @@ -173,7 +256,7 @@ data class FilterState( fun filterEvents( events: List, now: Long, - apply: Set = setOf(FilterType.CALENDAR, FilterType.STATUS, FilterType.TIME) + apply: Set = setOf(FilterType.CALENDAR, FilterType.STATUS, FilterType.TIME, FilterType.SNOOZED_UNTIL) ): Array { return filterEvents(events, now, apply) { it } } diff --git a/android/app/src/main/java/com/github/quarck/calnotify/ui/MainActivityModern.kt b/android/app/src/main/java/com/github/quarck/calnotify/ui/MainActivityModern.kt index 441c2af2..adc4fcf2 100644 --- a/android/app/src/main/java/com/github/quarck/calnotify/ui/MainActivityModern.kt +++ b/android/app/src/main/java/com/github/quarck/calnotify/ui/MainActivityModern.kt @@ -400,10 +400,11 @@ class MainActivityModern : MainActivityBase() { when (currentDestination) { R.id.activeEventsFragment -> { - // Active tab: Calendar, Status, Time + // Active tab: Calendar, Status, Time, Snoozed Until addCalendarChip() addStatusChip() addTimeChip(TimeFilterBottomSheet.TabType.ACTIVE) + addSnoozedUntilChip() } R.id.upcomingEventsFragment -> { addCalendarChip() @@ -634,6 +635,51 @@ class MainActivityModern : MainActivityBase() { bottomSheet.show(supportFragmentManager, "UpcomingTimeFilterBottomSheet") } + // === Snoozed Until Filter Chip === + + private fun addSnoozedUntilChip() { + val materialContext = ContextThemeWrapper(this, com.google.android.material.R.style.Theme_MaterialComponents_DayNight) + val chip = Chip(materialContext).apply { + text = getSnoozedUntilChipText() + isCheckable = false + isChipIconVisible = false + isCloseIconVisible = true + closeIcon = getDrawable(R.drawable.ic_arrow_drop_down) + setOnClickListener { showSnoozedUntilFilterBottomSheet() } + setOnCloseIconClickListener { showSnoozedUntilFilterBottomSheet() } + } + chipGroup?.addView(chip) + } + + private fun getSnoozedUntilChipText(): String { + val config = filterState.snoozedUntilFilter + if (config.mode == SnoozedUntilFilterMode.ALL) { + return getString(R.string.filter_snoozed_until) + } + val symbol = when (config.direction) { + FilterDirection.BEFORE -> "\u2264" // ≤ + FilterDirection.AFTER -> ">" + } + val valueText = when (config.mode) { + SnoozedUntilFilterMode.ALL -> "" + SnoozedUntilFilterMode.PRESET, SnoozedUntilFilterMode.CUSTOM_PERIOD -> + PreferenceUtils.formatPresetHumanReadable(this, config.valueMillis) + SnoozedUntilFilterMode.SPECIFIC_TIME -> + android.text.format.DateUtils.formatDateTime( + this, config.valueMillis, + android.text.format.DateUtils.FORMAT_SHOW_DATE or + android.text.format.DateUtils.FORMAT_SHOW_TIME or + android.text.format.DateUtils.FORMAT_ABBREV_ALL + ) + } + return "$symbol $valueText" + } + + private fun showSnoozedUntilFilterBottomSheet() { + val bottomSheet = SnoozedUntilFilterBottomSheet.newInstance(filterState.snoozedUntilFilter) + bottomSheet.show(supportFragmentManager, "SnoozedUntilFilterBottomSheet") + } + /** Setup Fragment Result listeners for bottom sheets (survives config changes) */ private fun setupFilterResultListeners() { // Time filter result @@ -674,6 +720,23 @@ class MainActivityModern : MainActivityBase() { updateFilterChipsForCurrentTab() notifyCurrentFragmentFilterChanged() } + + // Snoozed until filter result + supportFragmentManager.setFragmentResultListener( + SnoozedUntilFilterBottomSheet.REQUEST_KEY, this + ) { _, bundle -> + val mode = SnoozedUntilFilterMode.entries.getOrNull( + bundle.getInt(SnoozedUntilFilterBottomSheet.RESULT_MODE, 0) + ) ?: SnoozedUntilFilterMode.ALL + val direction = FilterDirection.entries.getOrNull( + bundle.getInt(SnoozedUntilFilterBottomSheet.RESULT_DIRECTION, 0) + ) ?: FilterDirection.BEFORE + val value = bundle.getLong(SnoozedUntilFilterBottomSheet.RESULT_VALUE, 0L) + val includeUnsnoozed = bundle.getBoolean(SnoozedUntilFilterBottomSheet.RESULT_INCLUDE_UNSNOOZED, false) + filterState = filterState.copy(snoozedUntilFilter = SnoozedUntilFilterConfig(mode, direction, value, includeUnsnoozed)) + updateFilterChipsForCurrentTab() + notifyCurrentFragmentFilterChanged() + } } // === Selection Mode Coordination === diff --git a/android/app/src/main/java/com/github/quarck/calnotify/ui/SnoozedUntilFilterBottomSheet.kt b/android/app/src/main/java/com/github/quarck/calnotify/ui/SnoozedUntilFilterBottomSheet.kt new file mode 100644 index 00000000..cb007acd --- /dev/null +++ b/android/app/src/main/java/com/github/quarck/calnotify/ui/SnoozedUntilFilterBottomSheet.kt @@ -0,0 +1,386 @@ +// +// Calendar Notifications Plus +// Copyright (C) 2025 William Harris (wharris+cnplus@upscalews.com) +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +package com.github.quarck.calnotify.ui + +import android.content.DialogInterface +import android.os.Bundle +import android.text.format.DateUtils +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Button +import android.widget.CheckBox +import android.widget.DatePicker +import android.widget.RadioButton +import android.widget.RadioGroup +import android.widget.TimePicker +import android.widget.TextView +import androidx.appcompat.app.AlertDialog +import androidx.core.os.bundleOf +import androidx.fragment.app.setFragmentResult +import com.github.quarck.calnotify.R +import com.github.quarck.calnotify.Settings +import com.github.quarck.calnotify.prefs.PreferenceUtils +import com.github.quarck.calnotify.utils.findOrThrow +import com.github.quarck.calnotify.utils.hourCompat +import com.github.quarck.calnotify.utils.minuteCompat +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import java.util.Calendar + +/** + * Bottom sheet for filtering active events by snoozed-until time. + * Features configurable interval presets, before/after toggle, + * and custom period / specific date-time pickers. + */ +class SnoozedUntilFilterBottomSheet : BottomSheetDialogFragment() { + + private val CUSTOM_PERIOD_RADIO_ID = View.generateViewId() + private val SPECIFIC_TIME_RADIO_ID = View.generateViewId() + + private var customPeriodMillis: Long = 0L + private var specificTimeMillis: Long = 0L + + private val currentConfig: SnoozedUntilFilterConfig + get() { + val args = arguments ?: return SnoozedUntilFilterConfig() + return SnoozedUntilFilterConfig( + mode = SnoozedUntilFilterMode.entries.getOrNull( + args.getInt(ARG_MODE, 0) + ) ?: SnoozedUntilFilterMode.ALL, + direction = FilterDirection.entries.getOrNull( + args.getInt(ARG_DIRECTION, 0) + ) ?: FilterDirection.BEFORE, + valueMillis = args.getLong(ARG_VALUE, 0L), + includeUnsnoozed = args.getBoolean(ARG_INCLUDE_UNSNOOZED, false) + ) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + return inflater.inflate(R.layout.bottom_sheet_snoozed_until_filter, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + val settings = Settings(requireContext()) + val directionGroup = view.findViewById(R.id.direction_toggle) + val radioGroup = view.findViewById(R.id.snoozed_until_radio_group).apply { + isSaveEnabled = false + } + val applyButton = view.findViewById