Make the clock agenda's calendars and row count configurable
The agenda part filtered calendars through the search settings and always collapsed after four rows. It now has its own selection: an exclusion set (`clockWidgetCalendarPartExcludedCalendars`, empty = all) that is added to the search settings' exclusions, because the picker only offers calendars those settings leave enabled. The row cap is `clockWidgetCalendarPartMaxRows` (1..8, default 4); the agenda shows that many events and then a "+N more events" row if more are left, so with the default five events now render as four rows plus "+1 more event" instead of three plus "+2". Both are configured in `ConfigureCalendarPart`, shown by the clock sheet while the Events part is enabled: a slider, a Calendars row that expands into a colour-tinted checkbox per calendar, and a button to Settings -> Search -> Calendar (new `ROUTE_SEARCH_CALENDARS`) for calendars the search settings disable. A missing calendar permission shows a banner with a Grant button. New setting fields default to the previous behaviour, so no datastore migration is needed.
This commit is contained in:
19
CLAUDE.md
19
CLAUDE.md
@@ -59,12 +59,21 @@ changes on top of that:
|
||||
* **A new part shows today's agenda.** `CalendarPartProvider`
|
||||
(`app/ui/.../widgets/clock/parts/`) is toggled with the *Events* switch
|
||||
(`clockWidgetCalendarPart`). It lists the events that are still running or upcoming today, plus
|
||||
today's all-day events, capped at 4 rows — if there are more, the last row is a "+N more events"
|
||||
hint that opens the calendar app. Tapping a row opens the event
|
||||
today's all-day events, capped at `clockWidgetCalendarPartMaxRows` rows (1..8, default 4) — if
|
||||
there are more, the rows are followed by a "+N more events" hint that opens the calendar app.
|
||||
Tapping a row opens the event
|
||||
(`CalendarEvent.launch`, i.e. `ACTION_VIEW` on `content://com.android.calendar/events/<id>`,
|
||||
which KashCal handles). Rows reuse `CalendarEvent.getSummary()` from
|
||||
`ui/launcher/search/calendar/CalendarItem.kt` (made `internal` for this), so an all-day event
|
||||
reads "All day" and a timed one reads e.g. "09:00 – 09:30".
|
||||
* **The agenda picks its calendars.** `clockWidgetCalendarPartExcludedCalendars` (empty = all)
|
||||
hides individual calendars; the query's exclusion set is that set plus the search settings'
|
||||
exclusions, because the picker only ever offers calendars the search settings leave enabled
|
||||
(`ClockWidgetSettingsScreenVM.calendars` filters them out of `CalendarRepository.getCalendars()`,
|
||||
and a "Manage calendars" button opens `Settings → Search → Calendar` via the
|
||||
`ROUTE_SEARCH_CALENDARS` route in `SettingsActivity`). The whole calendar configuration lives in
|
||||
`ConfigureCalendarPart.kt`, shown by `ConfigureClockWidgetSheet` while the Events part is on —
|
||||
the agenda deliberately ignores the calendar *widget*'s own `excludedCalendarIds`.
|
||||
|
||||
Rankings, in the order the zone prefers them: `CalendarPartProvider` 70 when an event is running or
|
||||
starts within 30 minutes, else 30 while there are events left today, else 0 (so the date part keeps
|
||||
@@ -78,8 +87,10 @@ launcher only re-query when they become visible). `PartProvider.setTime()` is ca
|
||||
by the clock widget; the provider folds that into its ranking instead of re-querying.
|
||||
|
||||
The clock settings themselves are in `core/preferences`: `LauncherSettingsData` (new fields
|
||||
`clockWidgetCalendarPart`, `clockWidgetDynamicZoneSlots`), `ClockWidgetParts` and the
|
||||
`ClockWidgetSettings` accessors in `core/preferences/.../ui/ClockWidgetSettings.kt`, and the UI
|
||||
`clockWidgetCalendarPart`, `clockWidgetCalendarPartExcludedCalendars`,
|
||||
`clockWidgetCalendarPartMaxRows`, `clockWidgetDynamicZoneSlots`), `ClockWidgetParts` and the
|
||||
`ClockWidgetSettings` accessors in `core/preferences/.../ui/ClockWidgetSettings.kt` (including the
|
||||
`MinCalendarPartRows`/`MaxCalendarPartRows` bounds), and the UI
|
||||
state in `ui/settings/clockwidget/ClockWidgetSettingsScreenVM.kt`. New strings are English-only in
|
||||
`core/i18n/src/main/res/values/strings.xml`; translations come from Crowdin upstream.
|
||||
|
||||
|
||||
@@ -709,6 +709,14 @@ fun ConfigureClockWidgetSheet(
|
||||
}
|
||||
}
|
||||
}
|
||||
if (smartspacer == false) {
|
||||
AnimatedVisibility(visible = parts?.calendar == true) {
|
||||
ConfigureCalendarPart(
|
||||
modifier = Modifier.padding(top = 16.dp),
|
||||
viewModel = viewModel,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
package de.mm20.launcher2.ui.launcher.widgets.clock
|
||||
|
||||
import android.content.Intent
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.requiredSize
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.CheckboxDefaults
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedCard
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import de.mm20.launcher2.permissions.PermissionGroup
|
||||
import de.mm20.launcher2.permissions.PermissionsManager
|
||||
import de.mm20.launcher2.preferences.ui.MaxCalendarPartRows
|
||||
import de.mm20.launcher2.preferences.ui.MinCalendarPartRows
|
||||
import de.mm20.launcher2.themes.colors.atTone
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.component.MissingPermissionBanner
|
||||
import de.mm20.launcher2.ui.component.preferences.CheckboxPreference
|
||||
import de.mm20.launcher2.ui.component.preferences.Preference
|
||||
import de.mm20.launcher2.ui.component.preferences.SliderPreference
|
||||
import de.mm20.launcher2.ui.locals.LocalDarkTheme
|
||||
import de.mm20.launcher2.ui.settings.SettingsActivity
|
||||
import de.mm20.launcher2.ui.settings.clockwidget.ClockWidgetSettingsScreenVM
|
||||
import org.koin.compose.koinInject
|
||||
|
||||
/**
|
||||
* Settings for the agenda in the clock widget's dynamic zone: how many events are shown
|
||||
* before they collapse into a "+N more events" row, and which calendars they come from.
|
||||
*/
|
||||
@Composable
|
||||
internal fun ConfigureCalendarPart(
|
||||
viewModel: ClockWidgetSettingsScreenVM,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val activity = LocalLifecycleOwner.current as? AppCompatActivity
|
||||
val permissionsManager: PermissionsManager = koinInject()
|
||||
|
||||
val calendars by viewModel.calendars.collectAsStateWithLifecycle()
|
||||
val excludedCalendars by viewModel.calendarPartExcludedCalendars.collectAsStateWithLifecycle()
|
||||
val maxRows by viewModel.calendarPartMaxRows.collectAsStateWithLifecycle()
|
||||
val hasPermission by viewModel.hasCalendarPermission.collectAsStateWithLifecycle()
|
||||
|
||||
var showCalendars by remember { mutableStateOf(false) }
|
||||
|
||||
val shownCalendars = calendars.count { it.id !in excludedCalendars }
|
||||
|
||||
Column(modifier = modifier) {
|
||||
if (!hasPermission) {
|
||||
MissingPermissionBanner(
|
||||
modifier = Modifier.padding(bottom = 8.dp),
|
||||
text = stringResource(R.string.missing_permission_calendar_widget_settings),
|
||||
onClick = {
|
||||
activity?.let {
|
||||
permissionsManager.requestPermission(it, PermissionGroup.Calendar)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
OutlinedCard(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
SliderPreference(
|
||||
title = stringResource(R.string.preference_clockwidget_calendar_part_events),
|
||||
icon = R.drawable.event_24px,
|
||||
value = maxRows,
|
||||
min = MinCalendarPartRows,
|
||||
max = MaxCalendarPartRows,
|
||||
step = 1,
|
||||
onValueChanged = {
|
||||
viewModel.setCalendarPartMaxRows(it)
|
||||
}
|
||||
)
|
||||
HorizontalDivider()
|
||||
Preference(
|
||||
title = stringResource(R.string.preference_clockwidget_calendar_part_calendars),
|
||||
summary = when {
|
||||
calendars.isEmpty() -> stringResource(R.string.preference_clockwidget_calendar_part_no_calendars)
|
||||
shownCalendars == calendars.size -> stringResource(R.string.preference_clockwidget_calendar_part_all_calendars)
|
||||
else -> stringResource(
|
||||
R.string.preference_clockwidget_calendar_part_calendars_summary,
|
||||
shownCalendars,
|
||||
calendars.size,
|
||||
)
|
||||
},
|
||||
icon = R.drawable.calendar_today_24px,
|
||||
onClick = { showCalendars = !showCalendars },
|
||||
enabled = calendars.isNotEmpty(),
|
||||
controls = {
|
||||
Icon(
|
||||
painter = painterResource(
|
||||
if (showCalendars) R.drawable.keyboard_arrow_up_24px
|
||||
else R.drawable.keyboard_arrow_down_24px
|
||||
),
|
||||
contentDescription = null,
|
||||
)
|
||||
},
|
||||
)
|
||||
AnimatedVisibility(
|
||||
visible = showCalendars && calendars.isNotEmpty(),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
HorizontalDivider()
|
||||
for (calendar in calendars) {
|
||||
CheckboxPreference(
|
||||
title = calendar.name,
|
||||
summary = calendar.owner,
|
||||
iconPadding = false,
|
||||
value = calendar.id !in excludedCalendars,
|
||||
onValueChanged = {
|
||||
viewModel.setCalendarExcluded(calendar.id, !it)
|
||||
},
|
||||
checkboxColors = CheckboxDefaults.colors(
|
||||
checkedColor = if (calendar.color == 0) MaterialTheme.colorScheme.primary
|
||||
else Color(
|
||||
calendar.color.atTone(if (LocalDarkTheme.current) 80 else 40)
|
||||
),
|
||||
checkmarkColor = if (calendar.color == 0) MaterialTheme.colorScheme.onPrimary
|
||||
else Color(
|
||||
calendar.color.atTone(if (LocalDarkTheme.current) 20 else 100)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
TextButton(
|
||||
modifier = Modifier
|
||||
.padding(top = 8.dp)
|
||||
.align(Alignment.CenterHorizontally),
|
||||
contentPadding = ButtonDefaults.TextButtonWithIconContentPadding,
|
||||
onClick = {
|
||||
context.startActivity(
|
||||
Intent(context, SettingsActivity::class.java).apply {
|
||||
putExtra(
|
||||
SettingsActivity.EXTRA_ROUTE,
|
||||
SettingsActivity.ROUTE_SEARCH_CALENDARS,
|
||||
)
|
||||
}
|
||||
)
|
||||
},
|
||||
) {
|
||||
Text(stringResource(R.string.preference_clockwidget_calendar_part_manage_calendars))
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.padding(start = ButtonDefaults.IconSpacing)
|
||||
.requiredSize(ButtonDefaults.IconSize),
|
||||
painter = painterResource(R.drawable.open_in_new_20px),
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,7 @@ import de.mm20.launcher2.permissions.PermissionGroup
|
||||
import de.mm20.launcher2.permissions.PermissionsManager
|
||||
import de.mm20.launcher2.preferences.TimeFormat
|
||||
import de.mm20.launcher2.preferences.search.CalendarSearchSettings
|
||||
import de.mm20.launcher2.preferences.ui.ClockWidgetSettings
|
||||
import de.mm20.launcher2.search.CalendarEvent
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.locals.LocalTimeFormat
|
||||
@@ -65,10 +66,10 @@ import java.time.OffsetDateTime
|
||||
import java.util.Date
|
||||
|
||||
/**
|
||||
* Highest number of rows the agenda shows. If there are more events than this, the last
|
||||
* row is a "+N more events" hint.
|
||||
* Number of rows the agenda shows before it collapses into a "+N more events" row, until the
|
||||
* setting has been read.
|
||||
*/
|
||||
private const val MaxRows = 4
|
||||
private const val DefaultMaxRows = 4
|
||||
|
||||
/** Re-query interval, so the agenda survives calendar changes that don't notify. */
|
||||
private const val RefreshInterval = 15 * 60 * 1000L
|
||||
@@ -96,13 +97,17 @@ class CalendarPartProvider : PartProvider, KoinComponent {
|
||||
private val calendarRepository: CalendarRepository by inject()
|
||||
private val permissionsManager: PermissionsManager by inject()
|
||||
private val searchSettings: CalendarSearchSettings by inject()
|
||||
private val clockWidgetSettings: ClockWidgetSettings by inject()
|
||||
|
||||
private val agenda = MutableStateFlow<List<CalendarEvent>>(emptyList())
|
||||
|
||||
private val maxRows = MutableStateFlow(DefaultMaxRows)
|
||||
|
||||
override fun getRanking(context: Context): Flow<Int> = channelFlow {
|
||||
// Load the agenda in the background; the ranking itself only depends on the
|
||||
// already known agenda, so it can be emitted right away.
|
||||
launch { observeAgenda(context) }
|
||||
launch { clockWidgetSettings.calendarPartMaxRows.collect { maxRows.value = it } }
|
||||
agenda.map { ranking(it) }
|
||||
.distinctUntilChanged()
|
||||
.collect { send(it) }
|
||||
@@ -112,8 +117,17 @@ class CalendarPartProvider : PartProvider, KoinComponent {
|
||||
combine(
|
||||
dayChanges(context),
|
||||
permissionsManager.hasPermission(PermissionGroup.Calendar),
|
||||
clockWidgetSettings.calendarPartExcludedCalendars,
|
||||
searchSettings.excludedCalendars,
|
||||
) { now, hasPermission, excludedCalendars -> Triple(now, hasPermission, excludedCalendars) }
|
||||
) { now, hasPermission, hiddenInWidget, excludedInSearch ->
|
||||
AgendaInputs(
|
||||
now = now,
|
||||
hasCalendarPermission = hasPermission,
|
||||
// Calendars the user disabled in the search settings cannot be picked in the
|
||||
// widget either, so the two sets just add up.
|
||||
excludedCalendars = if (hasPermission) hiddenInWidget + excludedInSearch else emptySet(),
|
||||
)
|
||||
}
|
||||
.collectLatest { (now, hasPermission, excludedCalendars) ->
|
||||
agenda.value = if (!hasPermission) {
|
||||
emptyList()
|
||||
@@ -176,13 +190,15 @@ class CalendarPartProvider : PartProvider, KoinComponent {
|
||||
@Composable
|
||||
override fun Component(compactLayout: Boolean) {
|
||||
val events by agenda.collectAsState()
|
||||
val maxRows by this.maxRows.collectAsState()
|
||||
val context = LocalContext.current
|
||||
if (events.isEmpty()) return
|
||||
|
||||
val timeFormat = LocalTimeFormat.current
|
||||
val contentColor = LocalContentColor.current
|
||||
|
||||
val visibleCount = if (events.size > MaxRows) MaxRows - 1 else events.size
|
||||
// The configured number of events, then a "+N more events" row if there are more.
|
||||
val visibleCount = minOf(events.size, maxRows)
|
||||
val hiddenCount = events.size - visibleCount
|
||||
|
||||
Column(
|
||||
@@ -216,6 +232,13 @@ class CalendarPartProvider : PartProvider, KoinComponent {
|
||||
}
|
||||
}
|
||||
|
||||
/** The three inputs the agenda query depends on. */
|
||||
private data class AgendaInputs(
|
||||
val now: Long,
|
||||
val hasCalendarPermission: Boolean,
|
||||
val excludedCalendars: Set<String>,
|
||||
)
|
||||
|
||||
/**
|
||||
* The time of an event, or "all-day" for all-day events. Never a date.
|
||||
*/
|
||||
|
||||
@@ -422,6 +422,7 @@ class SettingsActivity : BaseActivity() {
|
||||
ROUTE_MEDIA_INTEGRATION -> MediaIntegrationSettingsRoute
|
||||
ROUTE_SEARCH_ACTIONS -> SearchActionsSettingsRoute
|
||||
ROUTE_HIDDEN_ITEMS -> HiddenItemsSettingsRoute
|
||||
ROUTE_SEARCH_CALENDARS -> CalendarSearchSettingsRoute
|
||||
ROUTE_CRASH_REPORT if (intent.hasExtra(EXTRA_CRASH_REPORT_PATH)) -> {
|
||||
CrashReportRoute(intent.getStringExtra(EXTRA_CRASH_REPORT_PATH)!!)
|
||||
}
|
||||
@@ -435,6 +436,7 @@ class SettingsActivity : BaseActivity() {
|
||||
const val ROUTE_MEDIA_INTEGRATION = "settings/integrations/media"
|
||||
const val ROUTE_SEARCH_ACTIONS = "settings/search/searchactions"
|
||||
const val ROUTE_HIDDEN_ITEMS = "settings/search/hiddenitems"
|
||||
const val ROUTE_SEARCH_CALENDARS = "settings/search/calendar"
|
||||
const val ROUTE_CRASH_REPORT = "settings/debug/crashreport"
|
||||
const val EXTRA_CRASH_REPORT_PATH = "crash_report_path"
|
||||
}
|
||||
|
||||
@@ -2,11 +2,16 @@ package de.mm20.launcher2.ui.settings.clockwidget
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import de.mm20.launcher2.calendar.CalendarRepository
|
||||
import de.mm20.launcher2.calendar.providers.CalendarList
|
||||
import de.mm20.launcher2.permissions.PermissionGroup
|
||||
import de.mm20.launcher2.permissions.PermissionsManager
|
||||
import de.mm20.launcher2.preferences.BatteryStatusVisibility
|
||||
import de.mm20.launcher2.preferences.ClockWidgetAlignment
|
||||
import de.mm20.launcher2.preferences.ClockWidgetColors
|
||||
import de.mm20.launcher2.preferences.ClockWidgetStyle
|
||||
import de.mm20.launcher2.preferences.TimeFormat
|
||||
import de.mm20.launcher2.preferences.search.CalendarSearchSettings
|
||||
import de.mm20.launcher2.preferences.ui.ClockWidgetSettings
|
||||
import de.mm20.launcher2.preferences.ui.UiSettings
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
@@ -19,6 +24,9 @@ import org.koin.core.component.inject
|
||||
class ClockWidgetSettingsScreenVM : ViewModel(), KoinComponent {
|
||||
private val settings: ClockWidgetSettings by inject()
|
||||
private val uiSettings: UiSettings by inject()
|
||||
private val calendarRepository: CalendarRepository by inject()
|
||||
private val calendarSearchSettings: CalendarSearchSettings by inject()
|
||||
private val permissionsManager: PermissionsManager by inject()
|
||||
|
||||
val compact = settings.compact
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
|
||||
@@ -106,6 +114,40 @@ class ClockWidgetSettingsScreenVM : ViewModel(), KoinComponent {
|
||||
settings.setCalendarPart(calendarPart)
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of event rows in the agenda before it collapses into a "+N more events" row.
|
||||
*/
|
||||
val calendarPartMaxRows = settings.calendarPartMaxRows
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), 4)
|
||||
|
||||
fun setCalendarPartMaxRows(rows: Int) {
|
||||
settings.setCalendarPartMaxRows(rows)
|
||||
}
|
||||
|
||||
val calendarPartExcludedCalendars = settings.calendarPartExcludedCalendars
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptySet<String>())
|
||||
|
||||
fun setCalendarExcluded(calendarId: String, excluded: Boolean) {
|
||||
settings.setCalendarExcluded(calendarId, excluded)
|
||||
}
|
||||
|
||||
/**
|
||||
* Calendars the agenda can show, i.e. every calendar the search settings make available.
|
||||
* Calendars disabled there are missing here on purpose.
|
||||
*/
|
||||
val calendars = combine(
|
||||
calendarRepository.getCalendars(),
|
||||
calendarSearchSettings.excludedCalendars,
|
||||
) { calendars, excluded ->
|
||||
calendars
|
||||
.filter { it.id !in excluded }
|
||||
.sortedWith(compareBy({ it.owner ?: "" }, { it.name }))
|
||||
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList<CalendarList>())
|
||||
|
||||
/** Whether the agenda can show anything at all. */
|
||||
val hasCalendarPermission = permissionsManager.hasPermission(PermissionGroup.Calendar)
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), true)
|
||||
|
||||
val slots = settings.slots
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), 1)
|
||||
|
||||
|
||||
@@ -615,6 +615,12 @@
|
||||
<string name="preference_clockwidget_alarm_part_summary">Show alarms that will ring within the next 24 hours</string>
|
||||
<string name="preference_clockwidget_calendar_part">Events</string>
|
||||
<string name="preference_clockwidget_calendar_part_summary">Show today\'s agenda</string>
|
||||
<string name="preference_clockwidget_calendar_part_events">Visible events</string>
|
||||
<string name="preference_clockwidget_calendar_part_calendars">Calendars</string>
|
||||
<string name="preference_clockwidget_calendar_part_all_calendars">All calendars</string>
|
||||
<string name="preference_clockwidget_calendar_part_calendars_summary">%1$d of %2$d calendars</string>
|
||||
<string name="preference_clockwidget_calendar_part_no_calendars">No calendars found</string>
|
||||
<string name="preference_clockwidget_calendar_part_manage_calendars">Manage calendars</string>
|
||||
<string name="clockwidget_calendar_part_all_day">all-day</string>
|
||||
<plurals name="clockwidget_calendar_part_more_events">
|
||||
<item quantity="one">+%d more event</item>
|
||||
|
||||
@@ -59,6 +59,8 @@ data class LauncherSettingsData internal constructor(
|
||||
val clockWidgetMusicPart: Boolean = true,
|
||||
val clockWidgetDatePart: Boolean = true,
|
||||
val clockWidgetCalendarPart: Boolean = false,
|
||||
val clockWidgetCalendarPartExcludedCalendars: Set<String> = emptySet(),
|
||||
val clockWidgetCalendarPartMaxRows: Int = 4,
|
||||
val clockWidgetDynamicZoneSlots: Int = 1,
|
||||
val clockWidgetFillHeight: Boolean = false,
|
||||
val clockWidgetAlignment: ClockWidgetAlignment = ClockWidgetAlignment.Bottom,
|
||||
|
||||
@@ -11,6 +11,10 @@ import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
/** Bounds for the number of event rows the agenda shows before collapsing. */
|
||||
const val MinCalendarPartRows = 1
|
||||
const val MaxCalendarPartRows = 8
|
||||
|
||||
data class ClockWidgetParts(
|
||||
val date: Boolean,
|
||||
val music: Boolean = false,
|
||||
@@ -61,6 +65,39 @@ class ClockWidgetSettings internal constructor(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calendars the agenda does not show. Empty means "all calendars".
|
||||
*/
|
||||
val calendarPartExcludedCalendars
|
||||
get() = launcherDataStore.data.map { it.clockWidgetCalendarPartExcludedCalendars }
|
||||
.distinctUntilChanged()
|
||||
|
||||
fun setCalendarExcluded(calendarId: String, excluded: Boolean) {
|
||||
launcherDataStore.update {
|
||||
it.copy(
|
||||
clockWidgetCalendarPartExcludedCalendars =
|
||||
if (excluded) it.clockWidgetCalendarPartExcludedCalendars + calendarId
|
||||
else it.clockWidgetCalendarPartExcludedCalendars - calendarId
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of event rows in the agenda before it collapses into a "+N more events" row.
|
||||
*/
|
||||
val calendarPartMaxRows
|
||||
get() = launcherDataStore.data
|
||||
.map { it.clockWidgetCalendarPartMaxRows.coerceIn(MinCalendarPartRows, MaxCalendarPartRows) }
|
||||
.distinctUntilChanged()
|
||||
|
||||
fun setCalendarPartMaxRows(rows: Int) {
|
||||
launcherDataStore.update {
|
||||
it.copy(
|
||||
clockWidgetCalendarPartMaxRows = rows.coerceIn(MinCalendarPartRows, MaxCalendarPartRows)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun setDatePart(datePart: Boolean) {
|
||||
launcherDataStore.update {
|
||||
it.copy(clockWidgetDatePart = datePart)
|
||||
|
||||
Reference in New Issue
Block a user