diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/calendar/CalendarItem.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/calendar/CalendarItem.kt index 39e58f9f..396b7dda 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/calendar/CalendarItem.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/calendar/CalendarItem.kt @@ -44,6 +44,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.roundToIntRect @@ -121,7 +122,13 @@ fun CalendarItem( this@AnimatedContent ), text = calendar.labelOverride ?: calendar.label, - style = MaterialTheme.typography.titleMedium + style = MaterialTheme.typography.titleMedium, + maxLines = 1, + textDecoration = if (calendar.isCompleted == true) { + TextDecoration.LineThrough + } else { + TextDecoration.None + } ) if (calendar.calendarName != null) { Text( @@ -300,7 +307,13 @@ fun CalendarItem( this@AnimatedContent ), text = calendar.labelOverride ?: calendar.label, - style = MaterialTheme.typography.titleSmall + style = MaterialTheme.typography.titleSmall, + maxLines = 1, + textDecoration = if (calendar.isCompleted == true) { + TextDecoration.LineThrough + } else { + TextDecoration.None + } ) Text( modifier = Modifier diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/ConfigureWidgetSheet.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/ConfigureWidgetSheet.kt index 755238da..a67abf28 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/ConfigureWidgetSheet.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/ConfigureWidgetSheet.kt @@ -12,6 +12,7 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity import androidx.browser.customtabs.CustomTabColorSchemeParams import androidx.browser.customtabs.CustomTabsIntent +import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.background import androidx.compose.foundation.gestures.awaitEachGesture import androidx.compose.foundation.gestures.awaitFirstDown @@ -34,6 +35,7 @@ import androidx.compose.material.icons.rounded.Link import androidx.compose.material.icons.rounded.LinkOff import androidx.compose.material.icons.rounded.OpenInNew 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 @@ -42,7 +44,6 @@ import androidx.compose.material3.OutlinedCard import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -52,22 +53,26 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.input.pointer.PointerEventPass import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.isUnspecified import androidx.core.net.toUri +import androidx.lifecycle.compose.LocalLifecycleOwner import de.mm20.launcher2.calendar.CalendarRepository -import de.mm20.launcher2.calendar.providers.CalendarList import de.mm20.launcher2.crashreporter.CrashReporter import de.mm20.launcher2.ktx.isAtLeastApiLevel import de.mm20.launcher2.permissions.PermissionGroup import de.mm20.launcher2.permissions.PermissionsManager +import de.mm20.launcher2.plugin.PluginRepository +import de.mm20.launcher2.plugin.PluginType +import de.mm20.launcher2.search.calendar.CalendarListType +import de.mm20.launcher2.themes.atTone import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.base.LocalAppWidgetHost import de.mm20.launcher2.ui.component.BottomSheetDialog @@ -383,7 +388,7 @@ fun ColumnScope.ConfigureAppWidget( val minHeight = if (widgetInfo.minResizeHeight in 1..widgetInfo.minHeight) { widgetInfo.minResizeHeight.toDp() } else { - widgetInfo.minHeight.toDp() + widgetInfo.minHeight.toDp() } DragResizeHandle( @@ -503,66 +508,103 @@ fun ColumnScope.ConfigureCalendarWidget( ) { val calendarRepository: CalendarRepository = get() val permissionsManager: PermissionsManager = get() + val pluginRepository: PluginRepository = get() val calendars by remember { calendarRepository.getCalendars().map { it.sortedBy { it.name } } }.collectAsState(null) + val plugins by remember { + pluginRepository.findMany( + type = PluginType.Calendar, + enabled = true, + ) + }.collectAsState(emptyList()) val hasPermission by remember { permissionsManager.hasPermission(PermissionGroup.Calendar) }.collectAsState(true) - OutlinedCard { - Column( - modifier = Modifier.fillMaxWidth() - ) { - SwitchPreference( - title = stringResource(R.string.preference_calendar_hide_allday), - iconPadding = false, - value = !widget.config.allDayEvents, - onValueChanged = { - onWidgetUpdated(widget.copy(config = widget.config.copy(allDayEvents = !it))) - } - ) - } + val hasTasks = remember(calendars) { + calendars?.any { it.types.contains(CalendarListType.Tasks) } == true } - Text( - modifier = Modifier.padding(top = 16.dp, bottom = 4.dp), - style = MaterialTheme.typography.titleSmall, - color = MaterialTheme.colorScheme.secondary, - text = stringResource(R.string.preference_calendar_calendars) - ) - val context = LocalLifecycleOwner.current as AppCompatActivity - val excludedCalendars = remember(widget.config) { - widget.config.excludedCalendarIds ?: widget.config.legacyExcludedCalendarIds?.map { "local:$it" } ?: emptyList() - } - if (calendars?.isNotEmpty() == true) { + + AnimatedVisibility(hasTasks) { OutlinedCard { Column( modifier = Modifier.fillMaxWidth() ) { - for ((i, calendar) in calendars!!.withIndex()) { - if (i > 0) HorizontalDivider() - CheckboxPreference( - title = calendar.name, - summary = calendar.owner, - iconPadding = false, - value = excludedCalendars.contains(calendar.id) != true, - onValueChanged = { - onWidgetUpdated( - widget.copy( - config = widget.config.copy( - excludedCalendarIds = if (it) { - excludedCalendars - calendar.id - } else { - excludedCalendars + calendar.id - } + SwitchPreference( + title = stringResource(R.string.preference_calendar_hide_completed), + iconPadding = false, + value = !widget.config.completedTasks, + onValueChanged = { + onWidgetUpdated(widget.copy(config = widget.config.copy(completedTasks = !it))) + } + ) + } + } + } + val context = LocalLifecycleOwner.current as AppCompatActivity + val excludedCalendars = remember(widget.config) { + widget.config.excludedCalendarIds + ?: widget.config.legacyExcludedCalendarIds?.map { "local:$it" } ?: emptyList() + } + + val groups = remember(calendars) { + calendars?.groupBy { it.providerId }?.entries + } + + if (groups?.isNotEmpty() == true) { + for (group in groups) { + val pluginName = remember(plugins, group.key) { + if (group.key == "local") context.getString(R.string.preference_calendar_calendars) + else plugins.find { it.authority == group.key }?.label + } + if (pluginName != null) { + Text( + modifier = Modifier.padding(top = 16.dp, bottom = 4.dp), + style = MaterialTheme.typography.titleSmall, + color = MaterialTheme.colorScheme.secondary, + text = pluginName + ) + } + OutlinedCard { + Column( + modifier = Modifier.fillMaxWidth() + ) { + for ((i, calendar) in group.value.withIndex()) { + if (i > 0) HorizontalDivider() + CheckboxPreference( + title = calendar.name, + summary = calendar.owner, + iconPadding = false, + value = !excludedCalendars.contains(calendar.id), + onValueChanged = { + onWidgetUpdated( + widget.copy( + config = widget.config.copy( + excludedCalendarIds = if (it) { + excludedCalendars - calendar.id + } else { + excludedCalendars + calendar.id + } + ) ) ) + }, + 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) + ) ) - } - ) + ) + } } } } diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/calendar/CalendarWidget.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/calendar/CalendarWidget.kt index 819b6b09..e7801197 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/calendar/CalendarWidget.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/calendar/CalendarWidget.kt @@ -208,7 +208,8 @@ fun CalendarWidget( ), onClick = { viewModel.showAllEvents() - } + }, + modifier = Modifier.padding(top = 8.dp) ) } if (nextEvents.isNotEmpty()) { @@ -263,10 +264,11 @@ fun CalendarWidget( @Composable private fun Info( text: String, + modifier: Modifier = Modifier, onClick: (() -> Unit)? = null, ) { Box( - modifier = Modifier + modifier = modifier .fillMaxWidth() .clip(MaterialTheme.shapes.small) .border(1.dp, MaterialTheme.colorScheme.outlineVariant, MaterialTheme.shapes.small) diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/calendar/CalendarWidgetVM.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/calendar/CalendarWidgetVM.kt index 7fd3a992..53fb063d 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/calendar/CalendarWidgetVM.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/calendar/CalendarWidgetVM.kt @@ -176,7 +176,10 @@ class CalendarWidgetVM : ViewModel(), KoinComponent { maxVisibility = VisibilityLevel.SearchOnly, limit = 9999, ).collectLatest { hidden -> - upcomingEvents = events.filter { !hidden.contains(it.key) }.sortedBy { it.startTime ?: it.endTime } + upcomingEvents = events + .filter { + !hidden.contains(it.key) && !(!config.completedTasks && it.isCompleted == true) + }.sortedBy { it.startTime ?: it.endTime } } } diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/calendarsearch/CalendarSearchSettingsScreen.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/calendarsearch/CalendarSearchSettingsScreen.kt index 48d5b87f..93c8070e 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/calendarsearch/CalendarSearchSettingsScreen.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/calendarsearch/CalendarSearchSettingsScreen.kt @@ -1,20 +1,19 @@ package de.mm20.launcher2.ui.settings.calendarsearch import android.app.PendingIntent +import android.util.Log import androidx.appcompat.app.AppCompatActivity import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.CalendarToday +import androidx.compose.material.icons.rounded.Checklist import androidx.compose.material.icons.rounded.ErrorOutline -import androidx.compose.material3.AlertDialogDefaults -import androidx.compose.material3.BasicAlertDialog import androidx.compose.material3.CheckboxDefaults import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface +import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable @@ -35,14 +34,15 @@ import androidx.lifecycle.viewmodel.compose.viewModel import de.mm20.launcher2.crashreporter.CrashReporter import de.mm20.launcher2.ktx.sendWithBackgroundPermission import de.mm20.launcher2.plugin.PluginState +import de.mm20.launcher2.search.calendar.CalendarListType import de.mm20.launcher2.themes.atTone import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.component.Banner import de.mm20.launcher2.ui.component.MissingPermissionBanner import de.mm20.launcher2.ui.component.preferences.CheckboxPreference +import de.mm20.launcher2.ui.component.preferences.PreferenceCategory import de.mm20.launcher2.ui.component.preferences.PreferenceScreen import de.mm20.launcher2.ui.component.preferences.PreferenceWithSwitch -import de.mm20.launcher2.ui.component.preferences.SwitchPreference import de.mm20.launcher2.ui.locals.LocalDarkTheme @Composable @@ -76,7 +76,7 @@ fun CalendarSearchSettingsScreen() { val selectedCalendars = remember(excludedCalendars, calendarLists) { calendarLists?.count { it.providerId == "local" } ?.minus(excludedCalendars.count { - it.startsWith("local") + it.startsWith("local:") }) } PreferenceWithSwitch( @@ -117,7 +117,7 @@ fun CalendarSearchSettingsScreen() { calendarLists?.count { it.providerId == plugin.plugin.authority } ?.minus(excludedCalendars.count { it.startsWith( - plugin.plugin.authority + "${plugin.plugin.authority}:" ) }) } @@ -126,7 +126,7 @@ fun CalendarSearchSettingsScreen() { enabled = state is PluginState.Ready, summary = (state as? PluginState.SetupRequired)?.message ?: if (selectedCalendars != null && calendarLists != null) "$selectedCalendars lists selected" - else (state as? PluginState.Ready)?.text ?: plugin.plugin.description, + else (state as? PluginState.Ready)?.text ?: plugin.plugin.description, switchValue = enabledProviders.contains(plugin.plugin.authority) && state is PluginState.Ready, onSwitchChanged = { viewModel.setProviderEnabled(plugin.plugin.authority, it) @@ -139,6 +139,8 @@ fun CalendarSearchSettingsScreen() { } } + Log.d("MM20", "${calendarLists.toString()}") + val dialogCalendarLists by remember { derivedStateOf { if (showDialogForProvider == null) null @@ -147,39 +149,41 @@ fun CalendarSearchSettingsScreen() { } if (showDialogForProvider != null && dialogCalendarLists != null) { - BasicAlertDialog( + ModalBottomSheet( onDismissRequest = { showDialogForProvider = null }, ) { - Surface( - modifier = Modifier - .wrapContentWidth() - .wrapContentHeight(), - shape = MaterialTheme.shapes.large, - tonalElevation = AlertDialogDefaults.TonalElevation - ) { - LazyColumn { - items(dialogCalendarLists ?: emptyList()) { - CheckboxPreference( - title = it.name, - summary = it.owner, - iconPadding = false, - value = it.id !in excludedCalendars, - onValueChanged = { value -> - viewModel.setCalendarExcluded(it.id, !value) - }, - checkboxColors = CheckboxDefaults.colors( - checkedColor = if (it.color == 0) MaterialTheme.colorScheme.primary - else Color( - it.color.atTone(if (LocalDarkTheme.current) 80 else 40) - ), - checkmarkColor = if (it.color == 0) MaterialTheme.colorScheme.onPrimary - else Color( - it.color.atTone(if (LocalDarkTheme.current) 20 else 100) + val groups = remember(dialogCalendarLists) { + dialogCalendarLists!!.groupBy { it.owner }.entries.sortedBy { it.key } + } + + LazyColumn { + items(groups) { + PreferenceCategory( + title = it.key, + iconPadding = false, + ) { + for (list in it.value) { + CheckboxPreference( + title = list.name, + iconPadding = false, + value = list.id !in excludedCalendars, + onValueChanged = { value -> + viewModel.setCalendarExcluded(list.id, !value) + }, + checkboxColors = CheckboxDefaults.colors( + checkedColor = if (list.color == 0) MaterialTheme.colorScheme.primary + else Color( + list.color.atTone(if (LocalDarkTheme.current) 80 else 40) + ), + checkmarkColor = if (list.color == 0) MaterialTheme.colorScheme.onPrimary + else Color( + list.color.atTone(if (LocalDarkTheme.current) 20 else 100) + ) ) ) - ) + } } } } diff --git a/core/i18n/src/main/res/values/strings.xml b/core/i18n/src/main/res/values/strings.xml index 59c2f4ae..474ccac2 100644 --- a/core/i18n/src/main/res/values/strings.xml +++ b/core/i18n/src/main/res/values/strings.xml @@ -627,6 +627,7 @@ Sign in to search Google Drive Calendars Hide all-day events + Hide completed tasks Build information More information about this build of this app Style diff --git a/data/calendar/src/main/java/de/mm20/launcher2/calendar/providers/AndroidCalendarProvider.kt b/data/calendar/src/main/java/de/mm20/launcher2/calendar/providers/AndroidCalendarProvider.kt index 26819a44..4342d4c1 100644 --- a/data/calendar/src/main/java/de/mm20/launcher2/calendar/providers/AndroidCalendarProvider.kt +++ b/data/calendar/src/main/java/de/mm20/launcher2/calendar/providers/AndroidCalendarProvider.kt @@ -6,6 +6,7 @@ import android.provider.CalendarContract import androidx.core.database.getStringOrNull import de.mm20.launcher2.permissions.PermissionGroup import de.mm20.launcher2.search.CalendarEvent +import de.mm20.launcher2.search.calendar.CalendarListType import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import java.util.Calendar @@ -201,6 +202,7 @@ class AndroidCalendarProvider( name = cursor.getStringOrNull(5) ?: cursor.getStringOrNull(1) ?: "", owner = cursor.getStringOrNull(2), color = cursor.getInt(3), + types = listOf(CalendarListType.Calendar), providerId = "local", ) ) diff --git a/data/widgets/src/main/java/de/mm20/launcher2/widgets/CalendarWidget.kt b/data/widgets/src/main/java/de/mm20/launcher2/widgets/CalendarWidget.kt index c94a35c9..31951ba0 100644 --- a/data/widgets/src/main/java/de/mm20/launcher2/widgets/CalendarWidget.kt +++ b/data/widgets/src/main/java/de/mm20/launcher2/widgets/CalendarWidget.kt @@ -15,6 +15,9 @@ data class CalendarWidgetConfig( val legacyExcludedCalendarIds: List? = null, @SerialName("excludedCalendars") val excludedCalendarIds: List? = null, + val completedTasks: Boolean = true, + val upcomingEventsCount: Int = 3, + val upcomingTaskCount: Int = 3, ) data class CalendarWidget( override val id: UUID,