Add calendar plugins

This commit is contained in:
MM20
2024-08-04 17:40:46 +02:00
parent c7987aabcd
commit 7479c7f401
36 changed files with 1260 additions and 351 deletions
@@ -51,14 +51,14 @@ abstract class FavoritesVM : ViewModel(), KoinComponent {
val frequentlyUsedRows = it.second.frequentlyUsedRows
val pinned = favoritesService.getFavorites(
excludeTypes = if (excludeCalendar) listOf("calendar", "tag") else listOf("tag"),
excludeTypes = if (excludeCalendar) listOf("calendar", "tag", "plugin.calendar") else listOf("tag"),
minPinnedLevel = PinnedLevel.AutomaticallySorted,
limit = 10 * columns,
)
if (includeFrequentlyUsed) {
emitAll(pinned.flatMapLatest { pinned ->
favoritesService.getFavorites(
excludeTypes = if (excludeCalendar) listOf("calendar", "tag") else listOf("tag"),
excludeTypes = if (excludeCalendar) listOf("calendar", "tag", "plugin.calendar") else listOf("tag"),
maxPinnedLevel = PinnedLevel.FrequentlyUsed,
minPinnedLevel = PinnedLevel.FrequentlyUsed,
limit = frequentlyUsedRows * columns - pinned.size % columns,
@@ -409,15 +409,15 @@ class SearchVM : ViewModel(), KoinComponent {
val missingCalendarPermission = combine(
permissionsManager.hasPermission(PermissionGroup.Calendar),
calendarSearchSettings.enabled,
) { perm, enabled -> !perm && enabled }
calendarSearchSettings.providers,
) { perm, providers -> !perm && providers.contains("local") }
fun requestCalendarPermission(context: AppCompatActivity) {
permissionsManager.requestPermission(context, PermissionGroup.Calendar)
}
fun disableCalendarSearch() {
calendarSearchSettings.setEnabled(false)
calendarSearchSettings.setProviderEnabled("local", false)
}
val missingContactsPermission = combine(
@@ -8,10 +8,10 @@ import androidx.compose.animation.SharedTransitionLayout
import androidx.compose.animation.core.MutableTransitionState
import androidx.compose.animation.core.tween
import androidx.compose.animation.expandIn
import androidx.compose.animation.expandVertically
import androidx.compose.animation.shrinkOut
import androidx.compose.foundation.background
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
@@ -19,21 +19,19 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.rounded.ArrowBack
import androidx.compose.material.icons.rounded.Edit
import androidx.compose.material.icons.rounded.CheckCircle
import androidx.compose.material.icons.rounded.Circle
import androidx.compose.material.icons.rounded.Notes
import androidx.compose.material.icons.rounded.OpenInNew
import androidx.compose.material.icons.rounded.People
import androidx.compose.material.icons.rounded.Place
import androidx.compose.material.icons.rounded.RadioButtonUnchecked
import androidx.compose.material.icons.rounded.Schedule
import androidx.compose.material.icons.rounded.Star
import androidx.compose.material.icons.rounded.StarOutline
import androidx.compose.material.icons.rounded.Tune
import androidx.compose.material.icons.rounded.Visibility
import androidx.compose.material.icons.rounded.VisibilityOff
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SnackbarDuration
import androidx.compose.material3.SnackbarResult
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@@ -49,8 +47,6 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.roundToIntRect
import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.lifecycle.lifecycleScope
import de.mm20.launcher2.search.CalendarEvent
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.DefaultToolbarAction
@@ -63,8 +59,6 @@ import de.mm20.launcher2.ui.launcher.sheets.LocalBottomSheetManager
import de.mm20.launcher2.ui.locals.LocalDarkTheme
import de.mm20.launcher2.ui.locals.LocalFavoritesEnabled
import de.mm20.launcher2.ui.locals.LocalGridSettings
import de.mm20.launcher2.ui.locals.LocalSnackbarHostState
import kotlinx.coroutines.launch
import palettes.TonalPalette
@Composable
@@ -99,33 +93,53 @@ fun CalendarItem(
Column {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(vertical = 16.dp)
) {
Box(
Icon(
when (calendar.isCompleted) {
true -> Icons.Rounded.CheckCircle
false -> Icons.Rounded.RadioButtonUnchecked
null -> Icons.Rounded.Circle
},
null,
modifier = Modifier
.padding(horizontal = 14.dp)
.padding(horizontal = 14.dp, vertical = 20.dp)
.size(24.dp)
.sharedBounds(
.sharedElement(
rememberSharedContentState("color"),
this@AnimatedContent
)
.background(eventColor, MaterialTheme.shapes.extraSmall)
),
tint = eventColor
)
Text(
Column(
modifier = Modifier
.padding(start = 4.dp, end = 16.dp)
.sharedBounds(
rememberSharedContentState("label"),
this@AnimatedContent
),
text = calendar.labelOverride ?: calendar.label,
style = MaterialTheme.typography.titleMedium
)
) {
Text(
modifier = Modifier
.sharedBounds(
rememberSharedContentState("label"),
this@AnimatedContent
),
text = calendar.labelOverride ?: calendar.label,
style = MaterialTheme.typography.titleMedium
)
if (calendar.calendarName != null) {
Text(
modifier = Modifier.animateEnterExit(
enter = expandVertically(),
exit = shrinkVertically()
),
text = calendar.calendarName!!,
style = MaterialTheme.typography.labelSmall,
color = eventColor,
)
}
}
}
Row(
Modifier
.fillMaxWidth().padding(bottom = 8.dp, end = 16.dp),
.fillMaxWidth()
.padding(bottom = 8.dp, end = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
@@ -146,7 +160,8 @@ fun CalendarItem(
if (!calendar.description.isNullOrBlank()) {
Row(
Modifier
.fillMaxWidth().padding(bottom = 8.dp, end = 16.dp),
.fillMaxWidth()
.padding(bottom = 8.dp, end = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
@@ -165,7 +180,8 @@ fun CalendarItem(
if (calendar.attendees.isNotEmpty()) {
Row(
Modifier
.fillMaxWidth().padding(bottom = 8.dp, end = 16.dp),
.fillMaxWidth()
.padding(bottom = 8.dp, end = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
@@ -261,15 +277,21 @@ fun CalendarItem(
modifier = modifier
.padding(16.dp)
) {
Box(
Icon(
when (calendar.isCompleted) {
true -> Icons.Rounded.CheckCircle
false -> Icons.Rounded.RadioButtonUnchecked
null -> Icons.Rounded.Circle
},
null,
modifier = Modifier
.padding(end = 16.dp)
.size(20.dp)
.sharedBounds(
.sharedElement(
rememberSharedContentState("color"),
this@AnimatedContent
)
.background(eventColor, MaterialTheme.shapes.extraSmall)
),
tint = eventColor
)
Column {
Text(
@@ -281,13 +303,16 @@ fun CalendarItem(
style = MaterialTheme.typography.titleSmall
)
Text(
modifier = Modifier.padding(top = 2.dp)
modifier = Modifier
.padding(top = 2.dp)
.sharedBounds(
rememberSharedContentState("date"),
this@AnimatedContent
),
text = calendar.getSummary(context),
style = MaterialTheme.typography.bodySmall
style = MaterialTheme.typography.bodySmall,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
@@ -327,6 +352,24 @@ fun CalendarItemGridPopup(
}
private fun CalendarEvent.formatTime(context: Context): String {
val startTime = startTime
if (startTime == null) {
if (allDay) {
return DateUtils.formatDateRange(
context,
endTime,
endTime,
DateUtils.FORMAT_SHOW_DATE
)
}
return DateUtils.formatDateRange(
context,
endTime,
endTime,
DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_SHOW_TIME
)
}
if (allDay) return DateUtils.formatDateRange(
context,
startTime,
@@ -343,6 +386,39 @@ private fun CalendarEvent.formatTime(context: Context): String {
}
private fun CalendarEvent.getSummary(context: Context): String {
val startTime = startTime
if (startTime == null) {
val isToday = DateUtils.isToday(endTime)
if (isToday) {
if (allDay) {
return "Due today"
}
return "Due ${
DateUtils.formatDateTime(
context,
endTime,
DateUtils.FORMAT_SHOW_TIME
)
}"
}
if (allDay) {
return "Due ${
DateUtils.formatDateTime(
context,
endTime,
DateUtils.FORMAT_SHOW_DATE
)
}"
}
return "Due ${
DateUtils.formatDateTime(
context,
endTime,
DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_SHOW_TIME
)
}"
}
val isToday =
DateUtils.isToday(startTime) && DateUtils.isToday(endTime)
return if (isToday) {
@@ -34,7 +34,6 @@ 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.Divider
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
@@ -64,7 +63,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.isUnspecified
import androidx.core.net.toUri
import de.mm20.launcher2.calendar.CalendarRepository
import de.mm20.launcher2.calendar.UserCalendar
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
@@ -75,7 +74,6 @@ import de.mm20.launcher2.ui.component.BottomSheetDialog
import de.mm20.launcher2.ui.component.DragResizeHandle
import de.mm20.launcher2.ui.component.LargeMessage
import de.mm20.launcher2.ui.component.MissingPermissionBanner
import de.mm20.launcher2.ui.component.ResizeAxis
import de.mm20.launcher2.ui.component.preferences.CheckboxPreference
import de.mm20.launcher2.ui.component.preferences.Preference
import de.mm20.launcher2.ui.component.preferences.SwitchPreference
@@ -91,6 +89,7 @@ import de.mm20.launcher2.widgets.MusicWidget
import de.mm20.launcher2.widgets.NotesWidget
import de.mm20.launcher2.widgets.WeatherWidget
import de.mm20.launcher2.widgets.Widget
import kotlinx.coroutines.flow.map
import org.koin.androidx.compose.get
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
@@ -504,18 +503,16 @@ fun ColumnScope.ConfigureCalendarWidget(
) {
val calendarRepository: CalendarRepository = get()
val permissionsManager: PermissionsManager = get()
var calendars by remember { mutableStateOf(emptyList<UserCalendar>()) }
var ready by remember { mutableStateOf(false) }
val calendars by remember {
calendarRepository.getCalendars().map {
it.sortedBy { it.name }
}
}.collectAsState(null)
val hasPermission by remember {
permissionsManager.hasPermission(PermissionGroup.Calendar)
}.collectAsState(true)
LaunchedEffect(hasPermission) {
calendars = calendarRepository.getCalendars().sortedBy { it.name }
ready = true
}
OutlinedCard {
Column(
modifier = Modifier.fillMaxWidth()
@@ -537,26 +534,29 @@ fun ColumnScope.ConfigureCalendarWidget(
text = stringResource(R.string.preference_calendar_calendars)
)
val context = LocalLifecycleOwner.current as AppCompatActivity
if (calendars.isNotEmpty()) {
val excludedCalendars = remember(widget.config) {
widget.config.excludedCalendarIds ?: widget.config.legacyExcludedCalendarIds?.map { "local:$it" } ?: emptyList()
}
if (calendars?.isNotEmpty() == true) {
OutlinedCard {
Column(
modifier = Modifier.fillMaxWidth()
) {
for ((i, calendar) in calendars.withIndex()) {
if (i > 0) Divider()
for ((i, calendar) in calendars!!.withIndex()) {
if (i > 0) HorizontalDivider()
CheckboxPreference(
title = calendar.name,
summary = calendar.owner,
iconPadding = false,
value = !widget.config.excludedCalendarIds.contains(calendar.id),
value = excludedCalendars.contains(calendar.id) != true,
onValueChanged = {
onWidgetUpdated(
widget.copy(
config = widget.config.copy(
excludedCalendarIds = if (it) {
widget.config.excludedCalendarIds - calendar.id
excludedCalendars - calendar.id
} else {
widget.config.excludedCalendarIds + calendar.id
excludedCalendars + calendar.id
}
)
)
@@ -572,7 +572,7 @@ fun ColumnScope.ConfigureCalendarWidget(
text = stringResource(R.string.missing_permission_calendar_widget_settings),
onClick = { permissionsManager.requestPermission(context, PermissionGroup.Calendar) },
)
} else if (ready) {
} else if (calendars != null) {
Text(
modifier = Modifier.padding(top = 8.dp, bottom = 16.dp),
style = MaterialTheme.typography.bodySmall,
@@ -43,7 +43,7 @@ class CalendarWidgetVM : ViewModel(), KoinComponent {
val calendarEvents = mutableStateOf<List<CalendarEvent>>(emptyList())
val pinnedCalendarEvents =
favoritesService.getFavorites(
includeTypes = listOf("calendar"),
includeTypes = listOf("calendar", "plugin.calendar"),
minPinnedLevel = PinnedLevel.AutomaticallySorted,
).stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
val nextEvents = mutableStateOf<List<CalendarEvent>>(emptyList())
@@ -67,10 +67,10 @@ class CalendarWidgetVM : ViewModel(), KoinComponent {
field = value
val dates = value.flatMap {
val startDate =
Instant.ofEpochMilli(it.startTime).atZone(ZoneId.systemDefault()).toLocalDate()
it.startTime?.let { Instant.ofEpochMilli(it).atZone(ZoneId.systemDefault()).toLocalDate() }
val endDate =
Instant.ofEpochMilli(it.endTime).atZone(ZoneId.systemDefault()).toLocalDate()
return@flatMap listOf(
return@flatMap listOfNotNull(
startDate,
endDate
)
@@ -136,14 +136,14 @@ class CalendarWidgetVM : ViewModel(), KoinComponent {
val dayStart = max(now, date.atStartOfDay().toEpochSecond(offset) * 1000)
val dayEnd = date.plusDays(1).atStartOfDay().toEpochSecond(offset) * 1000
var events = upcomingEvents.filter {
it.endTime >= dayStart && it.startTime < dayEnd
it.endTime >= dayStart && (it.startTime ?: it.endTime) < dayEnd
}
if (!showRunningPastDayEvents) {
val totalCount = events.size
events = events.filter {
it.startTime >= date.atStartOfDay().toEpochSecond(offset) * 1000 ||
(it.startTime != null && it.startTime!! >= date.atStartOfDay().toEpochSecond(offset) * 1000) ||
it.endTime < date.atStartOfDay().plusDays(1).toEpochSecond(offset) * 1000
}
@@ -169,14 +169,14 @@ class CalendarWidgetVM : ViewModel(), KoinComponent {
from = System.currentTimeMillis(),
to = System.currentTimeMillis() + 14 * 24 * 60 * 60 * 1000L,
excludeAllDayEvents = !config.allDayEvents,
excludeCalendars = config.excludedCalendarIds,
excludeCalendars = config.excludedCalendarIds ?: config.legacyExcludedCalendarIds?.map { "local:$it" } ?: emptyList(),
).collectLatest { events ->
searchableRepository.getKeys(
includeTypes = listOf("calendar"),
includeTypes = listOf("calendar", "plugin.calendar"),
maxVisibility = VisibilityLevel.SearchOnly,
limit = 9999,
).collectLatest { hidden ->
upcomingEvents = events.filter { !hidden.contains(it.key) }
upcomingEvents = events.filter { !hidden.contains(it.key) }.sortedBy { it.startTime ?: it.endTime }
}
}
@@ -36,6 +36,7 @@ import de.mm20.launcher2.ui.settings.about.AboutSettingsScreen
import de.mm20.launcher2.ui.settings.appearance.AppearanceSettingsScreen
import de.mm20.launcher2.ui.settings.backup.BackupSettingsScreen
import de.mm20.launcher2.ui.settings.buildinfo.BuildInfoSettingsScreen
import de.mm20.launcher2.ui.settings.calendarsearch.CalendarSearchSettingsScreen
import de.mm20.launcher2.ui.settings.cards.CardsSettingsScreen
import de.mm20.launcher2.ui.settings.colorscheme.ThemeSettingsScreen
import de.mm20.launcher2.ui.settings.colorscheme.ThemesSettingsScreen
@@ -180,6 +181,9 @@ class SettingsActivity : BaseActivity() {
composable("settings/search/files") {
FileSearchSettingsScreen()
}
composable("settings/search/calendar") {
CalendarSearchSettingsScreen()
}
composable("settings/search/searchactions") {
SearchActionsSettingsScreen()
}
@@ -0,0 +1,91 @@
package de.mm20.launcher2.ui.settings.calendarsearch
import android.app.PendingIntent
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.ErrorOutline
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
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.ui.R
import de.mm20.launcher2.ui.component.Banner
import de.mm20.launcher2.ui.component.MissingPermissionBanner
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
import de.mm20.launcher2.ui.component.preferences.SwitchPreference
@Composable
fun CalendarSearchSettingsScreen() {
val viewModel: CalendarSearchSettingsScreenVM = viewModel()
val context = LocalContext.current
val hasCalendarPermission by viewModel.hasCalendarPermission.collectAsState(null)
val plugins by viewModel.availablePlugins.collectAsState(emptyList())
val enabledProviders by viewModel.enabledProviders.collectAsState(emptySet())
PreferenceScreen(title = stringResource(R.string.preference_search_calendar)) {
item {
AnimatedVisibility(hasCalendarPermission == false) {
MissingPermissionBanner(
text = stringResource(R.string.missing_permission_calendar_search_settings),
onClick = {
viewModel.requestCalendarPermission(context as AppCompatActivity)
},
modifier = Modifier.padding(16.dp)
)
}
SwitchPreference(
title = stringResource(R.string.preference_search_calendar),
summary = stringResource(R.string.preference_search_calendar_summary),
value = enabledProviders.contains("local") && hasCalendarPermission == true,
onValueChanged = {
viewModel.setProviderEnabled("local", it)
},
enabled = hasCalendarPermission == true
)
for (plugin in plugins) {
val state = plugin.state
if (state is PluginState.SetupRequired) {
Banner(
modifier = Modifier.padding(16.dp),
text = state.message ?: stringResource(id = R.string.plugin_state_setup_required),
icon = Icons.Rounded.ErrorOutline,
primaryAction = {
TextButton(onClick = {
try {
state.setupActivity.sendWithBackgroundPermission(context)
} catch (e: PendingIntent.CanceledException) {
CrashReporter.logException(e)
}
}) {
Text(stringResource(id = R.string.plugin_action_setup))
}
}
)
}
SwitchPreference(
title = plugin.plugin.label,
enabled = state is PluginState.Ready,
summary = (state as? PluginState.Ready)?.text
?: (state as? PluginState.SetupRequired)?.message
?: plugin.plugin.description,
value = enabledProviders.contains(plugin.plugin.authority) && state is PluginState.Ready,
onValueChanged = {
viewModel.setProviderEnabled(plugin.plugin.authority, it)
},
)
}
}
}
}
@@ -0,0 +1,34 @@
package de.mm20.launcher2.ui.settings.calendarsearch
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModel
import de.mm20.launcher2.permissions.PermissionGroup
import de.mm20.launcher2.permissions.PermissionsManager
import de.mm20.launcher2.plugin.PluginType
import de.mm20.launcher2.plugins.PluginService
import de.mm20.launcher2.preferences.search.CalendarSearchSettings
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
class CalendarSearchSettingsScreenVM : ViewModel(), KoinComponent {
private val settings: CalendarSearchSettings by inject()
private val pluginService: PluginService by inject()
private val permissionsManager: PermissionsManager by inject()
val hasCalendarPermission = permissionsManager.hasPermission(PermissionGroup.Calendar)
val availablePlugins = pluginService.getPluginsWithState(
type = PluginType.Calendar,
enabled = true,
)
val enabledProviders = settings.enabledProviders
fun setProviderEnabled(providerId: String, enabled: Boolean) {
settings.setProviderEnabled(providerId, enabled)
}
fun requestCalendarPermission(activity: AppCompatActivity) {
permissionsManager.requestPermission(activity, PermissionGroup.Calendar)
}
}
@@ -27,6 +27,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.accounts.AccountType
import de.mm20.launcher2.crashreporter.CrashReporter
import de.mm20.launcher2.ktx.isAtLeastApiLevel
import de.mm20.launcher2.ktx.sendWithBackgroundPermission
import de.mm20.launcher2.plugin.PluginState
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.Banner
@@ -196,7 +197,7 @@ fun FileSearchSettingsScreen() {
primaryAction = {
TextButton(onClick = {
try {
state.setupActivity.send()
state.setupActivity.sendWithBackgroundPermission(context)
} catch (e: PendingIntent.CanceledException) {
CrashReporter.logException(e)
}
@@ -22,6 +22,7 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.collectAsStateWithLifecycle
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.preferences.search.LocationSearchSettings
import de.mm20.launcher2.ui.R
@@ -41,6 +42,7 @@ fun LocationsSettingsScreen() {
val viewModel: LocationsSettingsScreenVM = viewModel()
val navController = LocalNavController.current
val context = LocalContext.current
val osmLocations by viewModel.osmLocations.collectAsState()
val imperialUnits by viewModel.imperialUnits.collectAsState()
@@ -85,7 +87,7 @@ fun LocationsSettingsScreen() {
primaryAction = {
TextButton(onClick = {
try {
state.setupActivity.send()
state.setupActivity.sendWithBackgroundPermission(context)
} catch (e: PendingIntent.CanceledException) {
CrashReporter.logException(e)
}
@@ -27,6 +27,7 @@ import androidx.compose.material.icons.rounded.Info
import androidx.compose.material.icons.rounded.LightMode
import androidx.compose.material.icons.rounded.Place
import androidx.compose.material.icons.rounded.Settings
import androidx.compose.material.icons.rounded.Today
import androidx.compose.material.icons.rounded.Verified
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
@@ -243,6 +244,7 @@ fun PluginSettingsScreen(pluginId: String) {
PluginType.FileSearch -> Icons.AutoMirrored.Rounded.InsertDriveFile
PluginType.Weather -> Icons.Rounded.LightMode
PluginType.LocationSearch -> Icons.Rounded.Place
PluginType.Calendar -> Icons.Rounded.Today
},
null,
modifier = Modifier.size(16.dp),
@@ -253,6 +255,7 @@ fun PluginSettingsScreen(pluginId: String) {
PluginType.FileSearch -> stringResource(R.string.plugin_type_filesearch)
PluginType.Weather -> stringResource(R.string.plugin_type_weather)
PluginType.LocationSearch -> stringResource(R.string.plugin_type_locationsearch)
PluginType.Calendar -> stringResource(R.string.plugin_type_calendar)
},
modifier = Modifier.padding(horizontal = 4.dp),
style = MaterialTheme.typography.labelMedium,
@@ -116,28 +116,13 @@ fun SearchSettingsScreen() {
enabled = hasContactsPermission == true
)
val hasCalendarPermission by viewModel.hasCalendarPermission.collectAsStateWithLifecycle(
null
)
AnimatedVisibility(hasCalendarPermission == false) {
MissingPermissionBanner(
text = stringResource(R.string.missing_permission_calendar_search_settings),
onClick = {
viewModel.requestCalendarPermission(context as AppCompatActivity)
},
modifier = Modifier.padding(16.dp)
)
}
val calendar by viewModel.calendar.collectAsStateWithLifecycle(null)
SwitchPreference(
Preference(
title = stringResource(R.string.preference_search_calendar),
summary = stringResource(R.string.preference_search_calendar_summary),
icon = Icons.Rounded.Today,
value = calendar == true && hasCalendarPermission == true,
onValueChanged = {
viewModel.setCalendar(it)
onClick = {
navController?.navigate("settings/search/calendar")
},
enabled = hasCalendarPermission == true
)
val hasAppShortcutsPermission by viewModel.hasAppShortcutPermission.collectAsStateWithLifecycle(
@@ -61,19 +61,6 @@ class SearchSettingsScreenVM : ViewModel(), KoinComponent {
permissionsManager.requestPermission(activity, PermissionGroup.Contacts)
}
val hasCalendarPermission = permissionsManager.hasPermission(PermissionGroup.Calendar)
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
val calendar = calendarSearchSettings.enabled
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
fun setCalendar(calendar: Boolean) {
calendarSearchSettings.setEnabled(calendar)
}
fun requestCalendarPermission(activity: AppCompatActivity) {
permissionsManager.requestPermission(activity, PermissionGroup.Calendar)
}
val calculator = calculatorSearchSettings.enabled
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
@@ -17,6 +17,7 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.collectAsStateWithLifecycle
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.ui.BuildConfig
import de.mm20.launcher2.ui.R
@@ -58,7 +59,7 @@ fun WeatherIntegrationSettingsScreen() {
primaryAction = {
TextButton(onClick = {
try {
state.setupActivity.send()
state.setupActivity.sendWithBackgroundPermission(context)
} catch (e: PendingIntent.CanceledException) {
CrashReporter.logException(e)
}