Date widget: 1×1 weekday and day number on a cookie shape, opens today (#376)

This commit is contained in:
2026-10-23 18:51:49 +02:00
parent 85d4dc535c
commit cfcba283f1
8 changed files with 208 additions and 2 deletions
+12
View File
@@ -364,6 +364,18 @@
android:resource="@xml/appwidget_info_quick_add" />
</receiver>
<receiver
android:name=".widget.date.DateWidgetReceiver"
android:label="@string/widget_date_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_date" />
</receiver>
<!-- Keeps both widgets fresh: the calendar provider broadcasts
PROVIDER_CHANGED on any data change (external sync; the app's own
writes redraw directly). The day boundary arrives as the app's own
@@ -12,6 +12,8 @@ import de.jeanlucmakiola.calendula.widget.month.MonthWidget
import de.jeanlucmakiola.calendula.widget.month.MonthWidgetReceiver
import de.jeanlucmakiola.calendula.widget.quickadd.QuickAddWidget
import de.jeanlucmakiola.calendula.widget.quickadd.QuickAddWidgetReceiver
import de.jeanlucmakiola.calendula.widget.date.DateWidget
import de.jeanlucmakiola.calendula.widget.date.DateWidgetReceiver
/** A content option a widget's config screen offers, besides background and theme. */
enum class WidgetOption { RANGE, TEXT_SIZE, PAST_DISPLAY, SHOW_TODAY, CALENDARS }
@@ -45,12 +47,19 @@ enum class WidgetKind(
emptySet(),
DpSize(76.dp, 76.dp),
),
DATE(
DateWidgetReceiver::class.java,
R.string.widget_date_label,
emptySet(),
DpSize(76.dp, 76.dp),
),
;
fun widget(): GlanceAppWidget = when (this) {
AGENDA -> AgendaWidget()
MONTH -> MonthWidget()
QUICK_ADD -> QuickAddWidget()
DATE -> DateWidget()
}
/** Whether this kind stores [AgendaWidgetOptions] of its own. */
@@ -49,6 +49,8 @@ import de.jeanlucmakiola.calendula.widget.agenda.AgendaWidgetContent
import de.jeanlucmakiola.calendula.widget.agenda.metricsFor
import de.jeanlucmakiola.calendula.widget.month.MonthWidgetContent
import de.jeanlucmakiola.calendula.widget.quickadd.QuickAddWidgetContent
import de.jeanlucmakiola.calendula.widget.date.DateWidgetContent
import de.jeanlucmakiola.calendula.widget.WidgetKind
import de.jeanlucmakiola.floret.components.GroupedChoiceRow
import de.jeanlucmakiola.floret.components.GroupedHint
import de.jeanlucmakiola.floret.components.GroupedRow
@@ -147,8 +149,11 @@ internal fun WidgetConfigScreen(
AgendaWidgetContent(preview.data, state.appearance, state.appTheme, preview = true)
is WidgetPreviewData.Month ->
MonthWidgetContent(preview.source, preview.index, state.appearance, state.appTheme)
is WidgetPreviewData.Today ->
is WidgetPreviewData.Today -> if (state.kind == WidgetKind.DATE) {
DateWidgetContent(preview.today, state.appearance, state.appTheme)
} else {
QuickAddWidgetContent(preview.today, state.appearance, state.appTheme)
}
}
}
}
@@ -228,7 +228,7 @@ class WidgetConfigViewModel @Inject constructor(
val source = context.loadThemedMonthSource(monthWindowCenter(index), draft.hiddenCalendars)
WidgetPreviewData.Month(source.content, index)
}
WidgetKind.QUICK_ADD -> WidgetPreviewData.Today(today(systemZone()))
WidgetKind.QUICK_ADD, WidgetKind.DATE -> WidgetPreviewData.Today(today(systemZone()))
}
}
@@ -0,0 +1,122 @@
package de.jeanlucmakiola.calendula.widget.date
import android.content.Context
import androidx.compose.runtime.Composable
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.datastore.preferences.core.Preferences
import androidx.glance.GlanceId
import androidx.glance.GlanceModifier
import androidx.glance.GlanceTheme
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.Column
import androidx.glance.layout.Spacer
import androidx.glance.layout.fillMaxSize
import androidx.glance.layout.height
import androidx.glance.layout.padding
import androidx.glance.semantics.contentDescription
import androidx.glance.semantics.semantics
import androidx.glance.state.PreferencesGlanceStateDefinition
import androidx.glance.text.FontWeight
import androidx.glance.text.Text
import androidx.glance.text.TextAlign
import androidx.glance.text.TextStyle
import de.jeanlucmakiola.calendula.MainActivity
import de.jeanlucmakiola.calendula.ui.common.CalendarView
import de.jeanlucmakiola.calendula.widget.CalendulaWidgetReceiver
import de.jeanlucmakiola.calendula.widget.CalendulaWidgetRoot
import de.jeanlucmakiola.calendula.widget.WidgetAppearance
import de.jeanlucmakiola.calendula.widget.loadToday
import de.jeanlucmakiola.calendula.widget.widgetAppearance
import de.jeanlucmakiola.calendula.widget.widgetLocale
import de.jeanlucmakiola.floret.glance.LocalWidgetBackground
import de.jeanlucmakiola.floret.glance.WidgetBackground
import de.jeanlucmakiola.floret.glance.WidgetData
import de.jeanlucmakiola.floret.glance.WidgetShape
import de.jeanlucmakiola.floret.glance.WidgetShapedBox
import de.jeanlucmakiola.floret.glance.currentRefreshStamp
import de.jeanlucmakiola.floret.glance.refreshStamp
import de.jeanlucmakiola.floret.glance.wallpaperBacking
import de.jeanlucmakiola.floret.locale.localizedDateFormatter
import de.jeanlucmakiola.floret.prefs.ThemeMode
import kotlinx.datetime.LocalDate
import kotlinx.datetime.toJavaDayOfWeek
import kotlinx.datetime.toJavaLocalDate
import java.time.format.TextStyle as JavaTextStyle
/** 1×1 date (#376): today's weekday above its number on an expressive shape; opens today. */
class DateWidget : GlanceAppWidget() {
override val stateDefinition = PreferencesGlanceStateDefinition
override val sizeMode = SizeMode.Exact
override suspend fun provideGlance(context: Context, id: GlanceId) {
val widgetData = WidgetData(context.loadToday(), refreshStamp(context, id))
provideContent {
val loaded = widgetData.current(currentRefreshStamp()) { context.loadToday() }
DateWidgetContent(loaded.content, currentState<Preferences>().widgetAppearance, loaded.appTheme)
}
}
}
class DateWidgetReceiver : CalendulaWidgetReceiver() {
override val glanceAppWidget: GlanceAppWidget = DateWidget()
}
private val WeekdayLine = 18.dp
@Composable
internal fun DateWidgetContent(today: LocalDate, appearance: WidgetAppearance, appTheme: ThemeMode) {
CalendulaWidgetRoot(appearance, appTheme) {
val context = LocalContext.current
val size = LocalSize.current
val fontScale = context.resources.configuration.fontScale
val side = (minOf(size.width, size.height - WeekdayLine * fontScale) - 8.dp).coerceIn(32.dp, 120.dp)
val label = localizedDateFormatter(widgetLocale(), "EEEEdMMMM").format(today.toJavaLocalDate())
val weekday = TextStyle(
color = GlanceTheme.colors.onSurface,
fontSize = 12.sp,
fontWeight = FontWeight.Medium,
textAlign = TextAlign.Center,
)
Column(
modifier = GlanceModifier
.fillMaxSize()
.clickable(actionStartActivity(MainActivity.openDateIntent(context, today, CalendarView.Day)))
.semantics { contentDescription = label },
horizontalAlignment = Alignment.CenterHorizontally,
verticalAlignment = Alignment.CenterVertically,
) {
val name = today.dayOfWeek.toJavaDayOfWeek().getDisplayName(JavaTextStyle.SHORT, widgetLocale())
val backing = if (LocalWidgetBackground.current == WidgetBackground.TRANSPARENT) {
GlanceModifier.wallpaperBacking(radius = 8.dp).padding(horizontal = 6.dp)
} else {
GlanceModifier
}
Text(text = name.uppercase(widgetLocale()), maxLines = 1, style = weekday, modifier = backing)
Spacer(GlanceModifier.height(2.dp))
WidgetShapedBox(shape = WidgetShape.COOKIE, color = GlanceTheme.colors.primary, size = side) {
Text(
text = today.day.toString(),
maxLines = 1,
style = TextStyle(
color = GlanceTheme.colors.onPrimary,
// Sized to the shape, so held against the font scale.
fontSize = (side.value * 0.45f / fontScale).sp,
fontWeight = FontWeight.Bold,
textAlign = TextAlign.Center,
),
)
}
}
}
}
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Static mock shown in the widget picker (android:previewLayout). -->
<LinearLayout 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:gravity="center"
android:orientation="vertical"
android:padding="4dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="WED"
android:textColor="@color/widget_preview_on_surface"
android:textSize="11sp"
android:textStyle="bold"
tools:ignore="HardcodedText" />
<FrameLayout
android:layout_width="48dp"
android:layout_height="48dp">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:importantForAccessibility="no"
android:src="@drawable/floret_widget_shape_cookie"
android:tint="@color/widget_preview_primary"
tools:ignore="UseAppTint" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="7"
android:textColor="@color/widget_preview_on_primary"
android:textSize="22sp"
android:textStyle="bold"
tools:ignore="HardcodedText" />
</FrameLayout>
</LinearLayout>
+1
View File
@@ -436,6 +436,7 @@
<string name="widget_agenda_label">Calendula agenda</string>
<string name="widget_month_label">Calendula month</string>
<string name="widget_quick_add_label">Calendula quick add</string>
<string name="widget_date_label">Calendula date</string>
<string name="widget_refresh">Refresh</string>
<string name="widget_new_event">New event</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_date_label"
android:updatePeriodMillis="1800000"
android:previewLayout="@layout/widget_preview_date"
android:initialLayout="@layout/glance_default_loading_layout" />