Add calendar plugins
This commit is contained in:
@@ -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(
|
||||
|
||||
+111
-35
@@ -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,
|
||||
|
||||
+8
-8
@@ -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()
|
||||
}
|
||||
|
||||
+91
@@ -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)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+34
@@ -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)
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -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)
|
||||
}
|
||||
|
||||
+3
-1
@@ -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)
|
||||
|
||||
|
||||
+2
-1
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user