Compare commits
2 Commits
7e843aa740
...
release/v2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e8119be02 | ||
| 2cf7d590bd |
@@ -135,6 +135,7 @@ fun AgendaScreen(
|
|||||||
AgendaTopBar(
|
AgendaTopBar(
|
||||||
selectedView = selectedView,
|
selectedView = selectedView,
|
||||||
onCycleView = { onSelectView(selectedView.next(quickSwitchViews)) },
|
onCycleView = { onSelectView(selectedView.next(quickSwitchViews)) },
|
||||||
|
quickSwitchViews = quickSwitchViews,
|
||||||
onOpenDrawer = { scope.launch { drawerState.open() } },
|
onOpenDrawer = { scope.launch { drawerState.open() } },
|
||||||
onOpenSearch = onOpenSearch,
|
onOpenSearch = onOpenSearch,
|
||||||
showTodayButton = todayInToolbar,
|
showTodayButton = todayInToolbar,
|
||||||
@@ -409,6 +410,7 @@ private fun AgendaEmpty(modifier: Modifier = Modifier) {
|
|||||||
private fun AgendaTopBar(
|
private fun AgendaTopBar(
|
||||||
selectedView: CalendarView,
|
selectedView: CalendarView,
|
||||||
onCycleView: () -> Unit,
|
onCycleView: () -> Unit,
|
||||||
|
quickSwitchViews: List<CalendarView>,
|
||||||
onOpenDrawer: () -> Unit,
|
onOpenDrawer: () -> Unit,
|
||||||
onOpenSearch: () -> Unit,
|
onOpenSearch: () -> Unit,
|
||||||
showTodayButton: Boolean,
|
showTodayButton: Boolean,
|
||||||
@@ -440,6 +442,7 @@ private fun AgendaTopBar(
|
|||||||
}
|
}
|
||||||
ViewSwitcherPill(
|
ViewSwitcherPill(
|
||||||
current = selectedView,
|
current = selectedView,
|
||||||
|
cycle = quickSwitchViews,
|
||||||
onCycle = onCycleView,
|
onCycle = onCycleView,
|
||||||
modifier = Modifier.padding(end = 8.dp),
|
modifier = Modifier.padding(end = 8.dp),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -57,7 +57,8 @@ fun CalendarView.next(available: List<CalendarView> = IMPLEMENTED_VIEWS): Calend
|
|||||||
* implemented view — the settings screen reorders the whole set — while [cycle]
|
* implemented view — the settings screen reorders the whole set — while [cycle]
|
||||||
* is the subset the pill actually steps through, in [order]. The navigation
|
* is the subset the pill actually steps through, in [order]. The navigation
|
||||||
* drawer keeps its own separate order and always lists every view, so a view
|
* drawer keeps its own separate order and always lists every view, so a view
|
||||||
* disabled here stays reachable there.
|
* disabled here stays reachable there — including when [cycle] is emptied and
|
||||||
|
* the pill disappears altogether.
|
||||||
*/
|
*/
|
||||||
data class QuickSwitchConfig(
|
data class QuickSwitchConfig(
|
||||||
val order: List<CalendarView>,
|
val order: List<CalendarView>,
|
||||||
@@ -67,14 +68,15 @@ data class QuickSwitchConfig(
|
|||||||
val cycle: List<CalendarView> get() = order.filter { it in enabled }
|
val cycle: List<CalendarView> get() = order.filter { it in enabled }
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
/**
|
||||||
|
* Fewest views that keep the switch meaningful. A single target is not a
|
||||||
|
* switch, so below this the pill is hidden rather than special-cased (#150);
|
||||||
|
* the drawer still reaches every view.
|
||||||
|
*/
|
||||||
|
const val MIN_CYCLE = 2
|
||||||
|
|
||||||
/** All views, in default order, all enabled. */
|
/** All views, in default order, all enabled. */
|
||||||
val Default = QuickSwitchConfig(IMPLEMENTED_VIEWS, IMPLEMENTED_VIEWS.toSet())
|
val Default = QuickSwitchConfig(IMPLEMENTED_VIEWS, IMPLEMENTED_VIEWS.toSet())
|
||||||
|
|
||||||
/**
|
|
||||||
* Fewest views that keep the switch meaningful — a "switch" needs at
|
|
||||||
* least two targets, so the settings screen blocks disabling below this.
|
|
||||||
*/
|
|
||||||
const val MIN_ENABLED = 2
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,13 +10,19 @@ import androidx.compose.ui.res.stringResource
|
|||||||
/**
|
/**
|
||||||
* Top-bar pill that shows the current view and cycles to the next one on tap
|
* Top-bar pill that shows the current view and cycles to the next one on tap
|
||||||
* (spec M1: Month → Week → Day → Month, restricted to [IMPLEMENTED_VIEWS]).
|
* (spec M1: Month → Week → Day → Month, restricted to [IMPLEMENTED_VIEWS]).
|
||||||
|
*
|
||||||
|
* Renders nothing when [cycle] holds fewer than [QuickSwitchConfig.MIN_CYCLE]
|
||||||
|
* views (#150), so it drops straight into an app bar's `actions` slot without a
|
||||||
|
* wrapping condition — the same way [TodayAction] handles being turned off.
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun ViewSwitcherPill(
|
fun ViewSwitcherPill(
|
||||||
current: CalendarView,
|
current: CalendarView,
|
||||||
|
cycle: List<CalendarView>,
|
||||||
onCycle: () -> Unit,
|
onCycle: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
|
if (cycle.size < QuickSwitchConfig.MIN_CYCLE) return
|
||||||
FilledTonalButton(
|
FilledTonalButton(
|
||||||
onClick = onCycle,
|
onClick = onCycle,
|
||||||
shape = MaterialTheme.shapes.large,
|
shape = MaterialTheme.shapes.large,
|
||||||
|
|||||||
@@ -243,6 +243,7 @@ fun DayScreen(
|
|||||||
currentYear = currentYear,
|
currentYear = currentYear,
|
||||||
selectedView = selectedView,
|
selectedView = selectedView,
|
||||||
onCycleView = { onSelectView(selectedView.next(quickSwitchViews)) },
|
onCycleView = { onSelectView(selectedView.next(quickSwitchViews)) },
|
||||||
|
quickSwitchViews = quickSwitchViews,
|
||||||
onOpenDrawer = { scope.launch { drawerState.open() } },
|
onOpenDrawer = { scope.launch { drawerState.open() } },
|
||||||
onOpenSearch = onOpenSearch,
|
onOpenSearch = onOpenSearch,
|
||||||
onJumpToDate = jumpToDate,
|
onJumpToDate = jumpToDate,
|
||||||
@@ -411,6 +412,7 @@ private fun DayTopBar(
|
|||||||
currentYear: Int,
|
currentYear: Int,
|
||||||
selectedView: CalendarView,
|
selectedView: CalendarView,
|
||||||
onCycleView: () -> Unit,
|
onCycleView: () -> Unit,
|
||||||
|
quickSwitchViews: List<CalendarView>,
|
||||||
onOpenDrawer: () -> Unit,
|
onOpenDrawer: () -> Unit,
|
||||||
onOpenSearch: () -> Unit,
|
onOpenSearch: () -> Unit,
|
||||||
onJumpToDate: (LocalDate) -> Unit,
|
onJumpToDate: (LocalDate) -> Unit,
|
||||||
@@ -445,6 +447,7 @@ private fun DayTopBar(
|
|||||||
}
|
}
|
||||||
ViewSwitcherPill(
|
ViewSwitcherPill(
|
||||||
current = selectedView,
|
current = selectedView,
|
||||||
|
cycle = quickSwitchViews,
|
||||||
onCycle = onCycleView,
|
onCycle = onCycleView,
|
||||||
modifier = Modifier.padding(end = 8.dp),
|
modifier = Modifier.padding(end = 8.dp),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -387,6 +387,7 @@ fun MonthScreen(
|
|||||||
titleDate = LocalDate(titleMonth.year, titleMonth.month, 1),
|
titleDate = LocalDate(titleMonth.year, titleMonth.month, 1),
|
||||||
selectedView = selectedView,
|
selectedView = selectedView,
|
||||||
onCycleView = { onSelectView(selectedView.next(quickSwitchViews)) },
|
onCycleView = { onSelectView(selectedView.next(quickSwitchViews)) },
|
||||||
|
quickSwitchViews = quickSwitchViews,
|
||||||
onOpenDrawer = { scope.launch { drawerState.open() } },
|
onOpenDrawer = { scope.launch { drawerState.open() } },
|
||||||
onOpenSearch = onOpenSearch,
|
onOpenSearch = onOpenSearch,
|
||||||
onJumpToDate = jumpToDate,
|
onJumpToDate = jumpToDate,
|
||||||
@@ -651,6 +652,7 @@ private fun MonthTopBar(
|
|||||||
titleDate: LocalDate,
|
titleDate: LocalDate,
|
||||||
selectedView: CalendarView,
|
selectedView: CalendarView,
|
||||||
onCycleView: () -> Unit,
|
onCycleView: () -> Unit,
|
||||||
|
quickSwitchViews: List<CalendarView>,
|
||||||
onOpenDrawer: () -> Unit,
|
onOpenDrawer: () -> Unit,
|
||||||
onOpenSearch: () -> Unit,
|
onOpenSearch: () -> Unit,
|
||||||
onJumpToDate: (LocalDate) -> Unit,
|
onJumpToDate: (LocalDate) -> Unit,
|
||||||
@@ -684,6 +686,7 @@ private fun MonthTopBar(
|
|||||||
}
|
}
|
||||||
ViewSwitcherPill(
|
ViewSwitcherPill(
|
||||||
current = selectedView,
|
current = selectedView,
|
||||||
|
cycle = quickSwitchViews,
|
||||||
onCycle = onCycleView,
|
onCycle = onCycleView,
|
||||||
modifier = Modifier.padding(end = 8.dp),
|
modifier = Modifier.padding(end = 8.dp),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -591,15 +591,15 @@ class SettingsViewModel @Inject constructor(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Enable or disable [view] in the quick-switch cycle, via an atomic
|
* Enable or disable [view] in the quick-switch cycle, via an atomic
|
||||||
* read-modify-write so a concurrent reorder can't clobber it. The
|
* read-modify-write so a concurrent reorder can't clobber it. Any number of
|
||||||
* MIN_ENABLED floor is re-checked inside the transform, because the screen's
|
* views may be turned off; below two the pill hides itself (#150).
|
||||||
* own guard reads an async-echoed snapshot.
|
|
||||||
*/
|
*/
|
||||||
fun setQuickSwitchViewEnabled(view: CalendarView, enabled: Boolean) {
|
fun setQuickSwitchViewEnabled(view: CalendarView, enabled: Boolean) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
prefs.updateQuickSwitch { config ->
|
prefs.updateQuickSwitch { config ->
|
||||||
val next = if (enabled) config.enabled + view else config.enabled - view
|
config.copy(
|
||||||
if (next.size < QuickSwitchConfig.MIN_ENABLED) config else config.copy(enabled = next)
|
enabled = if (enabled) config.enabled + view else config.enabled - view,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ import de.jeanlucmakiola.calendula.ui.common.AgendaRangePicker
|
|||||||
import de.jeanlucmakiola.calendula.ui.common.CalendarView
|
import de.jeanlucmakiola.calendula.ui.common.CalendarView
|
||||||
import de.jeanlucmakiola.calendula.ui.common.IMPLEMENTED_VIEWS
|
import de.jeanlucmakiola.calendula.ui.common.IMPLEMENTED_VIEWS
|
||||||
import de.jeanlucmakiola.calendula.ui.common.PickerDescription
|
import de.jeanlucmakiola.calendula.ui.common.PickerDescription
|
||||||
import de.jeanlucmakiola.calendula.ui.common.QuickSwitchConfig
|
|
||||||
import de.jeanlucmakiola.calendula.ui.common.TimelineScale
|
import de.jeanlucmakiola.calendula.ui.common.TimelineScale
|
||||||
import de.jeanlucmakiola.calendula.ui.common.agendaRangeLabel
|
import de.jeanlucmakiola.calendula.ui.common.agendaRangeLabel
|
||||||
import de.jeanlucmakiola.calendula.ui.common.descriptionRes
|
import de.jeanlucmakiola.calendula.ui.common.descriptionRes
|
||||||
@@ -55,8 +54,8 @@ import java.time.format.TextStyle as JavaTextStyle
|
|||||||
* it belongs to, plus the two cross-view ordering lists (#24, #69).
|
* it belongs to, plus the two cross-view ordering lists (#24, #69).
|
||||||
*
|
*
|
||||||
* The quick-switch cycle and the drawer list are independent orders — a view
|
* The quick-switch cycle and the drawer list are independent orders — a view
|
||||||
* off in the cycle is still reachable from the drawer. The cycle needs at least
|
* off in the cycle is still reachable from the drawer, including when the cycle
|
||||||
* [QuickSwitchConfig.MIN_ENABLED] targets.
|
* is emptied and the pill disappears (#150).
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
internal fun ViewsScreen(
|
internal fun ViewsScreen(
|
||||||
@@ -221,8 +220,6 @@ internal fun ViewsScreen(
|
|||||||
SectionHeader(stringResource(R.string.settings_quick_switch_header))
|
SectionHeader(stringResource(R.string.settings_quick_switch_header))
|
||||||
SettingsHint(stringResource(R.string.settings_quick_switch_hint))
|
SettingsHint(stringResource(R.string.settings_quick_switch_hint))
|
||||||
Spacer(Modifier.height(8.dp))
|
Spacer(Modifier.height(8.dp))
|
||||||
// Turning a view off is blocked once only the minimum remain enabled.
|
|
||||||
val canDisable = config.enabled.size > QuickSwitchConfig.MIN_ENABLED
|
|
||||||
ReorderableColumn(
|
ReorderableColumn(
|
||||||
items = config.order,
|
items = config.order,
|
||||||
keyOf = { it },
|
keyOf = { it },
|
||||||
@@ -238,8 +235,6 @@ internal fun ViewsScreen(
|
|||||||
trailing = {
|
trailing = {
|
||||||
Switch(
|
Switch(
|
||||||
checked = checked,
|
checked = checked,
|
||||||
// Keep the last two on: with fewer, the pill can't switch.
|
|
||||||
enabled = !checked || canDisable,
|
|
||||||
onCheckedChange = { on -> viewModel.setQuickSwitchViewEnabled(view, on) },
|
onCheckedChange = { on -> viewModel.setQuickSwitchViewEnabled(view, on) },
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -266,6 +266,7 @@ fun WeekScreen(
|
|||||||
currentYear = currentYear,
|
currentYear = currentYear,
|
||||||
selectedView = selectedView,
|
selectedView = selectedView,
|
||||||
onCycleView = { onSelectView(selectedView.next(quickSwitchViews)) },
|
onCycleView = { onSelectView(selectedView.next(quickSwitchViews)) },
|
||||||
|
quickSwitchViews = quickSwitchViews,
|
||||||
onOpenDrawer = { scope.launch { drawerState.open() } },
|
onOpenDrawer = { scope.launch { drawerState.open() } },
|
||||||
onOpenSearch = onOpenSearch,
|
onOpenSearch = onOpenSearch,
|
||||||
onJumpToDate = jumpToDate,
|
onJumpToDate = jumpToDate,
|
||||||
@@ -446,6 +447,7 @@ private fun WeekTopBar(
|
|||||||
currentYear: Int,
|
currentYear: Int,
|
||||||
selectedView: CalendarView,
|
selectedView: CalendarView,
|
||||||
onCycleView: () -> Unit,
|
onCycleView: () -> Unit,
|
||||||
|
quickSwitchViews: List<CalendarView>,
|
||||||
onOpenDrawer: () -> Unit,
|
onOpenDrawer: () -> Unit,
|
||||||
onOpenSearch: () -> Unit,
|
onOpenSearch: () -> Unit,
|
||||||
onJumpToDate: (LocalDate) -> Unit,
|
onJumpToDate: (LocalDate) -> Unit,
|
||||||
@@ -480,6 +482,7 @@ private fun WeekTopBar(
|
|||||||
}
|
}
|
||||||
ViewSwitcherPill(
|
ViewSwitcherPill(
|
||||||
current = selectedView,
|
current = selectedView,
|
||||||
|
cycle = quickSwitchViews,
|
||||||
onCycle = onCycleView,
|
onCycle = onCycleView,
|
||||||
modifier = Modifier.padding(end = 8.dp),
|
modifier = Modifier.padding(end = 8.dp),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -496,7 +496,7 @@
|
|||||||
<string name="month_split_expand">Show the whole month</string>
|
<string name="month_split_expand">Show the whole month</string>
|
||||||
<string name="month_split_collapse">Show the day\'s events</string>
|
<string name="month_split_collapse">Show the day\'s events</string>
|
||||||
<string name="settings_quick_switch_header">Quick-switch button</string>
|
<string name="settings_quick_switch_header">Quick-switch button</string>
|
||||||
<string name="settings_quick_switch_hint">Choose which views the top-right button cycles through, and drag to reorder them. Turned-off views stay reachable from the navigation menu.</string>
|
<string name="settings_quick_switch_hint">Choose which views the top-right button cycles through, and drag to reorder them. With fewer than two views turned on the button is hidden. Turned-off views stay reachable from the navigation menu.</string>
|
||||||
<string name="settings_drawer_order_header">Navigation menu</string>
|
<string name="settings_drawer_order_header">Navigation menu</string>
|
||||||
<string name="settings_drawer_order_hint">Drag to reorder the views listed in the navigation menu.</string>
|
<string name="settings_drawer_order_hint">Drag to reorder the views listed in the navigation menu.</string>
|
||||||
<string name="reorder_drag_handle">Drag to reorder</string>
|
<string name="reorder_drag_handle">Drag to reorder</string>
|
||||||
|
|||||||
@@ -120,4 +120,30 @@ class ViewBackStackTest {
|
|||||||
)
|
)
|
||||||
assertThat(config.cycle).containsExactly(CalendarView.Agenda, CalendarView.Month).inOrder()
|
assertThat(config.cycle).containsExactly(CalendarView.Agenda, CalendarView.Month).inOrder()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `a cycle can be emptied down to no views at all`() {
|
||||||
|
// #150: the settings screen no longer holds a floor, so every view may go.
|
||||||
|
val config = QuickSwitchConfig(order = IMPLEMENTED_VIEWS, enabled = emptySet())
|
||||||
|
assertThat(config.cycle).isEmpty()
|
||||||
|
assertThat(config.cycle.size).isLessThan(QuickSwitchConfig.MIN_CYCLE)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `one enabled view is below the cycle minimum, so the pill hides`() {
|
||||||
|
// A single target is not a switch — it hides rather than becoming a
|
||||||
|
// jump-to-one-view button, which would be dead once you were there.
|
||||||
|
val config = QuickSwitchConfig(order = IMPLEMENTED_VIEWS, enabled = setOf(CalendarView.Day))
|
||||||
|
assertThat(config.cycle).containsExactly(CalendarView.Day)
|
||||||
|
assertThat(config.cycle.size).isLessThan(QuickSwitchConfig.MIN_CYCLE)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `two enabled views are enough to show the pill`() {
|
||||||
|
val config = QuickSwitchConfig(
|
||||||
|
order = IMPLEMENTED_VIEWS,
|
||||||
|
enabled = setOf(CalendarView.Day, CalendarView.Month),
|
||||||
|
)
|
||||||
|
assertThat(config.cycle.size).isAtLeast(QuickSwitchConfig.MIN_CYCLE)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user