diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index c1e42a6..9789b41 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -140,6 +140,18 @@
android:resource="@xml/widget_agenda_info" />
+
+
+
+
+
+
+
= PreferencesGlanceStateDefinition
+
+ override suspend fun provideGlance(context: Context, id: GlanceId) {
+ val localized = LocaleHelper.wrap(context)
+ val colors = WidgetTheme.colors(context)
+ val weekModel = WidgetDataLoader.loadAgenda(context, AgendaRange.WEEK)
+ val monthModel = WidgetDataLoader.loadAgenda(context, AgendaRange.MONTH)
+ val openCalendar = actionStartActivity(WidgetIntents.openTab(context, "calendar"))
+ provideContent {
+ val range = rangeFromPrefs(currentState())
+ val model = if (range == AgendaRange.MONTH) monthModel else weekModel
+ GlanceTheme {
+ LockAgendaContent(
+ context = localized,
+ colors = colors,
+ model = model,
+ openCalendar = openCalendar,
+ )
+ }
+ }
+ }
+
+ companion object {
+ val RangePrefKey = stringPreferencesKey("lock_agenda_range")
+ const val MaxEvents = 4
+
+ fun rangeFromPrefs(prefs: Preferences): AgendaRange =
+ if (prefs[RangePrefKey] == "month") AgendaRange.MONTH else AgendaRange.WEEK
+ }
+}
+
+@Composable
+private fun LockAgendaContent(
+ context: Context,
+ colors: WidgetColors,
+ model: AgendaWidgetModel,
+ openCalendar: androidx.glance.action.Action,
+) {
+ val events = model.events.take(LockAgendaCalendarWidget.MaxEvents)
+ Column(
+ modifier = GlanceModifier
+ .fillMaxSize()
+ .cornerRadius(16.dp)
+ .background(colors.surface)
+ .padding(horizontal = 10.dp, vertical = 8.dp)
+ .clickable(openCalendar),
+ ) {
+ Row(
+ modifier = GlanceModifier.fillMaxWidth(),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Column(modifier = GlanceModifier.defaultWeight()) {
+ Text(
+ text = model.headerTitle,
+ style = TextStyle(
+ color = colors.primary,
+ fontSize = 13.sp,
+ fontWeight = FontWeight.Bold,
+ ),
+ maxLines = 1,
+ )
+ Text(
+ text = model.subtitle,
+ style = TextStyle(
+ color = colors.onSurfaceVariant,
+ fontSize = 10.sp,
+ ),
+ maxLines = 1,
+ )
+ }
+ Text(
+ text = "W",
+ style = TextStyle(
+ color = if (model.range == AgendaRange.WEEK) colors.primary else colors.onSurfaceVariant,
+ fontSize = 10.sp,
+ fontWeight = FontWeight.Medium,
+ ),
+ )
+ Spacer(modifier = GlanceModifier.width(2.dp))
+ Switch(
+ checked = model.range == AgendaRange.MONTH,
+ onCheckedChange = actionRunCallback(),
+ colors = SwitchDefaults.switchColors(
+ checkedThumbColor = colors.onPrimary,
+ checkedTrackColor = colors.primary,
+ uncheckedThumbColor = colors.onSurfaceVariant,
+ uncheckedTrackColor = colors.primaryContainer,
+ ),
+ )
+ Spacer(modifier = GlanceModifier.width(2.dp))
+ Text(
+ text = "M",
+ style = TextStyle(
+ color = if (model.range == AgendaRange.MONTH) colors.primary else colors.onSurfaceVariant,
+ fontSize = 10.sp,
+ fontWeight = FontWeight.Medium,
+ ),
+ )
+ }
+ Spacer(modifier = GlanceModifier.height(6.dp))
+ if (events.isEmpty()) {
+ Text(
+ text = if (model.range == AgendaRange.WEEK) {
+ context.getString(app.taskdav.R.string.widget_empty_events_week)
+ } else {
+ context.getString(app.taskdav.R.string.widget_empty_events_month)
+ },
+ style = TextStyle(color = colors.onSurfaceVariant, fontSize = 11.sp),
+ maxLines = 2,
+ )
+ } else {
+ LazyColumn(modifier = GlanceModifier.fillMaxSize()) {
+ items(events, itemId = { it.id }) { event ->
+ Row(
+ modifier = GlanceModifier
+ .fillMaxWidth()
+ .padding(vertical = 2.dp),
+ ) {
+ Text(
+ text = event.timeLabel,
+ style = TextStyle(
+ color = colors.onSurfaceVariant,
+ fontSize = 10.sp,
+ fontWeight = FontWeight.Medium,
+ ),
+ maxLines = 1,
+ modifier = GlanceModifier.width(72.dp),
+ )
+ Spacer(modifier = GlanceModifier.width(4.dp))
+ Text(
+ text = event.title,
+ style = TextStyle(color = colors.onSurface, fontSize = 12.sp),
+ maxLines = 1,
+ modifier = GlanceModifier.defaultWeight(),
+ )
+ }
+ }
+ }
+ }
+ }
+}
+
+class ToggleLockAgendaRangeAction : ActionCallback {
+ override suspend fun onAction(
+ context: Context,
+ glanceId: GlanceId,
+ parameters: ActionParameters,
+ ) {
+ val toMonth = parameters[ToggleableStateKey] == true
+ val value = if (toMonth) "month" else "week"
+ Log.i("LockAgendaWidget", "Toggle range → $value")
+ updateAppWidgetState(context, glanceId) { prefs ->
+ prefs[LockAgendaCalendarWidget.RangePrefKey] = value
+ }
+ LockAgendaCalendarWidget().update(context, glanceId)
+ }
+}
+
+class LockAgendaCalendarWidgetReceiver : GlanceAppWidgetReceiver() {
+ override val glanceAppWidget: GlanceAppWidget = LockAgendaCalendarWidget()
+}
diff --git a/app/src/main/java/app/taskdav/widget/WidgetDataLoader.kt b/app/src/main/java/app/taskdav/widget/WidgetDataLoader.kt
index 440cd5a..cacd95d 100644
--- a/app/src/main/java/app/taskdav/widget/WidgetDataLoader.kt
+++ b/app/src/main/java/app/taskdav/widget/WidgetDataLoader.kt
@@ -3,6 +3,7 @@ package app.taskdav.widget
import android.content.Context
import app.taskdav.R
import app.taskdav.TaskDavApp
+import app.taskdav.data.EventEntity
import app.taskdav.data.LocaleHelper
import app.taskdav.domain.EventRecurrence
import app.taskdav.domain.TaskTreeBuilder
@@ -97,30 +98,28 @@ object WidgetDataLoader {
val loc = locale(context)
val app = context.applicationContext as TaskDavApp
val now = System.currentTimeMillis()
- val (rangeStart, rangeEnd, header, subtitle) = when (range) {
+ val (rangeEnd, header, subtitle) = when (range) {
AgendaRange.WEEK -> {
- val start = CalendarViewModel.startOfWeek(now)
- val end = start + TimeUnit.DAYS.toMillis(7)
+ val weekStart = CalendarViewModel.startOfWeek(now)
+ val end = weekStart + TimeUnit.DAYS.toMillis(7)
val endLabel = SimpleDateFormat("MMM d", loc)
.format(Date(end - TimeUnit.DAYS.toMillis(1)))
- val startLabel = SimpleDateFormat("MMM d", loc).format(Date(start))
+ val startLabel = SimpleDateFormat("MMM d", loc).format(Date(now))
AgendaBounds(
- start,
end,
strings.getString(R.string.widget_subtitle_this_week),
"$startLabel – $endLabel",
)
}
AgendaRange.MONTH -> {
- val start = CalendarViewModel.startOfMonth(now)
+ val monthStart = CalendarViewModel.startOfMonth(now)
val cal = Calendar.getInstance().apply {
- timeInMillis = start
+ timeInMillis = monthStart
add(Calendar.MONTH, 1)
}
val end = cal.timeInMillis
- val monthLabel = SimpleDateFormat("MMMM yyyy", loc).format(Date(start))
+ val monthLabel = SimpleDateFormat("MMMM yyyy", loc).format(Date(monthStart))
AgendaBounds(
- start,
end,
monthLabel,
strings.getString(R.string.widget_subtitle_this_month),
@@ -128,7 +127,9 @@ object WidgetDataLoader {
}
}
val events = app.database.events().getActive()
- val expanded = EventRecurrence.expandAll(events, rangeStart, rangeEnd)
+ // From now forward only — skip earlier days this week/month and events that already ended.
+ val expanded = EventRecurrence.expandAll(events, now, rangeEnd)
+ .filter { event -> eventStillRelevant(event, now) }
.sortedBy { it.dtStartMillis ?: Long.MAX_VALUE }
val dayFmt = SimpleDateFormat("EEE d", loc)
return AgendaWidgetModel(
@@ -154,6 +155,18 @@ object WidgetDataLoader {
)
}
+ /** True while the occurrence has not finished (ongoing or future). */
+ private fun eventStillRelevant(event: EventEntity, now: Long): Boolean {
+ val start = event.dtStartMillis ?: return false
+ val end = event.dtEndMillis?.takeIf { it > start }
+ ?: if (event.allDay) {
+ start + TimeUnit.DAYS.toMillis(1)
+ } else {
+ start + TimeUnit.HOURS.toMillis(1)
+ }
+ return end > now
+ }
+
suspend fun loadTasks(context: Context): TasksWidgetModel {
val strings = localized(context)
val app = context.applicationContext as TaskDavApp
@@ -212,7 +225,6 @@ object WidgetDataLoader {
}
private data class AgendaBounds(
- val start: Long,
val end: Long,
val header: String,
val subtitle: String,
diff --git a/app/src/main/java/app/taskdav/widget/WidgetUpdater.kt b/app/src/main/java/app/taskdav/widget/WidgetUpdater.kt
index 57b1ae9..c92e50b 100644
--- a/app/src/main/java/app/taskdav/widget/WidgetUpdater.kt
+++ b/app/src/main/java/app/taskdav/widget/WidgetUpdater.kt
@@ -25,6 +25,7 @@ object WidgetUpdater {
withContext(Dispatchers.Default) {
CalendarWidget().updateAll(appContext)
AgendaCalendarWidget().updateAll(appContext)
+ LockAgendaCalendarWidget().updateAll(appContext)
TasksWidget().updateAll(appContext)
NotesWidget().updateAll(appContext)
}
diff --git a/app/src/main/res/layout/widget_lock_agenda_preview.xml b/app/src/main/res/layout/widget_lock_agenda_preview.xml
new file mode 100644
index 0000000..668b1a5
--- /dev/null
+++ b/app/src/main/res/layout/widget_lock_agenda_preview.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
index d478671..887d164 100644
--- a/app/src/main/res/values-es/strings.xml
+++ b/app/src/main/res/values-es/strings.xml
@@ -80,10 +80,12 @@
TaskDav
Eventos del calendario de hoy
Eventos de esta semana o mes
+ Agenda compacta de semana o mes para la pantalla de bloqueo
Tareas abiertas con fecha de vencimiento
Notas recientes
TaskDav Hoy
TaskDav Semana / Mes
+ TaskDav Agenda bloqueo
TaskDav Tareas
TaskDav Notas
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index 531b14e..421678f 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -80,10 +80,12 @@
TaskDav
Eventi del calendario di oggi
Eventi di questa settimana o mese
+ Agenda compatta settimana o mese per la schermata di blocco
Attività aperte con scadenza
Note recenti
TaskDav Oggi
TaskDav Settimana / Mese
+ TaskDav Agenda blocco
TaskDav Attività
TaskDav Note
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 9c24130..15aabf9 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -80,10 +80,12 @@
TaskDav
Today\'s calendar events
This week or month\'s events
+ Compact week or month agenda for the lock screen
Open tasks with due dates
Recent notes
TaskDav Today
TaskDav Week / Month
+ TaskDav Lock agenda
TaskDav Tasks
TaskDav Notes
diff --git a/app/src/main/res/xml/widget_agenda_info.xml b/app/src/main/res/xml/widget_agenda_info.xml
index aa11e8a..71b3150 100644
--- a/app/src/main/res/xml/widget_agenda_info.xml
+++ b/app/src/main/res/xml/widget_agenda_info.xml
@@ -8,5 +8,5 @@
android:initialLayout="@layout/widget_loading"
android:previewLayout="@layout/widget_agenda_preview"
android:resizeMode="horizontal|vertical"
- android:widgetCategory="home_screen"
+ android:widgetCategory="home_screen|keyguard"
android:description="@string/widget_agenda_description" />
diff --git a/app/src/main/res/xml/widget_lock_agenda_info.xml b/app/src/main/res/xml/widget_lock_agenda_info.xml
new file mode 100644
index 0000000..53f7e1b
--- /dev/null
+++ b/app/src/main/res/xml/widget_lock_agenda_info.xml
@@ -0,0 +1,14 @@
+
+