diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index acc935c..06eb245 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -388,6 +388,18 @@
android:resource="@xml/appwidget_info_week_strip" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values-night-v31/widget_preview_colors.xml b/app/src/main/res/values-night-v31/widget_preview_colors.xml
index 26162c8..323de5f 100644
--- a/app/src/main/res/values-night-v31/widget_preview_colors.xml
+++ b/app/src/main/res/values-night-v31/widget_preview_colors.xml
@@ -5,4 +5,5 @@
@android:color/system_neutral2_200
@android:color/system_accent1_200
@android:color/system_accent1_800
+ @android:color/system_accent2_800
diff --git a/app/src/main/res/values-night/widget_preview_colors.xml b/app/src/main/res/values-night/widget_preview_colors.xml
index ca6b3f7..233d427 100644
--- a/app/src/main/res/values-night/widget_preview_colors.xml
+++ b/app/src/main/res/values-night/widget_preview_colors.xml
@@ -5,4 +5,5 @@
#A8ADB2
#A3CBE2
#003348
+ #2D3236
diff --git a/app/src/main/res/values-v31/widget_preview_colors.xml b/app/src/main/res/values-v31/widget_preview_colors.xml
index 0eb2ece..902c1e0 100644
--- a/app/src/main/res/values-v31/widget_preview_colors.xml
+++ b/app/src/main/res/values-v31/widget_preview_colors.xml
@@ -5,4 +5,5 @@
@android:color/system_neutral2_700
@android:color/system_accent1_600
@android:color/system_accent1_0
+ @android:color/system_accent2_100
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index cd18448..c1672e4 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -443,6 +443,8 @@
- %1$s, %2$d event
- %1$s, %2$d events
+ Calendula upcoming
+ No upcoming events
Refresh
New event
Open Calendula to grant calendar access
diff --git a/app/src/main/res/values/widget_preview_colors.xml b/app/src/main/res/values/widget_preview_colors.xml
index 98d328b..69ddbb1 100644
--- a/app/src/main/res/values/widget_preview_colors.xml
+++ b/app/src/main/res/values/widget_preview_colors.xml
@@ -8,4 +8,5 @@
#6E7479
#3B5364
#FFFFFF
+ #E1E6EB
diff --git a/app/src/main/res/values/widget_preview_styles.xml b/app/src/main/res/values/widget_preview_styles.xml
index 29c6ff6..d4d081b 100644
--- a/app/src/main/res/values/widget_preview_styles.xml
+++ b/app/src/main/res/values/widget_preview_styles.xml
@@ -10,4 +10,14 @@
- @color/widget_preview_on_surface
- 12sp
+
diff --git a/app/src/main/res/xml/appwidget_info_upcoming.xml b/app/src/main/res/xml/appwidget_info_upcoming.xml
new file mode 100644
index 0000000..e80cddb
--- /dev/null
+++ b/app/src/main/res/xml/appwidget_info_upcoming.xml
@@ -0,0 +1,16 @@
+
+
diff --git a/app/src/test/java/de/jeanlucmakiola/calendula/widget/upcoming/UpcomingEventsTest.kt b/app/src/test/java/de/jeanlucmakiola/calendula/widget/upcoming/UpcomingEventsTest.kt
new file mode 100644
index 0000000..69dd7d2
--- /dev/null
+++ b/app/src/test/java/de/jeanlucmakiola/calendula/widget/upcoming/UpcomingEventsTest.kt
@@ -0,0 +1,51 @@
+package de.jeanlucmakiola.calendula.widget.upcoming
+
+import com.google.common.truth.Truth.assertThat
+import de.jeanlucmakiola.calendula.data.prefs.PastEventDisplay
+import de.jeanlucmakiola.calendula.domain.EventInstance
+import de.jeanlucmakiola.calendula.ui.agenda.AgendaDay
+import de.jeanlucmakiola.calendula.ui.agenda.AgendaRange
+import de.jeanlucmakiola.calendula.widget.AgendaWidgetOptions
+import de.jeanlucmakiola.calendula.widget.DaysWidgetData
+import de.jeanlucmakiola.calendula.widget.WidgetSize
+import kotlinx.datetime.DayOfWeek
+import kotlinx.datetime.LocalDate
+import org.junit.jupiter.api.Test
+import kotlin.time.Duration.Companion.hours
+import kotlin.time.Instant
+
+class UpcomingEventsTest {
+
+ private val now = Instant.parse("2026-10-21T12:00:00Z")
+
+ private fun event(id: Long, start: Instant, end: Instant) = EventInstance(
+ instanceId = id, eventId = id, calendarId = 1, title = "e$id",
+ start = start, end = end, isAllDay = false, color = 0, location = null,
+ )
+
+ private fun data(vararg days: AgendaDay) = DaysWidgetData.Ready(
+ today = LocalDate(2026, 10, 21),
+ firstDay = LocalDate(2026, 10, 21),
+ lastDay = LocalDate(2026, 10, 27),
+ days = days.toList(),
+ weekStart = DayOfWeek.MONDAY,
+ is24Hour = true,
+ soften = true,
+ options = AgendaWidgetOptions(AgendaRange.Week, WidgetSize.SMALL, true, PastEventDisplay.SHOW),
+ now = now,
+ )
+
+ @Test
+ fun `ended events drop out and a multi-day one lists once, on its first day`() {
+ val ended = event(1, now - 3.hours, now - 1.hours)
+ val running = event(2, now - 1.hours, now + 30.hours)
+ val later = event(3, now + 2.hours, now + 3.hours)
+ val today = AgendaDay(LocalDate(2026, 10, 21), listOf(ended, running, later))
+ val tomorrow = AgendaDay(LocalDate(2026, 10, 22), listOf(running))
+
+ val upcoming = upcomingEvents(data(today, tomorrow))
+
+ assertThat(upcoming.map { it.second.instanceId }).containsExactly(2L, 3L).inOrder()
+ assertThat(upcoming.first().first).isEqualTo(LocalDate(2026, 10, 21))
+ }
+}