Quick add widget: 1×1 shaped + opening the editor on today (#435)
This commit is contained in:
@@ -352,6 +352,18 @@
|
|||||||
android:resource="@xml/appwidget_info_month" />
|
android:resource="@xml/appwidget_info_month" />
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
|
<receiver
|
||||||
|
android:name=".widget.quickadd.QuickAddWidgetReceiver"
|
||||||
|
android:label="@string/widget_quick_add_label"
|
||||||
|
android:exported="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||||
|
</intent-filter>
|
||||||
|
<meta-data
|
||||||
|
android:name="android.appwidget.provider"
|
||||||
|
android:resource="@xml/appwidget_info_quick_add" />
|
||||||
|
</receiver>
|
||||||
|
|
||||||
<!-- Keeps both widgets fresh: the calendar provider broadcasts
|
<!-- Keeps both widgets fresh: the calendar provider broadcasts
|
||||||
PROVIDER_CHANGED on any data change (external sync; the app's own
|
PROVIDER_CHANGED on any data change (external sync; the app's own
|
||||||
writes redraw directly). The day boundary arrives as the app's own
|
writes redraw directly). The day boundary arrives as the app's own
|
||||||
|
|||||||
@@ -256,3 +256,7 @@ private suspend fun Context.queryMonthSource(
|
|||||||
months = months,
|
months = months,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Today and the app theme, for a widget that draws no events. */
|
||||||
|
internal suspend fun Context.loadToday(): Themed<LocalDate> =
|
||||||
|
Themed(today(systemZone()), widgetEntryPoint().settingsPrefs().widgetSettings.first().themeMode)
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import de.jeanlucmakiola.calendula.widget.agenda.AgendaWidget
|
|||||||
import de.jeanlucmakiola.calendula.widget.agenda.AgendaWidgetReceiver
|
import de.jeanlucmakiola.calendula.widget.agenda.AgendaWidgetReceiver
|
||||||
import de.jeanlucmakiola.calendula.widget.month.MonthWidget
|
import de.jeanlucmakiola.calendula.widget.month.MonthWidget
|
||||||
import de.jeanlucmakiola.calendula.widget.month.MonthWidgetReceiver
|
import de.jeanlucmakiola.calendula.widget.month.MonthWidgetReceiver
|
||||||
|
import de.jeanlucmakiola.calendula.widget.quickadd.QuickAddWidget
|
||||||
|
import de.jeanlucmakiola.calendula.widget.quickadd.QuickAddWidgetReceiver
|
||||||
|
|
||||||
/** A content option a widget's config screen offers, besides background and theme. */
|
/** A content option a widget's config screen offers, besides background and theme. */
|
||||||
enum class WidgetOption { RANGE, TEXT_SIZE, PAST_DISPLAY, SHOW_TODAY, CALENDARS }
|
enum class WidgetOption { RANGE, TEXT_SIZE, PAST_DISPLAY, SHOW_TODAY, CALENDARS }
|
||||||
@@ -37,11 +39,18 @@ enum class WidgetKind(
|
|||||||
setOf(WidgetOption.CALENDARS),
|
setOf(WidgetOption.CALENDARS),
|
||||||
DpSize(320.dp, 340.dp),
|
DpSize(320.dp, 340.dp),
|
||||||
),
|
),
|
||||||
|
QUICK_ADD(
|
||||||
|
QuickAddWidgetReceiver::class.java,
|
||||||
|
R.string.widget_quick_add_label,
|
||||||
|
emptySet(),
|
||||||
|
DpSize(76.dp, 76.dp),
|
||||||
|
),
|
||||||
;
|
;
|
||||||
|
|
||||||
fun widget(): GlanceAppWidget = when (this) {
|
fun widget(): GlanceAppWidget = when (this) {
|
||||||
AGENDA -> AgendaWidget()
|
AGENDA -> AgendaWidget()
|
||||||
MONTH -> MonthWidget()
|
MONTH -> MonthWidget()
|
||||||
|
QUICK_ADD -> QuickAddWidget()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Whether this kind stores [AgendaWidgetOptions] of its own. */
|
/** Whether this kind stores [AgendaWidgetOptions] of its own. */
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ import de.jeanlucmakiola.calendula.widget.WidgetTheme
|
|||||||
import de.jeanlucmakiola.calendula.widget.agenda.AgendaWidgetContent
|
import de.jeanlucmakiola.calendula.widget.agenda.AgendaWidgetContent
|
||||||
import de.jeanlucmakiola.calendula.widget.agenda.metricsFor
|
import de.jeanlucmakiola.calendula.widget.agenda.metricsFor
|
||||||
import de.jeanlucmakiola.calendula.widget.month.MonthWidgetContent
|
import de.jeanlucmakiola.calendula.widget.month.MonthWidgetContent
|
||||||
|
import de.jeanlucmakiola.calendula.widget.quickadd.QuickAddWidgetContent
|
||||||
import de.jeanlucmakiola.floret.components.GroupedChoiceRow
|
import de.jeanlucmakiola.floret.components.GroupedChoiceRow
|
||||||
import de.jeanlucmakiola.floret.components.GroupedHint
|
import de.jeanlucmakiola.floret.components.GroupedHint
|
||||||
import de.jeanlucmakiola.floret.components.GroupedRow
|
import de.jeanlucmakiola.floret.components.GroupedRow
|
||||||
@@ -146,6 +147,8 @@ internal fun WidgetConfigScreen(
|
|||||||
AgendaWidgetContent(preview.data, state.appearance, state.appTheme, preview = true)
|
AgendaWidgetContent(preview.data, state.appearance, state.appTheme, preview = true)
|
||||||
is WidgetPreviewData.Month ->
|
is WidgetPreviewData.Month ->
|
||||||
MonthWidgetContent(preview.source, preview.index, state.appearance, state.appTheme)
|
MonthWidgetContent(preview.source, preview.index, state.appearance, state.appTheme)
|
||||||
|
is WidgetPreviewData.Today ->
|
||||||
|
QuickAddWidgetContent(preview.today, state.appearance, state.appTheme)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,12 +50,14 @@ import kotlinx.coroutines.flow.first
|
|||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.datetime.DayOfWeek
|
import kotlinx.datetime.DayOfWeek
|
||||||
|
import kotlinx.datetime.LocalDate
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
/** What the preview draws, loaded with the draft's options. */
|
/** What the preview draws, loaded with the draft's options. */
|
||||||
sealed interface WidgetPreviewData {
|
sealed interface WidgetPreviewData {
|
||||||
data class Agenda(val data: AgendaWidgetData) : WidgetPreviewData
|
data class Agenda(val data: AgendaWidgetData) : WidgetPreviewData
|
||||||
data class Month(val source: MonthWidgetSource, val index: Int) : WidgetPreviewData
|
data class Month(val source: MonthWidgetSource, val index: Int) : WidgetPreviewData
|
||||||
|
data class Today(val today: LocalDate) : WidgetPreviewData
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The options being edited, applied to the preview as they change and written on save. */
|
/** The options being edited, applied to the preview as they change and written on save. */
|
||||||
@@ -226,6 +228,7 @@ class WidgetConfigViewModel @Inject constructor(
|
|||||||
val source = context.loadThemedMonthSource(monthWindowCenter(index), draft.hiddenCalendars)
|
val source = context.loadThemedMonthSource(monthWindowCenter(index), draft.hiddenCalendars)
|
||||||
WidgetPreviewData.Month(source.content, index)
|
WidgetPreviewData.Month(source.content, index)
|
||||||
}
|
}
|
||||||
|
WidgetKind.QUICK_ADD -> WidgetPreviewData.Today(today(systemZone()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
package de.jeanlucmakiola.calendula.widget.quickadd
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.datastore.preferences.core.Preferences
|
||||||
|
import androidx.glance.GlanceId
|
||||||
|
import androidx.glance.GlanceModifier
|
||||||
|
import androidx.glance.GlanceTheme
|
||||||
|
import androidx.glance.ImageProvider
|
||||||
|
import androidx.glance.LocalContext
|
||||||
|
import androidx.glance.LocalSize
|
||||||
|
import androidx.glance.action.clickable
|
||||||
|
import androidx.glance.appwidget.GlanceAppWidget
|
||||||
|
import androidx.glance.appwidget.SizeMode
|
||||||
|
import androidx.glance.appwidget.action.actionStartActivity
|
||||||
|
import androidx.glance.appwidget.provideContent
|
||||||
|
import androidx.glance.currentState
|
||||||
|
import androidx.glance.layout.Alignment
|
||||||
|
import androidx.glance.layout.Box
|
||||||
|
import androidx.glance.layout.fillMaxSize
|
||||||
|
import androidx.glance.state.PreferencesGlanceStateDefinition
|
||||||
|
import de.jeanlucmakiola.calendula.MainActivity
|
||||||
|
import de.jeanlucmakiola.calendula.R
|
||||||
|
import de.jeanlucmakiola.calendula.widget.CalendulaWidgetRoot
|
||||||
|
import de.jeanlucmakiola.calendula.widget.WidgetAppearance
|
||||||
|
import de.jeanlucmakiola.calendula.widget.CalendulaWidgetReceiver
|
||||||
|
import de.jeanlucmakiola.calendula.widget.loadToday
|
||||||
|
import de.jeanlucmakiola.calendula.widget.widgetAppearance
|
||||||
|
import de.jeanlucmakiola.floret.glance.WidgetData
|
||||||
|
import de.jeanlucmakiola.floret.glance.WidgetShape
|
||||||
|
import de.jeanlucmakiola.floret.glance.WidgetShapedIconButton
|
||||||
|
import de.jeanlucmakiola.floret.glance.currentRefreshStamp
|
||||||
|
import de.jeanlucmakiola.floret.glance.refreshStamp
|
||||||
|
import de.jeanlucmakiola.floret.prefs.ThemeMode
|
||||||
|
import kotlinx.datetime.LocalDate
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1×1 quick add (#435): one shaped "+" that opens the new-event editor on
|
||||||
|
* today, with the same defaults as the app's own new-event button.
|
||||||
|
*/
|
||||||
|
class QuickAddWidget : GlanceAppWidget() {
|
||||||
|
|
||||||
|
override val stateDefinition = PreferencesGlanceStateDefinition
|
||||||
|
|
||||||
|
// Exact: the shape fills whatever square the launcher gives it.
|
||||||
|
override val sizeMode = SizeMode.Exact
|
||||||
|
|
||||||
|
override suspend fun provideGlance(context: Context, id: GlanceId) {
|
||||||
|
val widgetData = WidgetData(context.loadToday(), refreshStamp(context, id))
|
||||||
|
provideContent {
|
||||||
|
// The rollover moves the stamp, so the editor opens on the new day.
|
||||||
|
val loaded = widgetData.current(currentRefreshStamp()) { context.loadToday() }
|
||||||
|
QuickAddWidgetContent(loaded.content, currentState<Preferences>().widgetAppearance, loaded.appTheme)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class QuickAddWidgetReceiver : CalendulaWidgetReceiver() {
|
||||||
|
override val glanceAppWidget: GlanceAppWidget = QuickAddWidget()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
internal fun QuickAddWidgetContent(today: LocalDate, appearance: WidgetAppearance, appTheme: ThemeMode) {
|
||||||
|
CalendulaWidgetRoot(appearance, appTheme) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val size = LocalSize.current
|
||||||
|
val action = actionStartActivity(MainActivity.openCreateIntent(context, today))
|
||||||
|
Box(
|
||||||
|
modifier = GlanceModifier.fillMaxSize().clickable(action),
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
|
WidgetShapedIconButton(
|
||||||
|
shape = WidgetShape.SUNNY,
|
||||||
|
icon = ImageProvider(R.drawable.ic_widget_add),
|
||||||
|
contentDescription = context.getString(R.string.widget_new_event),
|
||||||
|
action = action,
|
||||||
|
color = GlanceTheme.colors.primary,
|
||||||
|
iconTint = GlanceTheme.colors.onPrimary,
|
||||||
|
size = (minOf(size.width, size.height) - 12.dp).coerceIn(36.dp, 120.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Static mock shown in the widget picker (android:previewLayout). -->
|
||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@drawable/preview_widget_bg"
|
||||||
|
android:padding="6dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
|
android:src="@drawable/floret_widget_shape_sunny"
|
||||||
|
android:tint="@color/widget_preview_primary"
|
||||||
|
tools:ignore="UseAppTint" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
|
android:src="@drawable/ic_widget_add"
|
||||||
|
android:tint="@color/widget_preview_on_primary"
|
||||||
|
tools:ignore="UseAppTint" />
|
||||||
|
</FrameLayout>
|
||||||
@@ -435,6 +435,7 @@
|
|||||||
<string name="widget_agenda_title">Upcoming</string>
|
<string name="widget_agenda_title">Upcoming</string>
|
||||||
<string name="widget_agenda_label">Calendula agenda</string>
|
<string name="widget_agenda_label">Calendula agenda</string>
|
||||||
<string name="widget_month_label">Calendula month</string>
|
<string name="widget_month_label">Calendula month</string>
|
||||||
|
<string name="widget_quick_add_label">Calendula quick add</string>
|
||||||
<string name="widget_refresh">Refresh</string>
|
<string name="widget_refresh">Refresh</string>
|
||||||
<string name="widget_new_event">New event</string>
|
<string name="widget_new_event">New event</string>
|
||||||
<string name="widget_needs_permission">Open Calendula to grant calendar access</string>
|
<string name="widget_needs_permission">Open Calendula to grant calendar access</string>
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:minWidth="40dp"
|
||||||
|
android:minHeight="40dp"
|
||||||
|
android:targetCellWidth="1"
|
||||||
|
android:targetCellHeight="1"
|
||||||
|
android:resizeMode="horizontal|vertical"
|
||||||
|
android:widgetCategory="home_screen"
|
||||||
|
android:configure="de.jeanlucmakiola.calendula.widget.config.WidgetConfigActivity"
|
||||||
|
android:widgetFeatures="reconfigurable"
|
||||||
|
android:description="@string/widget_quick_add_label"
|
||||||
|
android:updatePeriodMillis="1800000"
|
||||||
|
android:previewLayout="@layout/widget_preview_quick_add"
|
||||||
|
android:initialLayout="@layout/glance_default_loading_layout" />
|
||||||
Reference in New Issue
Block a user