Move widget specific settings to widgets
This commit is contained in:
parent
fb87ab20f9
commit
5b2ad94065
@ -26,10 +26,13 @@ import androidx.compose.material.FractionalThreshold
|
||||
import androidx.compose.material.SwipeableState
|
||||
import androidx.compose.material.swipeable
|
||||
import androidx.compose.material3.CenterAlignedTopAppBar
|
||||
import androidx.compose.material3.LocalAbsoluteTonalElevation
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocal
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@ -133,143 +136,147 @@ fun BottomSheetDialog(
|
||||
}
|
||||
}
|
||||
|
||||
Dialog(
|
||||
properties = DialogProperties(
|
||||
dismissOnBackPress = dismissOnBackPress(),
|
||||
dismissOnClickOutside = swipeToDismiss(),
|
||||
usePlatformDefaultWidth = false,
|
||||
),
|
||||
onDismissRequest = onDismissRequest,
|
||||
CompositionLocalProvider(
|
||||
LocalAbsoluteTonalElevation provides 0.dp,
|
||||
) {
|
||||
BoxWithConstraints(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
propagateMinConstraints = true,
|
||||
contentAlignment = Alignment.BottomCenter
|
||||
Dialog(
|
||||
properties = DialogProperties(
|
||||
dismissOnBackPress = dismissOnBackPress(),
|
||||
dismissOnClickOutside = swipeToDismiss(),
|
||||
usePlatformDefaultWidth = false,
|
||||
),
|
||||
onDismissRequest = onDismissRequest,
|
||||
) {
|
||||
val maxHeightPx = maxHeight.toPixels()
|
||||
val scrimAlpha by animateFloatAsState(
|
||||
if (swipeState.targetValue == SwipeState.Dismiss) 0f else 0.32f,
|
||||
label = "Scrim alpha"
|
||||
)
|
||||
BoxWithConstraints(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
propagateMinConstraints = true,
|
||||
contentAlignment = Alignment.BottomCenter
|
||||
) {
|
||||
val maxHeightPx = maxHeight.toPixels()
|
||||
val scrimAlpha by animateFloatAsState(
|
||||
if (swipeState.targetValue == SwipeState.Dismiss) 0f else 0.32f,
|
||||
label = "Scrim alpha"
|
||||
)
|
||||
|
||||
Box(modifier = Modifier
|
||||
.background(MaterialTheme.colorScheme.scrim.copy(alpha = scrimAlpha))
|
||||
.fillMaxSize()
|
||||
.pointerInput(onDismissRequest, swipeToDismiss) {
|
||||
detectTapGestures {
|
||||
if (swipeToDismiss()) {
|
||||
scope.launch {
|
||||
swipeState.animateTo(SwipeState.Dismiss)
|
||||
onDismissRequest()
|
||||
Box(modifier = Modifier
|
||||
.background(MaterialTheme.colorScheme.scrim.copy(alpha = scrimAlpha))
|
||||
.fillMaxSize()
|
||||
.pointerInput(onDismissRequest, swipeToDismiss) {
|
||||
detectTapGestures {
|
||||
if (swipeToDismiss()) {
|
||||
scope.launch {
|
||||
swipeState.animateTo(SwipeState.Dismiss)
|
||||
onDismissRequest()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight(Alignment.Bottom)
|
||||
.clipToBounds(),
|
||||
verticalArrangement = Arrangement.Bottom
|
||||
) {
|
||||
var height by remember {
|
||||
mutableStateOf(maxHeightPx)
|
||||
}
|
||||
|
||||
LaunchedEffect(null) {
|
||||
swipeState.animateTo(SwipeState.Peek)
|
||||
}
|
||||
|
||||
val heightDp = height.toDp()
|
||||
val peekHeight = (height - maxHeightPx / 2).coerceAtLeast(0f)
|
||||
val anchors = mutableMapOf(
|
||||
peekHeight to SwipeState.Peek,
|
||||
height to SwipeState.Dismiss,
|
||||
).also {
|
||||
if (peekHeight > 0f) {
|
||||
it[0f] = SwipeState.Full
|
||||
}
|
||||
}
|
||||
Surface(
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.nestedScroll(nestedScrollConnection)
|
||||
.animateContentSize()
|
||||
.onSizeChanged {
|
||||
height = it.height.toFloat()
|
||||
}
|
||||
.offset { IntOffset(0, swipeState.offset.value.roundToInt()) }
|
||||
.swipeable(
|
||||
swipeState,
|
||||
anchors = anchors,
|
||||
orientation = Orientation.Vertical,
|
||||
thresholds = { _, to ->
|
||||
if (to == SwipeState.Dismiss) {
|
||||
FixedThreshold(heightDp - 48.dp)
|
||||
} else {
|
||||
FractionalThreshold(0.5f)
|
||||
}
|
||||
},
|
||||
resistance = null
|
||||
)
|
||||
.fillMaxWidth()
|
||||
.weight(1f, false),
|
||||
shape = MaterialTheme.shapes.extraLarge.copy(
|
||||
bottomStart = CornerSize(0),
|
||||
bottomEnd = CornerSize(0),
|
||||
),
|
||||
shadowElevation = 16.dp,
|
||||
.wrapContentHeight(Alignment.Bottom)
|
||||
.clipToBounds(),
|
||||
verticalArrangement = Arrangement.Bottom
|
||||
) {
|
||||
Column {
|
||||
CenterAlignedTopAppBar(
|
||||
title = title,
|
||||
actions = actions,
|
||||
colors = TopAppBarDefaults.centerAlignedTopAppBarColors(
|
||||
containerColor = Color.Transparent,
|
||||
),
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight(),
|
||||
propagateMinConstraints = true,
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
content(PaddingValues(horizontal = 24.dp, vertical = 8.dp))
|
||||
}
|
||||
|
||||
var height by remember {
|
||||
mutableStateOf(maxHeightPx)
|
||||
}
|
||||
}
|
||||
|
||||
if (confirmButton != null || dismissButton != null) {
|
||||
val elevation by animateDpAsState(if (swipeState.offset.value == 0f) 0.dp else 1.dp)
|
||||
val alpha by animateFloatAsState(if (swipeState.targetValue == SwipeState.Dismiss) 0f else 1f)
|
||||
LaunchedEffect(null) {
|
||||
swipeState.animateTo(SwipeState.Peek)
|
||||
}
|
||||
|
||||
val heightDp = height.toDp()
|
||||
val peekHeight = (height - maxHeightPx / 2).coerceAtLeast(0f)
|
||||
val anchors = mutableMapOf(
|
||||
peekHeight to SwipeState.Peek,
|
||||
height to SwipeState.Dismiss,
|
||||
).also {
|
||||
if (peekHeight > 0f) {
|
||||
it[0f] = SwipeState.Full
|
||||
}
|
||||
}
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.wrapContentHeight()
|
||||
.fillMaxWidth(),
|
||||
tonalElevation = elevation,
|
||||
.nestedScroll(nestedScrollConnection)
|
||||
.animateContentSize()
|
||||
.onSizeChanged {
|
||||
height = it.height.toFloat()
|
||||
}
|
||||
.offset { IntOffset(0, swipeState.offset.value.roundToInt()) }
|
||||
.swipeable(
|
||||
swipeState,
|
||||
anchors = anchors,
|
||||
orientation = Orientation.Vertical,
|
||||
thresholds = { _, to ->
|
||||
if (to == SwipeState.Dismiss) {
|
||||
FixedThreshold(heightDp - 48.dp)
|
||||
} else {
|
||||
FractionalThreshold(0.5f)
|
||||
}
|
||||
},
|
||||
resistance = null
|
||||
)
|
||||
.fillMaxWidth()
|
||||
.weight(1f, false),
|
||||
shape = MaterialTheme.shapes.extraLarge.copy(
|
||||
bottomStart = CornerSize(0),
|
||||
bottomEnd = CornerSize(0),
|
||||
),
|
||||
shadowElevation = 16.dp,
|
||||
) {
|
||||
Row(
|
||||
Column {
|
||||
CenterAlignedTopAppBar(
|
||||
title = title,
|
||||
actions = actions,
|
||||
colors = TopAppBarDefaults.centerAlignedTopAppBarColors(
|
||||
containerColor = Color.Transparent,
|
||||
),
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight(),
|
||||
propagateMinConstraints = true,
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
content(PaddingValues(horizontal = 24.dp, vertical = 8.dp))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (confirmButton != null || dismissButton != null) {
|
||||
val elevation by animateDpAsState(if (swipeState.offset.value == 0f) 0.dp else 1.dp)
|
||||
val alpha by animateFloatAsState(if (swipeState.targetValue == SwipeState.Dismiss) 0f else 1f)
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(12.dp),
|
||||
horizontalArrangement = Arrangement.End
|
||||
.wrapContentHeight()
|
||||
.fillMaxWidth(),
|
||||
tonalElevation = elevation,
|
||||
) {
|
||||
if (dismissButton != null) {
|
||||
dismissButton()
|
||||
}
|
||||
if (confirmButton != null && dismissButton != null) {
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
}
|
||||
if (confirmButton != null) {
|
||||
confirmButton()
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(12.dp),
|
||||
horizontalArrangement = Arrangement.End
|
||||
) {
|
||||
if (dismissButton != null) {
|
||||
dismissButton()
|
||||
}
|
||||
if (confirmButton != null && dismissButton != null) {
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
}
|
||||
if (confirmButton != null) {
|
||||
confirmButton()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,32 @@
|
||||
package de.mm20.launcher2.ui.component.preferences
|
||||
|
||||
import androidx.compose.material3.Checkbox
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
|
||||
@Composable
|
||||
fun CheckboxPreference(
|
||||
title: String,
|
||||
icon: ImageVector? = null,
|
||||
iconPadding: Boolean = true,
|
||||
summary: String? = null,
|
||||
value: Boolean,
|
||||
onValueChanged: (Boolean) -> Unit,
|
||||
enabled: Boolean = true
|
||||
) {
|
||||
Preference(
|
||||
title = title,
|
||||
icon = icon,
|
||||
iconPadding = iconPadding,
|
||||
summary = summary,
|
||||
enabled = enabled,
|
||||
onClick = {
|
||||
onValueChanged(!value)
|
||||
},
|
||||
controls = {
|
||||
Checkbox(
|
||||
enabled = enabled, checked = value, onCheckedChange = onValueChanged,
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
@ -8,6 +8,7 @@ import androidx.compose.ui.graphics.vector.ImageVector
|
||||
fun SwitchPreference(
|
||||
title: String,
|
||||
icon: ImageVector? = null,
|
||||
iconPadding: Boolean = true,
|
||||
summary: String? = null,
|
||||
value: Boolean,
|
||||
onValueChanged: (Boolean) -> Unit,
|
||||
@ -16,6 +17,7 @@ fun SwitchPreference(
|
||||
Preference(
|
||||
title = title,
|
||||
icon = icon,
|
||||
iconPadding = iconPadding,
|
||||
summary = summary,
|
||||
enabled = enabled,
|
||||
onClick = {
|
||||
|
||||
@ -0,0 +1,536 @@
|
||||
package de.mm20.launcher2.ui.launcher.sheets
|
||||
|
||||
import android.app.Activity
|
||||
import android.appwidget.AppWidgetHost
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.appwidget.AppWidgetProviderInfo
|
||||
import android.content.Intent
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.gestures.Orientation
|
||||
import androidx.compose.foundation.gestures.draggable
|
||||
import androidx.compose.foundation.gestures.rememberDraggableState
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.Build
|
||||
import androidx.compose.material.icons.rounded.Error
|
||||
import androidx.compose.material.icons.rounded.OpenInNew
|
||||
import androidx.compose.material.icons.rounded.UnfoldMore
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.Divider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.OutlinedCard
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
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
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
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.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.unit.dp
|
||||
import de.mm20.launcher2.calendar.CalendarRepository
|
||||
import de.mm20.launcher2.ktx.isAtLeastApiLevel
|
||||
import de.mm20.launcher2.permissions.PermissionGroup
|
||||
import de.mm20.launcher2.permissions.PermissionsManager
|
||||
import de.mm20.launcher2.search.data.UserCalendar
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.component.BottomSheetDialog
|
||||
import de.mm20.launcher2.ui.component.LargeMessage
|
||||
import de.mm20.launcher2.ui.component.MissingPermissionBanner
|
||||
import de.mm20.launcher2.ui.component.preferences.CheckboxPreference
|
||||
import de.mm20.launcher2.ui.component.preferences.SwitchPreference
|
||||
import de.mm20.launcher2.ui.launcher.widgets.external.ExternalWidget
|
||||
import de.mm20.launcher2.ui.settings.SettingsActivity
|
||||
import de.mm20.launcher2.widgets.AppWidget
|
||||
import de.mm20.launcher2.widgets.CalendarWidget
|
||||
import de.mm20.launcher2.widgets.FavoritesWidget
|
||||
import de.mm20.launcher2.widgets.MusicWidget
|
||||
import de.mm20.launcher2.widgets.WeatherWidget
|
||||
import de.mm20.launcher2.widgets.Widget
|
||||
import org.koin.androidx.compose.get
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@Composable
|
||||
fun ConfigureWidgetSheet(
|
||||
appWidgetHost: AppWidgetHost,
|
||||
widget: Widget,
|
||||
onWidgetUpdated: (Widget) -> Unit,
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
BottomSheetDialog(onDismissRequest = onDismiss,
|
||||
title = {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(32.dp)
|
||||
.height(4.dp)
|
||||
.clip(MaterialTheme.shapes.small)
|
||||
.background(MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.4f))
|
||||
)
|
||||
}
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = if (widget is AppWidget) 8.dp else 16.dp)
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(bottom = 8.dp)
|
||||
) {
|
||||
when (widget) {
|
||||
is WeatherWidget -> ConfigureWeatherWidget(widget, onWidgetUpdated)
|
||||
is AppWidget -> ConfigureAppWidget(appWidgetHost, widget, onWidgetUpdated)
|
||||
is CalendarWidget -> ConfigureCalendarWidget(widget, onWidgetUpdated)
|
||||
is FavoritesWidget -> ConfigureFavoritesWidget(widget, onWidgetUpdated)
|
||||
is MusicWidget -> ConfigureMusicWidget()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ColumnScope.ConfigureWeatherWidget(
|
||||
widget: WeatherWidget,
|
||||
onWidgetUpdated: (WeatherWidget) -> Unit,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
OutlinedCard {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.widget_config_weather_compact),
|
||||
iconPadding = false,
|
||||
value = !widget.config.showForecast,
|
||||
onValueChanged = {
|
||||
onWidgetUpdated(widget.copy(config = widget.config.copy(showForecast = !it)))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
TextButton(
|
||||
modifier = Modifier
|
||||
.padding(top = 8.dp)
|
||||
.align(Alignment.End),
|
||||
contentPadding = PaddingValues(
|
||||
end = 16.dp,
|
||||
top = 8.dp,
|
||||
start = 24.dp,
|
||||
bottom = 8.dp,
|
||||
),
|
||||
onClick = {
|
||||
context.startActivity(Intent(
|
||||
context,
|
||||
SettingsActivity::class.java
|
||||
).apply {
|
||||
putExtra(
|
||||
SettingsActivity.EXTRA_ROUTE,
|
||||
SettingsActivity.ROUTE_WEATHER_INTEGRATION
|
||||
)
|
||||
})
|
||||
}) {
|
||||
Text(stringResource(R.string.widget_config_weather_integration_settings))
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.padding(start = ButtonDefaults.IconSpacing)
|
||||
.size(ButtonDefaults.IconSize),
|
||||
imageVector = Icons.Rounded.OpenInNew, contentDescription = null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ColumnScope.ConfigureFavoritesWidget(
|
||||
widget: FavoritesWidget,
|
||||
onWidgetUpdated: (FavoritesWidget) -> Unit,
|
||||
) {
|
||||
val bottomSheetManager = LocalBottomSheetManager.current
|
||||
OutlinedCard {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.preference_edit_button),
|
||||
iconPadding = false,
|
||||
value = widget.config.editButton,
|
||||
onValueChanged = {
|
||||
onWidgetUpdated(widget.copy(config = widget.config.copy(editButton = it)))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
TextButton(
|
||||
modifier = Modifier
|
||||
.padding(top = 8.dp)
|
||||
.align(Alignment.End),
|
||||
contentPadding = PaddingValues(
|
||||
end = 16.dp,
|
||||
top = 8.dp,
|
||||
start = 24.dp,
|
||||
bottom = 8.dp,
|
||||
),
|
||||
onClick = {
|
||||
bottomSheetManager.showEditFavoritesSheet()
|
||||
}) {
|
||||
Text(stringResource(R.string.menu_item_edit_favs))
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.padding(start = ButtonDefaults.IconSpacing)
|
||||
.size(ButtonDefaults.IconSize),
|
||||
imageVector = Icons.Rounded.OpenInNew, contentDescription = null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ColumnScope.ConfigureMusicWidget(
|
||||
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
TextButton(
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterHorizontally),
|
||||
contentPadding = PaddingValues(
|
||||
end = 16.dp,
|
||||
top = 8.dp,
|
||||
start = 24.dp,
|
||||
bottom = 8.dp,
|
||||
),
|
||||
onClick = {
|
||||
context.startActivity(Intent(
|
||||
context,
|
||||
SettingsActivity::class.java
|
||||
).apply {
|
||||
putExtra(
|
||||
SettingsActivity.EXTRA_ROUTE,
|
||||
SettingsActivity.ROUTE_MEDIA_INTEGRATION,
|
||||
)
|
||||
})
|
||||
}) {
|
||||
Text(stringResource(R.string.widget_config_music_integration_settings))
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.padding(start = ButtonDefaults.IconSpacing)
|
||||
.size(ButtonDefaults.IconSize),
|
||||
imageVector = Icons.Rounded.OpenInNew, contentDescription = null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ColumnScope.ConfigureAppWidget(
|
||||
appWidgetHost: AppWidgetHost,
|
||||
widget: AppWidget,
|
||||
onWidgetUpdated: (Widget) -> Unit,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val widgetInfo = remember(widget.config.widgetId) {
|
||||
AppWidgetManager.getInstance(context).getAppWidgetInfo(widget.config.widgetId)
|
||||
}
|
||||
|
||||
if (widgetInfo == null) {
|
||||
var replaceWidget by rememberSaveable {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 24.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
LargeMessage(
|
||||
icon = Icons.Rounded.Error,
|
||||
text = stringResource(id = R.string.app_widget_loading_failed)
|
||||
)
|
||||
}
|
||||
OutlinedButton(
|
||||
modifier = Modifier
|
||||
.padding(vertical = 24.dp)
|
||||
.align(Alignment.CenterHorizontally),
|
||||
contentPadding = ButtonDefaults.ButtonWithIconContentPadding,
|
||||
onClick = { replaceWidget = true }) {
|
||||
Icon(
|
||||
Icons.Rounded.Build,
|
||||
null,
|
||||
modifier = Modifier
|
||||
.padding(end = ButtonDefaults.IconSpacing)
|
||||
.size(ButtonDefaults.IconSize)
|
||||
)
|
||||
Text(stringResource(R.string.widget_action_replace))
|
||||
}
|
||||
if (replaceWidget) {
|
||||
WidgetPickerSheet(
|
||||
onDismiss = { replaceWidget = false },
|
||||
onWidgetSelected = {
|
||||
val updatedWidget = when (it) {
|
||||
is AppWidget -> widget.copy(
|
||||
config = widget.config.copy(
|
||||
widgetId = it.config.widgetId
|
||||
)
|
||||
)
|
||||
|
||||
is WeatherWidget -> it.copy(id = widget.id)
|
||||
is MusicWidget -> it.copy(id = widget.id)
|
||||
is CalendarWidget -> it.copy(id = widget.id)
|
||||
is FavoritesWidget -> it.copy(id = widget.id)
|
||||
}
|
||||
onWidgetUpdated(updatedWidget)
|
||||
replaceWidget = false
|
||||
}
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var dragDelta by remember { mutableStateOf(0) }
|
||||
Column {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(MaterialTheme.shapes.medium)
|
||||
.background(MaterialTheme.colorScheme.surfaceVariant)
|
||||
) {
|
||||
ExternalWidget(
|
||||
appWidgetHost = appWidgetHost,
|
||||
widgetInfo = widgetInfo,
|
||||
widgetId = widget.config.widgetId,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
height = widget.config.height + dragDelta,
|
||||
borderless = widget.config.borderless,
|
||||
)
|
||||
}
|
||||
val density = LocalDensity.current
|
||||
val draggableState = rememberDraggableState {
|
||||
dragDelta = (dragDelta + it / density.density).roundToInt()
|
||||
.coerceIn(
|
||||
-widget.config.height + 1,
|
||||
500 - widget.config.height
|
||||
)
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(top = 8.dp, bottom = 16.dp)
|
||||
.padding(horizontal = 8.dp)
|
||||
.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(end = 16.dp)
|
||||
.clip(MaterialTheme.shapes.small)
|
||||
.background(MaterialTheme.colorScheme.primaryContainer)
|
||||
.height(36.dp)
|
||||
.width(48.dp)
|
||||
.draggable(
|
||||
state = draggableState,
|
||||
orientation = Orientation.Vertical,
|
||||
startDragImmediately = true,
|
||||
onDragStopped = {
|
||||
onWidgetUpdated(
|
||||
widget.copy(
|
||||
config = widget.config.copy(
|
||||
height = widget.config.height + dragDelta
|
||||
)
|
||||
)
|
||||
)
|
||||
dragDelta = 0
|
||||
}
|
||||
),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.UnfoldMore,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
var textFieldValue by remember(widget.config.height) { mutableStateOf(widget.config.height.toString()) }
|
||||
OutlinedTextField(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(bottom = 8.dp),
|
||||
value = (widget.config.height + dragDelta).toString(),
|
||||
onValueChange = {
|
||||
val intValue = it.toIntOrNull()
|
||||
if (intValue == null) textFieldValue = ""
|
||||
else if (intValue in 1..500) {
|
||||
onWidgetUpdated(
|
||||
widget.copy(
|
||||
config = widget.config.copy(
|
||||
height = intValue
|
||||
)
|
||||
)
|
||||
)
|
||||
textFieldValue = intValue.toString()
|
||||
}
|
||||
},
|
||||
label = { Text(stringResource(R.string.widget_config_appwidget_height)) },
|
||||
suffix = { Text("dp") },
|
||||
keyboardOptions = KeyboardOptions(
|
||||
keyboardType = KeyboardType.Number,
|
||||
imeAction = ImeAction.Done
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier.padding(horizontal = 8.dp)
|
||||
) {
|
||||
OutlinedCard {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.widget_config_appwidget_borderless),
|
||||
iconPadding = false,
|
||||
value = widget.config.borderless,
|
||||
onValueChanged = {
|
||||
onWidgetUpdated(widget.copy(config = widget.config.copy(borderless = it)))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
if (isAtLeastApiLevel(28) && widgetInfo.widgetFeatures and AppWidgetProviderInfo.WIDGET_FEATURE_RECONFIGURABLE != 0) {
|
||||
TextButton(
|
||||
modifier = Modifier
|
||||
.padding(top = 8.dp)
|
||||
.align(Alignment.End),
|
||||
contentPadding = PaddingValues(
|
||||
end = 16.dp,
|
||||
top = 8.dp,
|
||||
start = 24.dp,
|
||||
bottom = 8.dp,
|
||||
),
|
||||
onClick = {
|
||||
appWidgetHost.startAppWidgetConfigureActivityForResult(
|
||||
context as Activity,
|
||||
widget.config.widgetId,
|
||||
0,
|
||||
0,
|
||||
null
|
||||
)
|
||||
}) {
|
||||
Text(stringResource(id = R.string.widget_config_appwidget_configure))
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.padding(start = ButtonDefaults.IconSpacing)
|
||||
.size(ButtonDefaults.IconSize),
|
||||
imageVector = Icons.Rounded.OpenInNew, contentDescription = null
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ColumnScope.ConfigureCalendarWidget(
|
||||
widget: CalendarWidget,
|
||||
onWidgetUpdated: (CalendarWidget) -> Unit
|
||||
) {
|
||||
val calendarRepository: CalendarRepository = get()
|
||||
val permissionsManager: PermissionsManager = get()
|
||||
var calendars by remember { mutableStateOf(emptyList<UserCalendar>()) }
|
||||
var ready by remember { mutableStateOf(false) }
|
||||
|
||||
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()
|
||||
) {
|
||||
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)))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
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
|
||||
if (calendars.isNotEmpty()) {
|
||||
OutlinedCard {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
for ((i, calendar) in calendars.withIndex()) {
|
||||
if (i > 0) Divider()
|
||||
CheckboxPreference(
|
||||
title = calendar.name,
|
||||
summary = calendar.owner,
|
||||
iconPadding = false,
|
||||
value = !widget.config.excludedCalendarIds.contains(calendar.id),
|
||||
onValueChanged = {
|
||||
onWidgetUpdated(
|
||||
widget.copy(
|
||||
config = widget.config.copy(
|
||||
excludedCalendarIds = if (it) {
|
||||
widget.config.excludedCalendarIds - calendar.id
|
||||
} else {
|
||||
widget.config.excludedCalendarIds + calendar.id
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (!hasPermission) {
|
||||
MissingPermissionBanner(
|
||||
modifier = Modifier.padding(8.dp),
|
||||
text = stringResource(R.string.missing_permission_calendar_widget_settings),
|
||||
onClick = { permissionsManager.requestPermission(context, PermissionGroup.Calendar) },
|
||||
)
|
||||
} else if (ready) {
|
||||
Text(
|
||||
modifier = Modifier.padding(top = 8.dp, bottom = 16.dp),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
text = "No calendars found"
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -15,7 +15,6 @@ class LauncherBottomSheetManager(registryOwner: SavedStateRegistryOwner) :
|
||||
val customizeSearchableSheetShown = mutableStateOf<SavableSearchable?>(null)
|
||||
val editFavoritesSheetShown = mutableStateOf(false)
|
||||
val hiddenItemsSheetShown = mutableStateOf(false)
|
||||
val widgetPickerSheetShown = mutableStateOf(false)
|
||||
|
||||
init {
|
||||
registryOwner.lifecycle.addObserver(LifecycleEventObserver { _, event ->
|
||||
@ -28,7 +27,6 @@ class LauncherBottomSheetManager(registryOwner: SavedStateRegistryOwner) :
|
||||
|
||||
editFavoritesSheetShown.value = state?.getBoolean(FAVORITES) ?: false
|
||||
hiddenItemsSheetShown.value = state?.getBoolean(HIDDEN) ?: false
|
||||
widgetPickerSheetShown.value = state?.getBoolean(WIDGETS) ?: false
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -37,7 +35,6 @@ class LauncherBottomSheetManager(registryOwner: SavedStateRegistryOwner) :
|
||||
return bundleOf(
|
||||
FAVORITES to editFavoritesSheetShown.value,
|
||||
HIDDEN to hiddenItemsSheetShown.value,
|
||||
WIDGETS to widgetPickerSheetShown.value,
|
||||
)
|
||||
}
|
||||
|
||||
@ -65,14 +62,6 @@ class LauncherBottomSheetManager(registryOwner: SavedStateRegistryOwner) :
|
||||
hiddenItemsSheetShown.value = false
|
||||
}
|
||||
|
||||
fun showWidgetPickerSheet() {
|
||||
widgetPickerSheetShown.value = true
|
||||
}
|
||||
|
||||
fun dismissWidgetPickerSheet() {
|
||||
widgetPickerSheetShown.value = false
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val PROVIDER = "bottom_sheet_manager"
|
||||
private const val FAVORITES = "favorites"
|
||||
|
||||
@ -13,9 +13,4 @@ fun LauncherBottomSheets() {
|
||||
if (bottomSheetManager.editFavoritesSheetShown.value) {
|
||||
EditFavoritesSheet(onDismiss = { bottomSheetManager.dismissEditFavoritesSheet() })
|
||||
}
|
||||
if (bottomSheetManager.widgetPickerSheetShown.value) {
|
||||
WidgetPickerSheet(
|
||||
onDismiss = { bottomSheetManager.dismissWidgetPickerSheet() }
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -199,7 +199,6 @@ private class BindAndConfigureAppWidgetContract(
|
||||
height = widgetProviderInfo.minHeight,
|
||||
widgetId = widgetId,
|
||||
),
|
||||
widgetProviderInfo = widgetProviderInfo,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -210,6 +209,7 @@ private class BindAndConfigureAppWidgetContract(
|
||||
|
||||
@Composable
|
||||
fun WidgetPickerSheet(
|
||||
onWidgetSelected: (Widget) -> Unit,
|
||||
onDismiss: () -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
@ -219,7 +219,7 @@ fun WidgetPickerSheet(
|
||||
val bindAppWidgetStarter =
|
||||
rememberLauncherForActivityResult(BindAndConfigureAppWidgetContract()) {
|
||||
if (it != null) {
|
||||
viewModel.pickWidget(it)
|
||||
onWidgetSelected(it)
|
||||
onDismiss()
|
||||
}
|
||||
}
|
||||
@ -290,7 +290,7 @@ fun WidgetPickerSheet(
|
||||
FavoritesWidget.Type -> FavoritesWidget(id)
|
||||
else -> return@OutlinedCard
|
||||
}
|
||||
viewModel.pickWidget(widget)
|
||||
onWidgetSelected(widget)
|
||||
onDismiss()
|
||||
}) {
|
||||
Row(
|
||||
|
||||
@ -110,11 +110,6 @@ class WidgetPickerSheetVM(
|
||||
|
||||
val expandedGroup = mutableStateOf<String?>(null)
|
||||
|
||||
fun pickWidget(widget: Widget) {
|
||||
val position = enabledWidgets.value.size
|
||||
widgetsService.addWidget(widget, position)
|
||||
}
|
||||
|
||||
fun toggleGroup(group: String) {
|
||||
expandedGroup.value = if (expandedGroup.value == group) null else group
|
||||
}
|
||||
|
||||
@ -24,6 +24,8 @@ import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.onPlaced
|
||||
@ -39,6 +41,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.ktx.animateTo
|
||||
import de.mm20.launcher2.ui.launcher.sheets.LocalBottomSheetManager
|
||||
import de.mm20.launcher2.ui.launcher.sheets.WidgetPickerSheet
|
||||
import de.mm20.launcher2.widgets.AppWidget
|
||||
import kotlinx.coroutines.awaitCancellation
|
||||
import kotlinx.coroutines.launch
|
||||
@ -56,6 +59,8 @@ fun WidgetColumn(
|
||||
val lifecycleOwner = LocalLifecycleOwner.current
|
||||
val widgetHost = remember { AppWidgetHost(context.applicationContext, 44203) }
|
||||
|
||||
var addNewWidget by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
LaunchedEffect(null) {
|
||||
lifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
widgetHost.startListening()
|
||||
@ -166,10 +171,20 @@ fun WidgetColumn(
|
||||
if (!editMode) {
|
||||
onEditModeChange(true)
|
||||
} else {
|
||||
bottomSheetManager.showWidgetPickerSheet()
|
||||
addNewWidget = true
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (addNewWidget) {
|
||||
WidgetPickerSheet(
|
||||
onDismiss = { addNewWidget = false },
|
||||
onWidgetSelected = {
|
||||
viewModel.addWidget(it)
|
||||
addNewWidget = false
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -1,36 +1,57 @@
|
||||
package de.mm20.launcher2.ui.launcher.widgets
|
||||
|
||||
import android.app.Activity
|
||||
import android.appwidget.AppWidgetHost
|
||||
import android.util.Log
|
||||
import android.appwidget.AppWidgetManager
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.foundation.gestures.*
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.gestures.DraggableState
|
||||
import androidx.compose.foundation.gestures.Orientation
|
||||
import androidx.compose.foundation.gestures.draggable
|
||||
import androidx.compose.foundation.gestures.rememberDraggableState
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.*
|
||||
import androidx.compose.material.icons.rounded.Delete
|
||||
import androidx.compose.material.icons.rounded.DragIndicator
|
||||
import androidx.compose.material.icons.rounded.Tune
|
||||
import androidx.compose.material.icons.rounded.Warning
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.zIndex
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.component.Banner
|
||||
import de.mm20.launcher2.ui.component.LauncherCard
|
||||
import de.mm20.launcher2.ui.launcher.sheets.ConfigureWidgetSheet
|
||||
import de.mm20.launcher2.ui.launcher.sheets.WidgetPickerSheet
|
||||
import de.mm20.launcher2.ui.launcher.widgets.calendar.CalendarWidget
|
||||
import de.mm20.launcher2.ui.launcher.widgets.external.ExternalWidget
|
||||
import de.mm20.launcher2.ui.launcher.widgets.favorites.FavoritesWidget
|
||||
import de.mm20.launcher2.ui.launcher.widgets.music.MusicWidget
|
||||
import de.mm20.launcher2.ui.launcher.widgets.weather.WeatherWidget
|
||||
import de.mm20.launcher2.widgets.*
|
||||
import kotlin.math.roundToInt
|
||||
import de.mm20.launcher2.widgets.AppWidget
|
||||
import de.mm20.launcher2.widgets.CalendarWidget
|
||||
import de.mm20.launcher2.widgets.FavoritesWidget
|
||||
import de.mm20.launcher2.widgets.MusicWidget
|
||||
import de.mm20.launcher2.widgets.WeatherWidget
|
||||
import de.mm20.launcher2.widgets.Widget
|
||||
|
||||
@Composable
|
||||
fun WidgetItem(
|
||||
@ -44,11 +65,16 @@ fun WidgetItem(
|
||||
onDragStopped: () -> Unit = {}
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
var resizeMode by remember(editMode) { mutableStateOf(false) }
|
||||
|
||||
var configure by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
var isDragged by remember { mutableStateOf(false) }
|
||||
val elevation by animateDpAsState(if (isDragged) 8.dp else 2.dp)
|
||||
|
||||
val appWidget = if (widget is AppWidget) remember(widget.config.widgetId) {
|
||||
AppWidgetManager.getInstance(context).getAppWidgetInfo(widget.config.widgetId)
|
||||
} else null
|
||||
|
||||
LauncherCard(
|
||||
modifier = modifier.zIndex(if (isDragged) 1f else 0f),
|
||||
elevation = elevation
|
||||
@ -76,7 +102,18 @@ fun WidgetItem(
|
||||
)
|
||||
)
|
||||
Text(
|
||||
text = remember(widget) { widget.loadLabel(context) },
|
||||
text = when (widget) {
|
||||
is WeatherWidget -> stringResource(R.string.widget_name_weather)
|
||||
is MusicWidget -> stringResource(R.string.widget_name_music)
|
||||
is CalendarWidget -> stringResource(R.string.widget_name_calendar)
|
||||
is FavoritesWidget -> stringResource(R.string.widget_name_favorites)
|
||||
is AppWidget -> remember(widget.config.widgetId) {
|
||||
appWidget?.loadLabel(
|
||||
context.packageManager
|
||||
)
|
||||
}
|
||||
?: stringResource(R.string.widget_name_unknown)
|
||||
},
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
@ -84,23 +121,13 @@ fun WidgetItem(
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1
|
||||
)
|
||||
if (widget is AppWidget) {
|
||||
IconButton(onClick = { resizeMode = !resizeMode }) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Edit,
|
||||
contentDescription = stringResource(R.string.widget_action_adjust_height)
|
||||
)
|
||||
}
|
||||
}
|
||||
if (widget.isConfigurable) {
|
||||
IconButton({
|
||||
widget.configure(context as Activity, appWidgetHost)
|
||||
}) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Settings,
|
||||
contentDescription = stringResource(R.string.settings)
|
||||
)
|
||||
}
|
||||
IconButton(onClick = {
|
||||
configure = true
|
||||
}) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Tune,
|
||||
contentDescription = stringResource(R.string.settings)
|
||||
)
|
||||
}
|
||||
IconButton(onClick = { onWidgetRemove() }) {
|
||||
Icon(
|
||||
@ -110,61 +137,89 @@ fun WidgetItem(
|
||||
}
|
||||
}
|
||||
}
|
||||
AnimatedVisibility(!editMode || resizeMode) {
|
||||
AnimatedVisibility(!editMode) {
|
||||
when (widget) {
|
||||
is WeatherWidget -> {
|
||||
WeatherWidget()
|
||||
WeatherWidget(widget)
|
||||
}
|
||||
|
||||
is MusicWidget -> {
|
||||
MusicWidget()
|
||||
}
|
||||
|
||||
is CalendarWidget -> {
|
||||
CalendarWidget()
|
||||
CalendarWidget(widget)
|
||||
}
|
||||
|
||||
is FavoritesWidget -> {
|
||||
FavoritesWidget()
|
||||
FavoritesWidget(widget)
|
||||
}
|
||||
|
||||
is AppWidget -> {
|
||||
var dragDelta by remember { mutableStateOf(0) }
|
||||
Column {
|
||||
val widgetInfo = remember(widget.config.widgetId) {
|
||||
AppWidgetManager.getInstance(context)
|
||||
.getAppWidgetInfo(widget.config.widgetId)
|
||||
}
|
||||
if (widgetInfo == null) {
|
||||
var replaceWidget by rememberSaveable {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
Banner(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
text = stringResource(R.string.app_widget_loading_failed),
|
||||
icon = Icons.Rounded.Warning,
|
||||
secondaryAction = {
|
||||
OutlinedButton(onClick = onWidgetRemove) {
|
||||
Text(stringResource(R.string.widget_action_remove))
|
||||
}
|
||||
},
|
||||
primaryAction = {
|
||||
Button(onClick = { replaceWidget = true }) {
|
||||
Text(stringResource(R.string.widget_action_replace))
|
||||
}
|
||||
}
|
||||
)
|
||||
if (replaceWidget) {
|
||||
WidgetPickerSheet(
|
||||
onDismiss = { replaceWidget = false },
|
||||
onWidgetSelected = {
|
||||
val updatedWidget = when (it) {
|
||||
is AppWidget -> widget.copy(
|
||||
config = widget.config.copy(
|
||||
widgetId = it.config.widgetId
|
||||
)
|
||||
)
|
||||
is WeatherWidget -> it.copy(id = widget.id)
|
||||
is MusicWidget -> it.copy(id = widget.id)
|
||||
is CalendarWidget -> it.copy(id = widget.id)
|
||||
is FavoritesWidget -> it.copy(id = widget.id)
|
||||
}
|
||||
onWidgetUpdate(updatedWidget)
|
||||
replaceWidget = false
|
||||
}
|
||||
)
|
||||
}
|
||||
} else {
|
||||
ExternalWidget(
|
||||
appWidgetHost = appWidgetHost,
|
||||
widgetId = widget.config.widgetId,
|
||||
widgetInfo = widgetInfo,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
height = widget.config.height + dragDelta,
|
||||
height = widget.config.height,
|
||||
borderless = widget.config.borderless,
|
||||
)
|
||||
if (resizeMode) {
|
||||
val density = LocalDensity.current
|
||||
val drgStt = rememberDraggableState {
|
||||
dragDelta += (it / density.density).roundToInt()
|
||||
}
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.DragHandle,
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.padding(top = 12.dp)
|
||||
.requiredHeight(24.dp)
|
||||
.fillMaxWidth()
|
||||
.draggable(
|
||||
state = drgStt,
|
||||
orientation = Orientation.Vertical,
|
||||
startDragImmediately = true,
|
||||
onDragStopped = {
|
||||
onWidgetUpdate(widget.copy(
|
||||
config = widget.config.copy(
|
||||
height = widget.config.height + dragDelta
|
||||
)
|
||||
))
|
||||
dragDelta = 0
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (configure) {
|
||||
ConfigureWidgetSheet(
|
||||
appWidgetHost = appWidgetHost,
|
||||
widget = widget,
|
||||
onWidgetUpdated = onWidgetUpdate,
|
||||
onDismiss = { configure = false },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,6 +19,11 @@ class WidgetsVM : ViewModel(), KoinComponent {
|
||||
|
||||
val widgets = widgetRepository.get().asLiveData()
|
||||
|
||||
fun addWidget(widget: Widget) {
|
||||
val widgets = widgets.value?.toMutableList() ?: return
|
||||
widgets.add(widget)
|
||||
widgetRepository.set(widgets)
|
||||
}
|
||||
|
||||
fun removeWidget(widget: Widget) {
|
||||
widgetRepository.delete(widget)
|
||||
|
||||
@ -27,11 +27,14 @@ import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.component.InnerCard
|
||||
import de.mm20.launcher2.ui.component.MissingPermissionBanner
|
||||
import de.mm20.launcher2.ui.launcher.search.common.list.SearchResultList
|
||||
import de.mm20.launcher2.widgets.CalendarWidget
|
||||
import java.time.LocalDate
|
||||
import java.time.ZoneId
|
||||
|
||||
@Composable
|
||||
fun CalendarWidget() {
|
||||
fun CalendarWidget(
|
||||
widget: CalendarWidget,
|
||||
) {
|
||||
val viewModel: CalendarWidgetVM = viewModel()
|
||||
val context = LocalContext.current
|
||||
val lifecycleOwner = LocalLifecycleOwner.current
|
||||
@ -42,6 +45,10 @@ fun CalendarWidget() {
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(widget) {
|
||||
viewModel.updateWidget(widget)
|
||||
}
|
||||
|
||||
Column {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
|
||||
@ -17,6 +17,9 @@ import de.mm20.launcher2.permissions.PermissionsManager
|
||||
import de.mm20.launcher2.preferences.LauncherDataStore
|
||||
import de.mm20.launcher2.search.data.CalendarEvent
|
||||
import de.mm20.launcher2.services.favorites.FavoritesService
|
||||
import de.mm20.launcher2.widgets.CalendarWidget
|
||||
import de.mm20.launcher2.widgets.CalendarWidgetConfig
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.map
|
||||
import org.koin.core.component.KoinComponent
|
||||
@ -30,11 +33,12 @@ import kotlin.math.max
|
||||
|
||||
class CalendarWidgetVM : ViewModel(), KoinComponent {
|
||||
|
||||
private val dataStore: LauncherDataStore by inject()
|
||||
private val calendarRepository: CalendarRepository by inject()
|
||||
private val favoritesService: FavoritesService by inject()
|
||||
private val searchableRepository: SearchableRepository by inject()
|
||||
|
||||
private val widgetConfig = MutableStateFlow(CalendarWidgetConfig())
|
||||
|
||||
val calendarEvents = MutableLiveData<List<CalendarEvent>>(emptyList())
|
||||
val pinnedCalendarEvents =
|
||||
favoritesService.getFavorites(
|
||||
@ -53,6 +57,10 @@ class CalendarWidgetVM : ViewModel(), KoinComponent {
|
||||
|
||||
val selectedDate = MutableLiveData(LocalDate.now())
|
||||
|
||||
fun updateWidget(widget: CalendarWidget) {
|
||||
widgetConfig.value = widget.config
|
||||
}
|
||||
|
||||
private var upcomingEvents: List<CalendarEvent> = emptyList()
|
||||
set(value) {
|
||||
field = value
|
||||
@ -155,10 +163,10 @@ class CalendarWidgetVM : ViewModel(), KoinComponent {
|
||||
|
||||
suspend fun onActive() {
|
||||
selectDate(LocalDate.now())
|
||||
dataStore.data.map { it.calendarWidget }.collectLatest { settings ->
|
||||
widgetConfig.collectLatest { config ->
|
||||
calendarRepository.getUpcomingEvents(
|
||||
excludeAllDayEvents = settings.hideAlldayEvents,
|
||||
excludeCalendars = settings.excludeCalendarsList
|
||||
excludeAllDayEvents = !config.allDayEvents,
|
||||
excludeCalendars = config.excludedCalendarIds,
|
||||
).collectLatest { events ->
|
||||
searchableRepository.getKeys(
|
||||
includeTypes = listOf(CalendarEvent.Domain),
|
||||
|
||||
@ -2,6 +2,7 @@ package de.mm20.launcher2.ui.launcher.widgets.external
|
||||
|
||||
import android.appwidget.AppWidgetHost
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.appwidget.AppWidgetProviderInfo
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
@ -22,20 +23,20 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.core.view.doOnNextLayout
|
||||
import androidx.core.view.iterator
|
||||
import androidx.core.view.setPadding
|
||||
import de.mm20.launcher2.ui.ktx.toPixels
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@Composable
|
||||
fun ExternalWidget(
|
||||
appWidgetHost: AppWidgetHost,
|
||||
widgetInfo: AppWidgetProviderInfo,
|
||||
widgetId: Int,
|
||||
height: Int,
|
||||
modifier: Modifier = Modifier
|
||||
modifier: Modifier = Modifier,
|
||||
borderless: Boolean = false,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val widgetInfo = remember(widgetId) {
|
||||
AppWidgetManager.getInstance(context).getAppWidgetInfo(widgetId)
|
||||
}
|
||||
val padding = if (borderless) 0 else 8.dp.toPixels().roundToInt()
|
||||
BoxWithConstraints {
|
||||
val maxWidth = maxWidth
|
||||
key(widgetId) {
|
||||
@ -56,6 +57,7 @@ fun ExternalWidget(
|
||||
maxWidth.value.roundToInt(),
|
||||
height,
|
||||
)
|
||||
it.setPadding(padding)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@ -18,14 +18,15 @@ import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.common.FavoritesTagSelector
|
||||
import de.mm20.launcher2.ui.component.Banner
|
||||
import de.mm20.launcher2.ui.launcher.search.common.grid.SearchResultGrid
|
||||
import de.mm20.launcher2.widgets.FavoritesWidget
|
||||
|
||||
@Composable
|
||||
fun FavoritesWidget() {
|
||||
fun FavoritesWidget(widget: FavoritesWidget) {
|
||||
val viewModel: FavoritesWidgetVM = viewModel()
|
||||
val favorites by remember { viewModel.favorites }.collectAsState(emptyList())
|
||||
val pinnedTags by viewModel.pinnedTags.collectAsState(emptyList())
|
||||
val selectedTag by viewModel.selectedTag.collectAsState(null)
|
||||
val favoritesEditButton by viewModel.showEditButton.collectAsState(false)
|
||||
val favoritesEditButton = widget.config.editButton
|
||||
|
||||
val tagsExpanded by viewModel.tagsExpanded.collectAsState(false)
|
||||
|
||||
|
||||
@ -33,6 +33,7 @@ import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@ -44,6 +45,7 @@ import androidx.compose.ui.draw.rotate
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||
import androidx.compose.ui.res.booleanResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
@ -62,13 +64,14 @@ import de.mm20.launcher2.ui.ktx.blendIntoViewScale
|
||||
import de.mm20.launcher2.ui.locals.LocalCardStyle
|
||||
import de.mm20.launcher2.weather.DailyForecast
|
||||
import de.mm20.launcher2.weather.Forecast
|
||||
import de.mm20.launcher2.widgets.WeatherWidget
|
||||
import java.text.DateFormat
|
||||
import java.text.DecimalFormat
|
||||
import java.text.SimpleDateFormat
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@Composable
|
||||
fun WeatherWidget() {
|
||||
fun WeatherWidget(widget: WeatherWidget) {
|
||||
val viewModel: WeatherWidgetWM = viewModel()
|
||||
|
||||
val context = LocalContext.current
|
||||
@ -82,9 +85,8 @@ fun WeatherWidget() {
|
||||
|
||||
val selectedForecast by viewModel.currentForecast.observeAsState()
|
||||
|
||||
val imperialUnits by viewModel.imperialUnits.observeAsState(false)
|
||||
|
||||
val compactMode by viewModel.compactMode.observeAsState(false)
|
||||
val imperialUnits by viewModel.imperialUnits.collectAsState(false)
|
||||
val compactMode = !widget.config.showForecast
|
||||
|
||||
var showLocationDialog by remember { mutableStateOf(false) }
|
||||
|
||||
|
||||
@ -8,8 +8,10 @@ import de.mm20.launcher2.preferences.LauncherDataStore
|
||||
import de.mm20.launcher2.weather.DailyForecast
|
||||
import de.mm20.launcher2.weather.Forecast
|
||||
import de.mm20.launcher2.weather.WeatherRepository
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.shareIn
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
@ -104,9 +106,7 @@ class WeatherWidgetWM : ViewModel(), KoinComponent {
|
||||
}
|
||||
val autoLocation = weatherRepository.autoLocation.asLiveData()
|
||||
|
||||
val imperialUnits = dataStore.data.map { it.weather.imperialUnits }.asLiveData()
|
||||
|
||||
val compactMode = dataStore.data.map { it.weather.compactMode }.asLiveData()
|
||||
val imperialUnits = dataStore.data.map { it.weather.imperialUnits }
|
||||
|
||||
fun selectDay(index: Int) {
|
||||
selectedDayIndex = min(index, forecasts.lastIndex)
|
||||
|
||||
@ -27,7 +27,6 @@ import de.mm20.launcher2.ui.settings.integrations.IntegrationsSettingsScreen
|
||||
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.calendarwidget.CalendarWidgetSettingsScreen
|
||||
import de.mm20.launcher2.ui.settings.cards.CardsSettingsScreen
|
||||
import de.mm20.launcher2.ui.settings.clockwidget.ClockWidgetSettingsScreen
|
||||
import de.mm20.launcher2.ui.settings.colorscheme.ColorSchemeSettingsScreen
|
||||
@ -139,15 +138,12 @@ class SettingsActivity : BaseActivity() {
|
||||
composable("settings/search/tags") {
|
||||
TagsSettingsScreen()
|
||||
}
|
||||
composable("settings/integrations/weather") {
|
||||
composable(ROUTE_WEATHER_INTEGRATION) {
|
||||
WeatherIntegrationSettingsScreen()
|
||||
}
|
||||
composable("settings/integrations/media") {
|
||||
composable(ROUTE_MEDIA_INTEGRATION) {
|
||||
MediaIntegrationSettingsScreen()
|
||||
}
|
||||
composable("settings/widgets/calendar") {
|
||||
CalendarWidgetSettingsScreen()
|
||||
}
|
||||
composable("settings/homescreen/clock") {
|
||||
ClockWidgetSettingsScreen()
|
||||
}
|
||||
@ -214,5 +210,7 @@ class SettingsActivity : BaseActivity() {
|
||||
|
||||
companion object {
|
||||
const val EXTRA_ROUTE = "de.mm20.launcher2.settings.ROUTE"
|
||||
const val ROUTE_WEATHER_INTEGRATION = "settings/integrations/weather"
|
||||
const val ROUTE_MEDIA_INTEGRATION = "settings/integrations/media"
|
||||
}
|
||||
}
|
||||
@ -1,173 +0,0 @@
|
||||
package de.mm20.launcher2.ui.settings.calendarwidget
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import de.mm20.launcher2.search.data.UserCalendar
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.component.MissingPermissionBanner
|
||||
import de.mm20.launcher2.ui.component.preferences.Preference
|
||||
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
|
||||
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
|
||||
import de.mm20.launcher2.ui.component.preferences.SwitchPreference
|
||||
|
||||
@Composable
|
||||
fun CalendarWidgetSettingsScreen() {
|
||||
val viewModel: CalendarWidgetSettingsScreenVM = viewModel()
|
||||
val context = LocalContext.current
|
||||
PreferenceScreen(
|
||||
title = stringResource(R.string.preference_screen_calendarwidget),
|
||||
helpUrl = "https://kvaesitso.mm20.de/docs/user-guide/widgets/calendar-widget"
|
||||
) {
|
||||
item {
|
||||
val excludeAllDayEvents by viewModel.excludeAllDayEvents.observeAsState()
|
||||
PreferenceCategory {
|
||||
val hasPermission by viewModel.hasCalendarPermission.observeAsState()
|
||||
AnimatedVisibility(hasPermission == false) {
|
||||
MissingPermissionBanner(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
text = stringResource(R.string.missing_permission_calendar_widget_settings),
|
||||
onClick = {
|
||||
viewModel.requestPermission(context as AppCompatActivity)
|
||||
}
|
||||
)
|
||||
}
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.preference_calendar_hide_allday),
|
||||
value = excludeAllDayEvents == true,
|
||||
onValueChanged = {
|
||||
viewModel.setExcludeAllDayEvents(it)
|
||||
}
|
||||
)
|
||||
val calendars by viewModel.calendars.observeAsState(emptyList())
|
||||
val unselectedCalendars by viewModel.unselectedCalendars.observeAsState(emptyList())
|
||||
ExcludedCalendarsPreference(
|
||||
calendars = calendars,
|
||||
value = unselectedCalendars,
|
||||
onValueChanged = {
|
||||
viewModel.setUnselectedCalendars(it)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ExcludedCalendarsPreference(
|
||||
calendars: List<UserCalendar>,
|
||||
value: List<Long>,
|
||||
onValueChanged: (List<Long>) -> Unit
|
||||
) {
|
||||
var showDialog by remember { mutableStateOf(false) }
|
||||
Preference(
|
||||
title = stringResource(R.string.preference_calendar_calendars),
|
||||
summary = pluralStringResource(
|
||||
R.plurals.preference_calendar_calendars_summary,
|
||||
count = calendars.size - value.size,
|
||||
calendars.size - value.size
|
||||
),
|
||||
onClick = {
|
||||
showDialog = true
|
||||
}
|
||||
)
|
||||
if (showDialog) {
|
||||
Dialog(
|
||||
onDismissRequest = { showDialog = false },
|
||||
) {
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(max = 400.dp),
|
||||
shape = MaterialTheme.shapes.extraLarge,
|
||||
tonalElevation = 16.dp,
|
||||
shadowElevation = 16.dp,
|
||||
) {
|
||||
Column {
|
||||
Text(
|
||||
text = stringResource(R.string.preference_calendar_calendars),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(
|
||||
start = 24.dp, end = 24.dp, top = 16.dp, bottom = 8.dp
|
||||
)
|
||||
)
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(horizontal = 16.dp)
|
||||
) {
|
||||
items(calendars) { c ->
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
if (value.contains(c.id)) {
|
||||
onValueChanged(
|
||||
value.filter { it != c.id }
|
||||
)
|
||||
} else {
|
||||
onValueChanged(
|
||||
value + c.id
|
||||
)
|
||||
}
|
||||
}
|
||||
) {
|
||||
Checkbox(
|
||||
checked = !value.contains(c.id),
|
||||
onCheckedChange = {
|
||||
if (it) {
|
||||
onValueChanged(
|
||||
value.filter { it != c.id }
|
||||
)
|
||||
} else {
|
||||
onValueChanged(
|
||||
value + c.id
|
||||
)
|
||||
}
|
||||
},
|
||||
colors = CheckboxDefaults.colors(
|
||||
checkedColor = Color(c.color)
|
||||
)
|
||||
)
|
||||
Text(text = c.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
TextButton(
|
||||
onClick = {
|
||||
onValueChanged(value.toList())
|
||||
showDialog = false
|
||||
},
|
||||
modifier = Modifier
|
||||
.align(Alignment.End)
|
||||
.padding(vertical = 12.dp, horizontal = 16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(android.R.string.ok),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,62 +0,0 @@
|
||||
package de.mm20.launcher2.ui.settings.calendarwidget
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.asLiveData
|
||||
import androidx.lifecycle.liveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import de.mm20.launcher2.calendar.CalendarRepository
|
||||
import de.mm20.launcher2.permissions.PermissionGroup
|
||||
import de.mm20.launcher2.permissions.PermissionsManager
|
||||
import de.mm20.launcher2.preferences.LauncherDataStore
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
|
||||
class CalendarWidgetSettingsScreenVM : ViewModel(), KoinComponent {
|
||||
private val dataStore: LauncherDataStore by inject()
|
||||
private val calendarRepository: CalendarRepository by inject()
|
||||
private val permissionsManager: PermissionsManager by inject()
|
||||
|
||||
val hasCalendarPermission = permissionsManager.hasPermission(PermissionGroup.Calendar).asLiveData()
|
||||
|
||||
val excludeAllDayEvents = dataStore.data.map { it.calendarWidget.hideAlldayEvents }.asLiveData()
|
||||
fun setExcludeAllDayEvents(excludeAllDayEvents: Boolean) {
|
||||
viewModelScope.launch {
|
||||
dataStore.updateData {
|
||||
it.toBuilder()
|
||||
.setCalendarWidget(
|
||||
it.calendarWidget
|
||||
.toBuilder()
|
||||
.setHideAlldayEvents(excludeAllDayEvents)
|
||||
)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val calendars = liveData {
|
||||
emit(calendarRepository.getCalendars())
|
||||
}
|
||||
val unselectedCalendars =
|
||||
dataStore.data.map { it.calendarWidget.excludeCalendarsList }.asLiveData()
|
||||
|
||||
fun setUnselectedCalendars(unselectedCalendars: List<Long>) {
|
||||
viewModelScope.launch {
|
||||
dataStore.updateData {
|
||||
it.toBuilder()
|
||||
.setCalendarWidget(
|
||||
it.calendarWidget.toBuilder()
|
||||
.clearExcludeCalendars()
|
||||
.addAllExcludeCalendars(unselectedCalendars)
|
||||
)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun requestPermission(activity: AppCompatActivity) {
|
||||
permissionsManager.requestPermission(activity, PermissionGroup.Calendar)
|
||||
}
|
||||
}
|
||||
@ -46,24 +46,17 @@ fun WeatherIntegrationSettingsScreen() {
|
||||
},
|
||||
value = weatherProvider
|
||||
)
|
||||
val imperialUnits by viewModel.imperialUnits.observeAsState(false)
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.preference_imperial_units),
|
||||
summary = stringResource(R.string.preference_imperial_units_summary),
|
||||
value = imperialUnits,
|
||||
onValueChanged = {
|
||||
viewModel.setImperialUnits(it)
|
||||
}
|
||||
)
|
||||
val compactMode by viewModel.compactMode.observeAsState(false)
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.preference_compact_mode),
|
||||
summary = stringResource(R.string.preference_compact_mode_summary),
|
||||
value = compactMode,
|
||||
onValueChanged = {
|
||||
viewModel.setCompactMode(it)
|
||||
})
|
||||
}
|
||||
val imperialUnits by viewModel.imperialUnits.collectAsState(false)
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.preference_imperial_units),
|
||||
summary = stringResource(R.string.preference_imperial_units_summary),
|
||||
value = imperialUnits,
|
||||
onValueChanged = {
|
||||
viewModel.setImperialUnits(it)
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
item {
|
||||
PreferenceCategory(title = stringResource(R.string.preference_category_location)) {
|
||||
|
||||
@ -20,12 +20,17 @@ import org.koin.core.component.inject
|
||||
|
||||
class WeatherIntegrationSettingsScreenVM : ViewModel(), KoinComponent {
|
||||
private val repository: WeatherRepository by inject()
|
||||
private val dataStore: LauncherDataStore by inject()
|
||||
private val permissionsManager: PermissionsManager by inject()
|
||||
private val dataStore: LauncherDataStore by inject()
|
||||
|
||||
val availableProviders = repository.getAvailableProviders()
|
||||
|
||||
val imperialUnits = dataStore.data.map { it.weather.imperialUnits }.asLiveData()
|
||||
val weatherProvider = repository.selectedProvider.asLiveData()
|
||||
fun setWeatherProvider(provider: WeatherSettings.WeatherProvider) {
|
||||
repository.selectProvider(provider)
|
||||
}
|
||||
|
||||
val imperialUnits = dataStore.data.map { it.weather.imperialUnits }
|
||||
fun setImperialUnits(imperialUnits: Boolean) {
|
||||
viewModelScope.launch {
|
||||
dataStore.updateData {
|
||||
@ -36,22 +41,6 @@ class WeatherIntegrationSettingsScreenVM : ViewModel(), KoinComponent {
|
||||
}
|
||||
}
|
||||
|
||||
val compactMode = dataStore.data.map { it.weather.compactMode }.asLiveData()
|
||||
fun setCompactMode(compactMode: Boolean) {
|
||||
viewModelScope.launch {
|
||||
dataStore.updateData {
|
||||
it.toBuilder()
|
||||
.setWeather(it.weather.toBuilder().setCompactMode(compactMode))
|
||||
.build()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val weatherProvider = repository.selectedProvider.asLiveData()
|
||||
fun setWeatherProvider(provider: WeatherSettings.WeatherProvider) {
|
||||
repository.selectProvider(provider)
|
||||
}
|
||||
|
||||
val autoLocation = repository.autoLocation.asLiveData()
|
||||
fun setAutoLocation(autoLocation: Boolean) {
|
||||
repository.setAutoLocation(autoLocation)
|
||||
|
||||
@ -574,7 +574,7 @@
|
||||
<string name="preference_layout_fixed_rotation_summary">Uzamknout rotaci obrazovky na režim na výšku</string>
|
||||
<string name="preference_layout_fixed_rotation">Pevná rotace obrazovky</string>
|
||||
<string name="icon_pack_dynamic_colors">Dynamické barvy</string>
|
||||
<string name="preference_compact_mode">Kompaktní režim</string>
|
||||
<string name="widget_config_weather_compact">Kompaktní režim</string>
|
||||
<string name="preference_compact_mode_summary">Skrýt hodinovou a denní předpověď</string>
|
||||
<string name="preference_search_bar_launch_on_enter">Spustit při vstupu</string>
|
||||
<string name="preference_search_bar_launch_on_enter_summary">Spustit zvýrazněnou shodu nebo rychlou akci při klepnutí na Přejít</string>
|
||||
|
||||
@ -43,7 +43,7 @@
|
||||
<string name="preference_location">Standort</string>
|
||||
<string name="preference_imperial_units_summary">Grad Fahrenheit und Meilen pro Stunde verwenden</string>
|
||||
<string name="preference_imperial_units">Imperiale Einheiten</string>
|
||||
<string name="preference_compact_mode">Kompakter Modus</string>
|
||||
<string name="widget_config_weather_compact">Kompakter Modus</string>
|
||||
<string name="preference_compact_mode_summary">Stündliche und tägliche Vorhersagen ausblenden</string>
|
||||
<string name="preference_category_debug">Debug</string>
|
||||
<string name="wikipedia_url">https://de.wikipedia.org</string>
|
||||
|
||||
@ -574,7 +574,7 @@
|
||||
<string name="preference_layout_fixed_rotation">Rotazione schermo fissa</string>
|
||||
<string name="preference_layout_fixed_rotation_summary">Blocca rotazione dello schermo in modalità verticale</string>
|
||||
<string name="icon_pack_dynamic_colors">Colori dinamici</string>
|
||||
<string name="preference_compact_mode">Modalità compatta</string>
|
||||
<string name="widget_config_weather_compact">Modalità compatta</string>
|
||||
<string name="preference_compact_mode_summary">Nascondi previsioni orarie e giornaliere</string>
|
||||
<string name="icon_picker_no_packs_installed">Nessun icon pack installato</string>
|
||||
<string name="preference_search_bar_launch_on_enter">Avvia con invio</string>
|
||||
|
||||
@ -568,7 +568,7 @@
|
||||
<string name="preference_layout_fixed_search_bar">Vastgezette zoekbalk</string>
|
||||
<string name="preference_layout_fixed_rotation">Vastgezette schermroratie</string>
|
||||
<string name="preference_layout_fixed_rotation_summary">Zet scherm vast in portretmodus</string>
|
||||
<string name="preference_compact_mode">Compacte modus</string>
|
||||
<string name="widget_config_weather_compact">Compacte modus</string>
|
||||
<string name="preference_search_bar_launch_on_enter">Uitvoeren bij enter</string>
|
||||
<string name="preference_search_bar_launch_on_enter_summary">De gemarkeerde app of snelle actie uitvoeren bij tikken op enter</string>
|
||||
<string name="preference_compact_mode_summary">Voorspellingen per uur en per dag verbergen</string>
|
||||
|
||||
@ -372,7 +372,7 @@
|
||||
<string name="preference_imperial_units_summary">Use graus Fahrenheit e milhas por hora</string>
|
||||
<string name="preference_cards">Cartões</string>
|
||||
<string name="preference_cards_summary">Personalizar aparência dos cartões</string>
|
||||
<string name="preference_compact_mode">Modo compacto</string>
|
||||
<string name="widget_config_weather_compact">Modo compacto</string>
|
||||
<string name="preference_compact_mode_summary">Ocultar previsões horárias e diárias</string>
|
||||
<string name="preference_cards_shape_cut">Cortado</string>
|
||||
<string name="preference_icon_shape_rounded_square">Quadrado arredondado</string>
|
||||
|
||||
@ -507,7 +507,7 @@
|
||||
<string name="favorites">Избранные</string>
|
||||
<string name="favorites_empty">Закрепленные и часто используемые приложения и действия будут находиться здесь</string>
|
||||
<string name="preference_clock_widget_fill_height_summary">Добавить дополнительное пространство над часами, чтобы заполнить всю высоту экрана</string>
|
||||
<string name="preference_compact_mode">Компактный вид</string>
|
||||
<string name="widget_config_weather_compact">Компактный вид</string>
|
||||
<string name="preference_compact_mode_summary">Скрыть почасовой и ежедневный прогноз погоды</string>
|
||||
<string name="preference_clock_widget_fill_height">Заполнить весь экран</string>
|
||||
<string name="preference_clock_widget_color">Цвет</string>
|
||||
|
||||
@ -563,7 +563,7 @@
|
||||
<string name="preference_layout_fixed_search_bar_summary">在离开主页面视图时禁止滚动搜索栏</string>
|
||||
<string name="preference_layout_fixed_rotation">修正屏幕方向</string>
|
||||
<string name="preference_layout_fixed_rotation_summary">锁定屏幕方向为竖屏模式</string>
|
||||
<string name="preference_compact_mode">紧凑模式</string>
|
||||
<string name="widget_config_weather_compact">紧凑模式</string>
|
||||
<string name="preference_compact_mode_summary">隐藏每小时和每日预测</string>
|
||||
<string name="icon_pack_dynamic_colors">动态颜色</string>
|
||||
<string name="icon_picker_no_packs_installed">没有已安装的图标包</string>
|
||||
|
||||
@ -294,7 +294,7 @@
|
||||
<string name="preference_location">位置</string>
|
||||
<string name="preference_imperial_units_summary">使用華氏度和英里/小時</string>
|
||||
<string name="preference_imperial_units">英制單位</string>
|
||||
<string name="preference_compact_mode">緊湊模式</string>
|
||||
<string name="widget_config_weather_compact">緊湊模式</string>
|
||||
<string name="preference_compact_mode_summary">隱藏每小時和每日預測</string>
|
||||
<string name="preference_category_debug">除錯</string>
|
||||
<string name="preference_category_debug_tools">工具</string>
|
||||
|
||||
@ -219,6 +219,7 @@
|
||||
<string name="widget_name_calendar">Calendar</string>
|
||||
<string name="widget_name_music">Music</string>
|
||||
<string name="widget_name_favorites">Favorites</string>
|
||||
<string name="widget_name_unknown">Unknown app widget</string>
|
||||
<string name="widget_add_widget">Add widget</string>
|
||||
<!-- Add a third party widget (=a standard Android app widget) -->
|
||||
<string name="widget_add_external">More</string>
|
||||
@ -246,6 +247,7 @@
|
||||
<string name="widget_action_adjust_height">Adjust height</string>
|
||||
<string name="widget_action_remove">Remove</string>
|
||||
<string name="widget_action_settings">Settings</string>
|
||||
<string name="widget_action_replace">Replace</string>
|
||||
<string name="menu_item_edit_favs">Edit favorites</string>
|
||||
<!-- Edit favorites, title for items that are frequently used but not pinned -->
|
||||
<string name="edit_favorites_dialog_unpinned">Not pinned – frequently used</string>
|
||||
@ -450,7 +452,7 @@
|
||||
<string name="preference_location">Location</string>
|
||||
<string name="preference_imperial_units_summary">Use degrees Fahrenheit and miles per hour</string>
|
||||
<string name="preference_imperial_units">Imperial units</string>
|
||||
<string name="preference_compact_mode">Compact mode</string>
|
||||
<string name="widget_config_weather_compact">Compact mode</string>
|
||||
<string name="preference_compact_mode_summary">Hide hourly and daily forecasts</string>
|
||||
<string name="preference_category_debug">Debug</string>
|
||||
<string name="preference_category_debug_tools">Tools</string>
|
||||
@ -778,4 +780,10 @@
|
||||
<string name="preference_search_result_ordering_weight_factor_low">Stable</string>
|
||||
<string name="preference_search_result_ordering_weight_factor_default">Balanced</string>
|
||||
<string name="preference_search_result_ordering_weight_factor_high">Variable</string>
|
||||
<string name="widget_config_appwidget_height">Height</string>
|
||||
<string name="widget_config_appwidget_borderless">Borderless</string>
|
||||
<string name="widget_config_appwidget_configure">Configure widget</string>
|
||||
<string name="widget_config_weather_integration_settings">Weather integration settings</string>
|
||||
<string name="app_widget_loading_failed">App widget failed to load.</string>
|
||||
<string name="widget_config_music_integration_settings">Media control integration settings</string>
|
||||
</resources>
|
||||
@ -92,7 +92,7 @@ message Settings {
|
||||
}
|
||||
WeatherProvider provider = 1;
|
||||
bool imperial_units = 2;
|
||||
bool compact_mode = 3;
|
||||
reserved 3;
|
||||
}
|
||||
WeatherSettings weather = 5;
|
||||
|
||||
|
||||
@ -15,16 +15,13 @@ import java.util.UUID
|
||||
data class AppWidgetConfig(
|
||||
val widgetId: Int,
|
||||
val height: Int,
|
||||
val borderless: Boolean = false,
|
||||
)
|
||||
|
||||
data class AppWidget(
|
||||
override val id: UUID,
|
||||
val config: AppWidgetConfig,
|
||||
val widgetProviderInfo: AppWidgetProviderInfo
|
||||
) : Widget() {
|
||||
override fun loadLabel(context: Context): String {
|
||||
return widgetProviderInfo.loadLabel(context.packageManager)
|
||||
}
|
||||
|
||||
override fun toDatabaseEntity(): PartialWidgetEntity {
|
||||
return PartialWidgetEntity(
|
||||
@ -34,22 +31,6 @@ data class AppWidget(
|
||||
)
|
||||
}
|
||||
|
||||
override val isConfigurable: Boolean = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
widgetProviderInfo.widgetFeatures and AppWidgetProviderInfo.WIDGET_FEATURE_RECONFIGURABLE != 0
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
||||
override fun configure(context: Activity, appWidgetHost: AppWidgetHost) {
|
||||
appWidgetHost.startAppWidgetConfigureActivityForResult(
|
||||
context,
|
||||
config.widgetId,
|
||||
0,
|
||||
0,
|
||||
null
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val Type = "app"
|
||||
}
|
||||
|
||||
@ -8,6 +8,8 @@ import android.content.Intent
|
||||
import de.mm20.launcher2.database.entities.PartialWidgetEntity
|
||||
import de.mm20.launcher2.ktx.tryStartActivity
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.util.UUID
|
||||
|
||||
@Serializable
|
||||
@ -19,33 +21,14 @@ data class CalendarWidget(
|
||||
override val id: UUID,
|
||||
val config: CalendarWidgetConfig = CalendarWidgetConfig(),
|
||||
) : Widget() {
|
||||
override fun loadLabel(context: Context): String {
|
||||
return context.getString(R.string.widget_name_calendar)
|
||||
}
|
||||
|
||||
override fun toDatabaseEntity(): PartialWidgetEntity {
|
||||
return PartialWidgetEntity(
|
||||
id = id,
|
||||
type = Type,
|
||||
config = null,
|
||||
config = Json.encodeToString(config),
|
||||
)
|
||||
}
|
||||
|
||||
override val isConfigurable: Boolean = true
|
||||
|
||||
override fun configure(context: Activity, appWidgetHost: AppWidgetHost) {
|
||||
val intent = Intent()
|
||||
intent.component = ComponentName(
|
||||
context.getPackageName(),
|
||||
"de.mm20.launcher2.ui.settings.SettingsActivity"
|
||||
)
|
||||
intent.putExtra(
|
||||
"de.mm20.launcher2.settings.ROUTE",
|
||||
"settings/widgets/calendar"
|
||||
)
|
||||
context.tryStartActivity(intent)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val Type = "calendar"
|
||||
}
|
||||
|
||||
@ -0,0 +1,30 @@
|
||||
package de.mm20.launcher2.widgets
|
||||
|
||||
import de.mm20.launcher2.database.entities.PartialWidgetEntity
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.util.UUID
|
||||
|
||||
@Serializable
|
||||
data class FavoritesWidgetConfig(
|
||||
val editButton: Boolean = true,
|
||||
)
|
||||
|
||||
data class FavoritesWidget(
|
||||
override val id: UUID,
|
||||
val config: FavoritesWidgetConfig = FavoritesWidgetConfig(),
|
||||
) : Widget() {
|
||||
|
||||
override fun toDatabaseEntity(): PartialWidgetEntity {
|
||||
return PartialWidgetEntity(
|
||||
id = id,
|
||||
type = Type,
|
||||
config = Json.encodeToString(config),
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val Type = "favorites"
|
||||
}
|
||||
}
|
||||
@ -1,8 +1,7 @@
|
||||
package de.mm20.launcher2.widgets
|
||||
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.dsl.module
|
||||
|
||||
val widgetsModule = module {
|
||||
single<WidgetRepository> { WidgetRepositoryImpl(androidContext(), get()) }
|
||||
single<WidgetRepository> { WidgetRepositoryImpl(get()) }
|
||||
}
|
||||
@ -22,9 +22,6 @@ data class WeatherWidget(
|
||||
override val id: UUID,
|
||||
val config: WeatherWidgetConfig = WeatherWidgetConfig(),
|
||||
) : Widget() {
|
||||
override fun loadLabel(context: Context): String {
|
||||
return context.getString(R.string.widget_name_weather)
|
||||
}
|
||||
|
||||
override fun toDatabaseEntity(): PartialWidgetEntity {
|
||||
return PartialWidgetEntity(
|
||||
@ -34,21 +31,6 @@ data class WeatherWidget(
|
||||
)
|
||||
}
|
||||
|
||||
override val isConfigurable: Boolean = true
|
||||
|
||||
override fun configure(context: Activity, appWidgetHost: AppWidgetHost) {
|
||||
val intent = Intent()
|
||||
intent.component = ComponentName(
|
||||
context.getPackageName(),
|
||||
"de.mm20.launcher2.ui.settings.SettingsActivity"
|
||||
)
|
||||
intent.putExtra(
|
||||
"de.mm20.launcher2.settings.ROUTE",
|
||||
"settings/widgets/weather"
|
||||
)
|
||||
context.tryStartActivity(intent)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val Type = "weather"
|
||||
}
|
||||
|
||||
@ -1,23 +1,15 @@
|
||||
package de.mm20.launcher2.widgets
|
||||
|
||||
import android.app.Activity
|
||||
import android.appwidget.AppWidgetHost
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import de.mm20.launcher2.database.entities.PartialWidgetEntity
|
||||
import de.mm20.launcher2.database.entities.WidgetEntity
|
||||
import de.mm20.launcher2.ktx.decodeFromStringOrNull
|
||||
import de.mm20.launcher2.ktx.tryStartActivity
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.util.UUID
|
||||
|
||||
sealed class Widget {
|
||||
|
||||
abstract val id: UUID
|
||||
abstract fun loadLabel(context: Context): String
|
||||
fun toDatabaseEntity(position: Int, parentId: UUID? = null): WidgetEntity {
|
||||
internal fun toDatabaseEntity(position: Int, parentId: UUID? = null): WidgetEntity {
|
||||
return toDatabaseEntity().let {
|
||||
WidgetEntity(
|
||||
id = it.id,
|
||||
@ -31,11 +23,8 @@ sealed class Widget {
|
||||
|
||||
abstract fun toDatabaseEntity(): PartialWidgetEntity
|
||||
|
||||
open val isConfigurable: Boolean = false
|
||||
open fun configure(context: Activity, appWidgetHost: AppWidgetHost) {}
|
||||
|
||||
companion object {
|
||||
fun fromDatabaseEntity(context: Context, entity: WidgetEntity): Widget? {
|
||||
fun fromDatabaseEntity(entity: WidgetEntity): Widget? {
|
||||
return when (entity.type) {
|
||||
WeatherWidget.Type -> {
|
||||
val config: WeatherWidgetConfig =
|
||||
@ -43,7 +32,6 @@ sealed class Widget {
|
||||
?: WeatherWidgetConfig()
|
||||
WeatherWidget(entity.id, config)
|
||||
}
|
||||
|
||||
MusicWidget.Type -> MusicWidget(entity.id)
|
||||
CalendarWidget.Type -> {
|
||||
val config: CalendarWidgetConfig =
|
||||
@ -51,8 +39,12 @@ sealed class Widget {
|
||||
?: CalendarWidgetConfig()
|
||||
CalendarWidget(entity.id, config)
|
||||
}
|
||||
|
||||
FavoritesWidget.Type -> FavoritesWidget(entity.id)
|
||||
FavoritesWidget.Type -> {
|
||||
val config: FavoritesWidgetConfig =
|
||||
Json.decodeFromStringOrNull(entity.config?.takeIf { it.isNotBlank() })
|
||||
?: FavoritesWidgetConfig()
|
||||
FavoritesWidget(entity.id, config)
|
||||
}
|
||||
AppWidget.Type -> {
|
||||
val config: AppWidgetConfig =
|
||||
Json.decodeFromStringOrNull(entity.config?.takeIf { it.isNotBlank() })
|
||||
@ -60,8 +52,6 @@ sealed class Widget {
|
||||
AppWidget(
|
||||
entity.id,
|
||||
config,
|
||||
widgetProviderInfo = AppWidgetManager.getInstance(context)
|
||||
.getAppWidgetInfo(config.widgetId)
|
||||
)
|
||||
}
|
||||
|
||||
@ -75,10 +65,6 @@ sealed class Widget {
|
||||
data class MusicWidget(
|
||||
override val id: UUID,
|
||||
) : Widget() {
|
||||
override fun loadLabel(context: Context): String {
|
||||
return context.getString(R.string.widget_name_music)
|
||||
}
|
||||
|
||||
override fun toDatabaseEntity(): PartialWidgetEntity {
|
||||
return PartialWidgetEntity(
|
||||
id = id,
|
||||
@ -87,62 +73,12 @@ data class MusicWidget(
|
||||
)
|
||||
}
|
||||
|
||||
override val isConfigurable: Boolean = true
|
||||
|
||||
override fun configure(context: Activity, appWidgetHost: AppWidgetHost) {
|
||||
val intent = Intent()
|
||||
intent.component = ComponentName(
|
||||
context.getPackageName(),
|
||||
"de.mm20.launcher2.ui.settings.SettingsActivity"
|
||||
)
|
||||
intent.putExtra(
|
||||
"de.mm20.launcher2.settings.ROUTE",
|
||||
"settings/widgets/music"
|
||||
)
|
||||
context.tryStartActivity(intent)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val Type = "music"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
data class FavoritesWidget(
|
||||
override val id: UUID,
|
||||
) : Widget() {
|
||||
override fun loadLabel(context: Context): String {
|
||||
return context.getString(R.string.widget_name_favorites)
|
||||
}
|
||||
|
||||
override fun toDatabaseEntity(): PartialWidgetEntity {
|
||||
return PartialWidgetEntity(
|
||||
id = id,
|
||||
type = Type,
|
||||
config = null,
|
||||
)
|
||||
}
|
||||
|
||||
override val isConfigurable: Boolean = true
|
||||
|
||||
override fun configure(context: Activity, appWidgetHost: AppWidgetHost) {
|
||||
val intent = Intent()
|
||||
intent.component = ComponentName(
|
||||
context.getPackageName(),
|
||||
"de.mm20.launcher2.ui.settings.SettingsActivity"
|
||||
)
|
||||
intent.putExtra(
|
||||
"de.mm20.launcher2.settings.ROUTE",
|
||||
"settings/favorites"
|
||||
)
|
||||
context.tryStartActivity(intent)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val Type = "favorites"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
enum class WidgetType(val value: String) {
|
||||
INTERNAL("internal"),
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
package de.mm20.launcher2.widgets
|
||||
|
||||
import android.content.Context
|
||||
import androidx.room.withTransaction
|
||||
import de.mm20.launcher2.crashreporter.CrashReporter
|
||||
import de.mm20.launcher2.database.AppDatabase
|
||||
@ -28,7 +27,6 @@ interface WidgetRepository {
|
||||
}
|
||||
|
||||
internal class WidgetRepositoryImpl(
|
||||
private val context: Context,
|
||||
private val database: AppDatabase,
|
||||
) : WidgetRepository {
|
||||
|
||||
@ -40,7 +38,7 @@ internal class WidgetRepositoryImpl(
|
||||
} else {
|
||||
dao.queryByParent(parent, limit, offset)
|
||||
}.map {
|
||||
it.mapNotNull { Widget.fromDatabaseEntity(context, it) }
|
||||
it.mapNotNull { Widget.fromDatabaseEntity(it) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user