Restructure calendar widget settings

This commit is contained in:
MM20 2024-08-16 23:26:26 +02:00
parent bf29ce6f08
commit cc9bff436a
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
8 changed files with 157 additions and 87 deletions

View File

@ -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

View File

@ -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)
)
)
}
)
)
}
}
}
}

View File

@ -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)

View File

@ -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 }
}
}

View File

@ -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)
)
)
)
)
}
}
}
}

View File

@ -627,6 +627,7 @@
<string name="preference_google_signin_summary">Sign in to search Google Drive</string>
<string name="preference_calendar_calendars">Calendars</string>
<string name="preference_calendar_hide_allday">Hide all-day events</string>
<string name="preference_calendar_hide_completed">Hide completed tasks</string>
<string name="preference_screen_buildinfo">Build information</string>
<string name="preference_screen_buildinfo_summary">More information about this build of this app</string>
<string name="preference_search_bar_style">Style</string>

View File

@ -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",
)
)

View File

@ -15,6 +15,9 @@ data class CalendarWidgetConfig(
val legacyExcludedCalendarIds: List<Long>? = null,
@SerialName("excludedCalendars")
val excludedCalendarIds: List<String>? = null,
val completedTasks: Boolean = true,
val upcomingEventsCount: Int = 3,
val upcomingTaskCount: Int = 3,
)
data class CalendarWidget(
override val id: UUID,