diff --git a/CHANGELOG.md b/CHANGELOG.md index 34b5dc1..9e901b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 searching by city ("new york"), IANA id ("europe/berlin"), or abbreviation ("CEST") to gather every matching zone at once. All-day events stay date-anchored and carry no zone, as before ([#31]). +- Put the "jump to today" button in the toolbar. A new **Today button in + toolbar** setting (Settings → Appearance, off by default) swaps the floating + button that fades into the corner while you're away from today for a permanent + today icon in the top bar — always there, on today or not, matching the + familiar calendar-app pattern. Leave it off to keep the floating button as + before ([#60]). ### Changed - Dates in the Month, Week and Day title bars now follow your language and diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/data/prefs/SettingsPrefs.kt b/app/src/main/java/de/jeanlucmakiola/calendula/data/prefs/SettingsPrefs.kt index 2b0d8ea..1026f2e 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/data/prefs/SettingsPrefs.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/data/prefs/SettingsPrefs.kt @@ -234,6 +234,21 @@ class SettingsPrefs @Inject constructor( store.edit { it[SHOW_WEEK_NUMBERS_KEY] = enabled } } + /** + * Where the jump-to-today control lives (issue #60). Default OFF — the + * historical layout, where it's an extended FAB that fades in above the "+" + * only while the view is off today. Turning it ON moves it to a persistent + * icon button in each calendar view's top bar (always shown, on today or + * not) and drops the FAB pill. + */ + val todayButtonInToolbar: Flow = store.data.map { prefs -> + prefs[TODAY_BUTTON_IN_TOOLBAR_KEY] ?: false + } + + suspend fun setTodayButtonInToolbar(enabled: Boolean) { + store.edit { it[TODAY_BUTTON_IN_TOOLBAR_KEY] = enabled } + } + /** * How far ahead the in-app Agenda screen shows events (v2.11). Defaults to * [AgendaRange.Month] — a month of upcoming events. Independent of the @@ -738,6 +753,7 @@ class SettingsPrefs @Inject constructor( internal val PAST_EVENT_DISPLAY_KEY = stringPreferencesKey("agenda_past_event_display") internal val DIM_COMPLETED_EVENTS_KEY = booleanPreferencesKey("dim_completed_events") internal val SHOW_WEEK_NUMBERS_KEY = booleanPreferencesKey("show_week_numbers") + internal val TODAY_BUTTON_IN_TOOLBAR_KEY = booleanPreferencesKey("today_button_in_toolbar") internal val DEFAULT_VIEW_KEY = stringPreferencesKey("default_view") internal val QUICK_SWITCH_VIEWS_KEY = stringPreferencesKey("quick_switch_views") internal val DRAWER_VIEW_ORDER_KEY = stringPreferencesKey("drawer_view_order") diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/CalendarHost.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/CalendarHost.kt index c18fd34..cf5abea 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/CalendarHost.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/CalendarHost.kt @@ -88,6 +88,8 @@ fun CalendarHost( // sensible non-empty initial values, so they're ready before the first frame. val quickSwitchViews = viewModel.quickSwitchViews.collectAsStateWithLifecycle().value val drawerViewOrder = viewModel.drawerViewOrder.collectAsStateWithLifecycle().value + // Whether the jump-to-today control sits in each view's top bar or the FAB (#60). + val todayInToolbar = viewModel.todayButtonInToolbar.collectAsStateWithLifecycle().value var viewStack by rememberSaveable(stateSaver = viewStackSaver) { mutableStateOf(listOf(defaultView)) @@ -318,6 +320,7 @@ fun CalendarHost( onCreateEvent = onCreateEvent, quickSwitchViews = quickSwitchViews, drawerViewOrder = drawerViewOrder, + todayInToolbar = todayInToolbar, ) CalendarView.Day -> DayScreen( selectedView = currentView, @@ -329,6 +332,7 @@ fun CalendarHost( initialDateIso = pendingDayIso, quickSwitchViews = quickSwitchViews, drawerViewOrder = drawerViewOrder, + todayInToolbar = todayInToolbar, ) CalendarView.Month -> MonthScreen( selectedView = currentView, @@ -339,6 +343,7 @@ fun CalendarHost( onCreateEvent = onCreateEvent, quickSwitchViews = quickSwitchViews, drawerViewOrder = drawerViewOrder, + todayInToolbar = todayInToolbar, ) CalendarView.Agenda -> AgendaScreen( selectedView = currentView, @@ -350,6 +355,7 @@ fun CalendarHost( onCreateEvent = onCreateEvent, quickSwitchViews = quickSwitchViews, drawerViewOrder = drawerViewOrder, + todayInToolbar = todayInToolbar, ) } } diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/CalendarHostViewModel.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/CalendarHostViewModel.kt index 773f76d..a5f3104 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/CalendarHostViewModel.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/CalendarHostViewModel.kt @@ -45,4 +45,12 @@ class CalendarHostViewModel @Inject constructor( started = SharingStarted.WhileSubscribed(5_000L), initialValue = IMPLEMENTED_VIEWS, ) + + /** Whether each view's jump-to-today control lives in the top bar, not the FAB (#60). */ + val todayButtonInToolbar: StateFlow = prefs.todayButtonInToolbar + .stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5_000L), + initialValue = false, + ) } diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/agenda/AgendaScreen.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/agenda/AgendaScreen.kt index 0f5b5c2..296adaf 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/agenda/AgendaScreen.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/agenda/AgendaScreen.kt @@ -65,6 +65,7 @@ import de.jeanlucmakiola.calendula.ui.common.agendaRangeLabel import de.jeanlucmakiola.floret.identity.animateItemMotion import de.jeanlucmakiola.calendula.ui.common.CalendarDrawer import de.jeanlucmakiola.calendula.ui.common.CalendarFabColumn +import de.jeanlucmakiola.calendula.ui.common.TodayAction import de.jeanlucmakiola.calendula.ui.common.CalendarFailure import de.jeanlucmakiola.calendula.ui.common.CalendarView import de.jeanlucmakiola.calendula.ui.common.IMPLEMENTED_VIEWS @@ -102,6 +103,7 @@ fun AgendaScreen( onCreateEvent: (LocalDate, Int?) -> Unit, quickSwitchViews: List = IMPLEMENTED_VIEWS, drawerViewOrder: List = IMPLEMENTED_VIEWS, + todayInToolbar: Boolean = false, modifier: Modifier = Modifier, viewModel: AgendaViewModel = hiltViewModel(), ) { @@ -151,12 +153,14 @@ fun AgendaScreen( onCycleView = { onSelectView(selectedView.next(quickSwitchViews)) }, onOpenDrawer = { scope.launch { drawerState.open() } }, onOpenSearch = onOpenSearch, + showTodayButton = todayInToolbar, + onToday = viewModel::goToToday, scrollBehavior = scrollBehavior, ) }, floatingActionButton = { CalendarFabColumn( - todayVisible = !isOnToday, + todayVisible = !isOnToday && !todayInToolbar, todayText = stringResource(R.string.agenda_today_action), onToday = viewModel::goToToday, onCreate = { onCreateEvent(anchor, null) }, @@ -509,6 +513,8 @@ private fun AgendaTopBar( onCycleView: () -> Unit, onOpenDrawer: () -> Unit, onOpenSearch: () -> Unit, + showTodayButton: Boolean, + onToday: () -> Unit, scrollBehavior: TopAppBarScrollBehavior, ) { TopAppBar( @@ -527,6 +533,7 @@ private fun AgendaTopBar( } }, actions = { + TodayAction(show = showTodayButton, onToday = onToday) IconButton(onClick = onOpenSearch) { Icon( imageVector = Icons.Default.Search, diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/TodayAction.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/TodayAction.kt new file mode 100644 index 0000000..1234bb1 --- /dev/null +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/TodayAction.kt @@ -0,0 +1,26 @@ +package de.jeanlucmakiola.calendula.ui.common + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Today +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource +import de.jeanlucmakiola.calendula.R + +/** + * The top-bar "jump to today" icon button, shared by every calendar view's app + * bar (#60). Shown in place of the fade-in FAB pill when the user moves the + * today control into the toolbar; renders nothing when [show] is false, so it + * drops straight into an app bar's `actions` slot without a wrapping condition. + */ +@Composable +fun TodayAction(show: Boolean, onToday: () -> Unit) { + if (!show) return + IconButton(onClick = onToday) { + Icon( + imageVector = Icons.Default.Today, + contentDescription = stringResource(R.string.today_jump_action), + ) + } +} diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/day/DayScreen.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/day/DayScreen.kt index 8507102..4526677 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/day/DayScreen.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/day/DayScreen.kt @@ -71,6 +71,7 @@ import de.jeanlucmakiola.calendula.domain.EventInstance import de.jeanlucmakiola.calendula.ui.common.formatCalendarTitle import de.jeanlucmakiola.calendula.ui.common.CalendarDrawer import de.jeanlucmakiola.calendula.ui.common.CalendarFabColumn +import de.jeanlucmakiola.calendula.ui.common.TodayAction import de.jeanlucmakiola.calendula.ui.common.CalendarFailure import de.jeanlucmakiola.calendula.ui.common.CalendarView import de.jeanlucmakiola.calendula.ui.common.IMPLEMENTED_VIEWS @@ -128,6 +129,7 @@ fun DayScreen( onCreateEvent: (LocalDate, Int?) -> Unit, quickSwitchViews: List = IMPLEMENTED_VIEWS, drawerViewOrder: List = IMPLEMENTED_VIEWS, + todayInToolbar: Boolean = false, modifier: Modifier = Modifier, initialDateIso: String? = null, viewModel: DayViewModel = hiltViewModel(), @@ -220,12 +222,14 @@ fun DayScreen( onCycleView = { onSelectView(selectedView.next(quickSwitchViews)) }, onOpenDrawer = { scope.launch { drawerState.open() } }, onOpenSearch = onOpenSearch, + showTodayButton = todayInToolbar, + onToday = jumpToToday, scrollBehavior = scrollBehavior, ) }, floatingActionButton = { CalendarFabColumn( - todayVisible = !isOnToday, + todayVisible = !isOnToday && !todayInToolbar, todayText = stringResource(R.string.day_today_action), onToday = jumpToToday, onCreate = { onCreateEvent(date, null) }, @@ -376,6 +380,8 @@ private fun DayTopBar( onCycleView: () -> Unit, onOpenDrawer: () -> Unit, onOpenSearch: () -> Unit, + showTodayButton: Boolean, + onToday: () -> Unit, scrollBehavior: androidx.compose.material3.TopAppBarScrollBehavior, ) { val locale = currentLocale() @@ -395,6 +401,7 @@ private fun DayTopBar( } }, actions = { + TodayAction(show = showTodayButton, onToday = onToday) IconButton(onClick = onOpenSearch) { Icon( imageVector = Icons.Default.Search, diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/month/MonthScreen.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/month/MonthScreen.kt index fbf5e26..7f501dc 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/month/MonthScreen.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/month/MonthScreen.kt @@ -67,6 +67,7 @@ import de.jeanlucmakiola.calendula.domain.hasEnded import de.jeanlucmakiola.calendula.ui.common.formatCalendarTitle import de.jeanlucmakiola.calendula.ui.common.CalendarDrawer import de.jeanlucmakiola.calendula.ui.common.CalendarFabColumn +import de.jeanlucmakiola.calendula.ui.common.TodayAction import de.jeanlucmakiola.calendula.ui.common.CalendarFailure import de.jeanlucmakiola.calendula.ui.common.CalendarView import de.jeanlucmakiola.calendula.ui.common.IMPLEMENTED_VIEWS @@ -106,6 +107,7 @@ fun MonthScreen( onCreateEvent: (LocalDate, Int?) -> Unit, quickSwitchViews: List = IMPLEMENTED_VIEWS, drawerViewOrder: List = IMPLEMENTED_VIEWS, + todayInToolbar: Boolean = false, modifier: Modifier = Modifier, viewModel: MonthViewModel = hiltViewModel(), ) { @@ -199,12 +201,14 @@ fun MonthScreen( onCycleView = { onSelectView(selectedView.next(quickSwitchViews)) }, onOpenDrawer = { scope.launch { drawerState.open() } }, onOpenSearch = onOpenSearch, + showTodayButton = todayInToolbar, + onToday = jumpToToday, scrollBehavior = scrollBehavior, ) }, floatingActionButton = { CalendarFabColumn( - todayVisible = !isOnCurrentMonth, + todayVisible = !isOnCurrentMonth && !todayInToolbar, todayText = stringResource(R.string.month_today_action), onToday = jumpToToday, onCreate = { @@ -308,6 +312,8 @@ private fun MonthTopBar( onCycleView: () -> Unit, onOpenDrawer: () -> Unit, onOpenSearch: () -> Unit, + showTodayButton: Boolean, + onToday: () -> Unit, scrollBehavior: androidx.compose.material3.TopAppBarScrollBehavior, ) { val locale = currentLocale() @@ -327,6 +333,7 @@ private fun MonthTopBar( } }, actions = { + TodayAction(show = showTodayButton, onToday = onToday) IconButton(onClick = onOpenSearch) { Icon( imageVector = Icons.Default.Search, diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsScreen.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsScreen.kt index dd5c76e..7339a75 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsScreen.kt @@ -571,6 +571,18 @@ private fun AppearanceScreen( position = Position.Top, onClick = { showDefaultView = true }, ) + GroupedRow( + title = stringResource(R.string.settings_today_toolbar), + summary = stringResource(R.string.settings_today_toolbar_summary), + position = Position.Middle, + trailing = { + Switch( + checked = state.todayButtonInToolbar, + onCheckedChange = viewModel::setTodayButtonInToolbar, + ) + }, + onClick = { viewModel.setTodayButtonInToolbar(!state.todayButtonInToolbar) }, + ) GroupedRow( title = stringResource(R.string.settings_week_start), summary = weekStartLabel(state.weekStart), diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsUiState.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsUiState.kt index 1bbc6c2..4fbb436 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsUiState.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsUiState.kt @@ -37,6 +37,8 @@ data class SettingsUiState( val dimCompletedEvents: Boolean = false, /** Whether the Month grid shows calendar-week numbers in a left gutter (#25). */ val showWeekNumbers: Boolean = false, + /** Whether the jump-to-today control sits in the top bar instead of the FAB (#60). */ + val todayButtonInToolbar: Boolean = false, /** How far ahead the in-app Agenda screen shows events (v2.11). */ val agendaScreenRange: AgendaRange = AgendaRange.Month, /** How far ahead the agenda widget shows events (v2.11). */ diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsViewModel.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsViewModel.kt index 22d1a6b..96727c9 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsViewModel.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsViewModel.kt @@ -124,8 +124,9 @@ class SettingsViewModel @Inject constructor( prefs.showWeekNumbers, prefs.agendaShowToday, prefs.softenCalendarColors, - ) { hourLines, weekNumbers, showToday, soften -> - DisplayToggles(hourLines, weekNumbers, showToday, soften) + prefs.todayButtonInToolbar, + ) { hourLines, weekNumbers, showToday, soften, todayInToolbar -> + DisplayToggles(hourLines, weekNumbers, showToday, soften, todayInToolbar) }, ) { view, screenRange, widgetRange, timeFormat, toggles -> ViewSettings( @@ -134,6 +135,7 @@ class SettingsViewModel @Inject constructor( showWeekNumbers = toggles.showWeekNumbers, agendaShowToday = toggles.agendaShowToday, softenColors = toggles.softenColors, + todayButtonInToolbar = toggles.todayButtonInToolbar, ) }, combine( @@ -159,6 +161,7 @@ class SettingsViewModel @Inject constructor( showWeekNumbers = views.showWeekNumbers, agendaShowToday = views.agendaShowToday, softenColors = views.softenColors, + todayButtonInToolbar = views.todayButtonInToolbar, agendaShowRangeBar = misc.showRangeBar, autofocusEventTitle = misc.autofocusEventTitle, pastEventDisplay = misc.pastEventDisplay, @@ -234,6 +237,7 @@ class SettingsViewModel @Inject constructor( val showWeekNumbers: Boolean, val agendaShowToday: Boolean, val softenColors: Boolean, + val todayButtonInToolbar: Boolean, ) private data class DisplayToggles( @@ -241,6 +245,7 @@ class SettingsViewModel @Inject constructor( val showWeekNumbers: Boolean, val agendaShowToday: Boolean, val softenColors: Boolean, + val todayButtonInToolbar: Boolean, ) private data class MiscSettings( @@ -460,6 +465,10 @@ class SettingsViewModel @Inject constructor( viewModelScope.launch { prefs.setShowWeekNumbers(enabled) } } + fun setTodayButtonInToolbar(enabled: Boolean) { + viewModelScope.launch { prefs.setTodayButtonInToolbar(enabled) } + } + fun setPastEventDisplay(mode: PastEventDisplay) { viewModelScope.launch { prefs.setPastEventDisplay(mode) diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/week/WeekScreen.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/week/WeekScreen.kt index 8cba3e2..bd44535 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/week/WeekScreen.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/week/WeekScreen.kt @@ -80,6 +80,7 @@ import de.jeanlucmakiola.calendula.domain.hasEnded import de.jeanlucmakiola.calendula.ui.common.formatCalendarTitle import de.jeanlucmakiola.calendula.ui.common.CalendarDrawer import de.jeanlucmakiola.calendula.ui.common.CalendarFabColumn +import de.jeanlucmakiola.calendula.ui.common.TodayAction import de.jeanlucmakiola.calendula.ui.common.CalendarFailure import de.jeanlucmakiola.calendula.ui.common.CalendarView import de.jeanlucmakiola.calendula.ui.common.IMPLEMENTED_VIEWS @@ -144,6 +145,7 @@ fun WeekScreen( onCreateEvent: (LocalDate, Int?) -> Unit, quickSwitchViews: List = IMPLEMENTED_VIEWS, drawerViewOrder: List = IMPLEMENTED_VIEWS, + todayInToolbar: Boolean = false, modifier: Modifier = Modifier, viewModel: WeekViewModel = hiltViewModel(), ) { @@ -241,12 +243,14 @@ fun WeekScreen( onCycleView = { onSelectView(selectedView.next(quickSwitchViews)) }, onOpenDrawer = { scope.launch { drawerState.open() } }, onOpenSearch = onOpenSearch, + showTodayButton = todayInToolbar, + onToday = jumpToToday, scrollBehavior = scrollBehavior, ) }, floatingActionButton = { CalendarFabColumn( - todayVisible = !isOnCurrentWeek, + todayVisible = !isOnCurrentWeek && !todayInToolbar, todayText = stringResource(R.string.week_today_action), onToday = jumpToToday, onCreate = { @@ -409,6 +413,8 @@ private fun WeekTopBar( onCycleView: () -> Unit, onOpenDrawer: () -> Unit, onOpenSearch: () -> Unit, + showTodayButton: Boolean, + onToday: () -> Unit, scrollBehavior: androidx.compose.material3.TopAppBarScrollBehavior, ) { val locale = currentLocale() @@ -428,6 +434,7 @@ private fun WeekTopBar( } }, actions = { + TodayAction(show = showTodayButton, onToday = onToday) IconButton(onClick = onOpenSearch) { Icon( imageVector = Icons.Default.Search, diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 43e3bdf..136c4e9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -274,6 +274,9 @@ Starts %1$s Ends %1$s + + Go to today + Search Search events @@ -324,6 +327,8 @@ Automatic Week numbers Show calendar-week numbers in month view + Today button in toolbar + Show a jump-to-today button in the toolbar instead of a floating button Time format Automatic 12-hour (2:00 PM) diff --git a/fastlane/metadata/android/en-US/changelogs/21600.txt b/fastlane/metadata/android/en-US/changelogs/21600.txt index 711f0e3..2cf7d30 100644 --- a/fastlane/metadata/android/en-US/changelogs/21600.txt +++ b/fastlane/metadata/android/en-US/changelogs/21600.txt @@ -7,6 +7,10 @@ original noted beneath. Pick a zone from a searchable full-screen picker — by city, IANA id, or abbreviation like "CEST". All-day events stay date-anchored and carry no zone ([#31]). +- Put the "jump to today" button in the toolbar. A new Today button in toolbar + setting (Settings → Appearance, off by default) swaps the floating corner + button for a permanent today icon in the top bar — always there, matching the + familiar calendar-app pattern. Leave it off to keep the floating button ([#60]). ### Changed - Dates in the Month, Week and Day title bars now follow your language and