feat(todo): rows in rounded boxes, Today as Todoist's, lists pulled to sync, a project's own colour

From the developer's review of 3 Nov 2026 (ADR 0033, D19 to D21):
- Today's tab shows today's day in its calendar, Browse the menu's lines,
  Display's button Lucide's square-menu.
- A task's row stands in a rounded box, the rows of a list a group rounded
  at its ends; the box is the row's own surface, so a chosen row's colour no
  longer shows under Upcoming's pinned week strip as the list scrolls. Its
  project and section end the line of its date at the row's right edge.
- Today has at most two groups: what is overdue, folding under its heading,
  and today's, headed "Tuesday, 3 Nov" ("Dienstag, 3. Nov."), without a
  "· Today" after it; Upcoming's days take the same form.
- Upcoming's way back to today shows whenever another day is picked.
- Today, Upcoming, the Inbox and every project pulled down sync now, an
  empty list too, as Learn's.
- A project's screen washes in its own colour, as a studyset's in Learn.
- "Add section" in place of "New section".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-11-03 21:39:28 +01:00
commit 753527aed3
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
16 changed files with 518 additions and 64 deletions

View file

@ -60,7 +60,7 @@
"projectGone": "Dieses Projekt wurde gelöscht.",
"projectMenu": "Mehr",
"projectPickTitle": "Projekt",
"sectionAdd": "Neuer Abschnitt",
"sectionAdd": "Abschnitt hinzufügen",
"sectionEdit": "Abschnitt umbenennen",
"sectionDelete": "Abschnitt löschen",
"sectionDeleteTitle": "Abschnitt löschen?",
@ -243,5 +243,8 @@
"snoozeTitle": "Schlummern für",
"snoozeMinutes": "{count, plural, =1{1 Minute} other{{count} Minuten}}",
"reminderSnooze": "{count} Min. schlummern",
"monthMore": "{count} weitere"
"monthMore": "{count} weitere",
"dayHeadingPattern": "EEEE, d. MMM",
"overdueHide": "Überfälliges ausblenden",
"overdueShow": "Überfälliges einblenden"
}

View file

@ -114,7 +114,7 @@
"projectGone": "This project was deleted.",
"projectMenu": "More",
"projectPickTitle": "Project",
"sectionAdd": "New section",
"sectionAdd": "Add section",
"sectionEdit": "Rename section",
"sectionDelete": "Delete section",
"sectionDeleteTitle": "Delete the section?",
@ -778,5 +778,11 @@
"type": "int"
}
}
}
},
"dayHeadingPattern": "EEEE, d MMM",
"@dayHeadingPattern": {
"description": "intl's DateFormat pattern of a day's heading in Today and Upcoming, as Todoist's (\"Tuesday, 3 Nov\"): EEEE the weekday, d the day, MMM the short month."
},
"overdueHide": "Hide what is overdue",
"overdueShow": "Show what is overdue"
}

View file

@ -463,7 +463,7 @@ abstract class AppLocalizations {
/// No description provided for @sectionAdd.
///
/// In en, this message translates to:
/// **'New section'**
/// **'Add section'**
String get sectionAdd;
/// No description provided for @sectionEdit.
@ -1563,6 +1563,24 @@ abstract class AppLocalizations {
/// In en, this message translates to:
/// **'{count} more'**
String monthMore(int count);
/// intl's DateFormat pattern of a day's heading in Today and Upcoming, as Todoist's ("Tuesday, 3 Nov"): EEEE the weekday, d the day, MMM the short month.
///
/// In en, this message translates to:
/// **'EEEE, d MMM'**
String get dayHeadingPattern;
/// No description provided for @overdueHide.
///
/// In en, this message translates to:
/// **'Hide what is overdue'**
String get overdueHide;
/// No description provided for @overdueShow.
///
/// In en, this message translates to:
/// **'Show what is overdue'**
String get overdueShow;
}
class _AppLocalizationsDelegate

View file

@ -212,7 +212,7 @@ class AppLocalizationsDe extends AppLocalizations {
String get projectPickTitle => 'Projekt';
@override
String get sectionAdd => 'Neuer Abschnitt';
String get sectionAdd => 'Abschnitt hinzufügen';
@override
String get sectionEdit => 'Abschnitt umbenennen';
@ -966,4 +966,13 @@ class AppLocalizationsDe extends AppLocalizations {
String monthMore(int count) {
return '$count weitere';
}
@override
String get dayHeadingPattern => 'EEEE, d. MMM';
@override
String get overdueHide => 'Überfälliges ausblenden';
@override
String get overdueShow => 'Überfälliges einblenden';
}

View file

@ -212,7 +212,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get projectPickTitle => 'Project';
@override
String get sectionAdd => 'New section';
String get sectionAdd => 'Add section';
@override
String get sectionEdit => 'Rename section';
@ -965,6 +965,15 @@ class AppLocalizationsEn extends AppLocalizations {
String monthMore(int count) {
return '$count more';
}
@override
String get dayHeadingPattern => 'EEEE, d MMM';
@override
String get overdueHide => 'Hide what is overdue';
@override
String get overdueShow => 'Show what is overdue';
}
/// The translations for English, as used in the United Kingdom (`en_GB`).

View file

@ -179,7 +179,7 @@ class const DisplayButton({required final DisplayTarget target, super.key})
icon: Badge(
smallSize: 8,
isLabelVisible: changed,
child: const Icon(LucideIcons.slidersHorizontal),
child: const Icon(LucideIcons.squareMenu),
),
onPressed: () => showDisplaySheet(context, target),
);

View file

@ -9,6 +9,7 @@ import 'package:halkyon_todo_flutter/src/feature/project/widget/project_dialogs.
import 'package:halkyon_todo_flutter/src/feature/project/widget/project_tasks.dart';
import 'package:halkyon_todo_flutter/src/feature/task/bloc/todo_cubit.dart';
import 'package:halkyon_todo_flutter/src/feature/task/widget/task_drag.dart';
import 'package:halkyon_todo_flutter/src/feature/task/widget/task_refresh.dart';
import 'package:halkyon_todo_flutter/src/tabs/tab_screen.dart';
import 'package:halkyon_ui/halkyon_ui.dart';
import 'package:lucide_icons_flutter/lucide_icons.dart';
@ -57,7 +58,7 @@ class const ProjectScreenBody({
const KeysResetBanner(),
Expanded(
child: switch (projectId) {
final id? => ProjectTasks(projectId: id),
final id? => TaskRefresh(child: ProjectTasks(projectId: id)),
// No Inbox yet: made by the first device holding the
// account's keys, or still on its way here.
null
@ -98,7 +99,9 @@ class const ProjectScreen({required final String projectId, super.key})
);
}
final content = project.content!;
return ProjectScreenBody(
// Washed in its own colour, as a studyset's screens in Learn (the
// developer's review, 3 Nov 2026); without one, in Todo's.
final body = ProjectScreenBody(
title: project.inbox ? l10n.shellTabInbox : content.name,
projectId: projectId,
menu: project.inbox
@ -136,5 +139,9 @@ class const ProjectScreen({required final String projectId, super.key})
),
],
);
return switch (content.color) {
final color? => ScreenColor(color: color.background, child: body),
null => body,
};
}
}

View file

@ -16,6 +16,7 @@ import 'package:halkyon_todo_flutter/src/feature/task/model/task.dart';
import 'package:halkyon_todo_flutter/src/feature/task/model/task_views.dart';
import 'package:halkyon_todo_flutter/src/feature/task/widget/quick_add_sheet.dart';
import 'package:halkyon_todo_flutter/src/feature/task/widget/task_drag.dart';
import 'package:halkyon_todo_flutter/src/feature/task/widget/task_refresh.dart';
import 'package:halkyon_todo_flutter/src/feature/task/widget/task_texts.dart';
import 'package:halkyon_todo_flutter/src/feature/task/widget/task_tile.dart';
import 'package:halkyon_todo_flutter/src/tabs/today_tab.dart';
@ -73,7 +74,7 @@ class const ProjectTasks({required final String projectId, super.key})
if (loose.isEmpty &&
sections.isEmpty &&
!state.tasks.any((t) => t.content.projectId == projectId)) {
return Center(
return PullRoom(
child: EmptyListMessage(
icon: LucideIcons.listTodo,
title: l10n.projectEmptyTitle,
@ -117,6 +118,7 @@ class const ProjectTasks({required final String projectId, super.key})
),
),
child: CustomScrollView(
physics: const AlwaysScrollableScrollPhysics(),
slivers: [
SliverList.list(
children: [
@ -220,7 +222,7 @@ class const ProjectTasks({required final String projectId, super.key})
final l10n = AppLocalizations.of(context);
final theme = Theme.of(context);
if (tasks.isEmpty) {
return Center(
return PullRoom(
child: EmptyListMessage(
icon: LucideIcons.listFilter,
title: l10n.viewNothingTitle,
@ -241,6 +243,7 @@ class const ProjectTasks({required final String projectId, super.key})
labelOrder: [for (final label in state.labels) label.id],
);
return CustomScrollView(
physics: const AlwaysScrollableScrollPhysics(),
slivers: [
for (final (index, group) in groups.indexed)
SliverMainAxisGroup(
@ -260,8 +263,10 @@ class const ProjectTasks({required final String projectId, super.key})
),
SliverList.builder(
itemCount: group.tasks.length,
itemBuilder: (context, index) =>
TaskTile(task: group.tasks[index]),
itemBuilder: (context, index) => TaskTile(
task: group.tasks[index],
position: rowPosition(index, group.tasks.length),
),
),
],
),
@ -288,9 +293,12 @@ class const ProjectTasks({required final String projectId, super.key})
sectionId: group.sectionId,
tasks: group.tasks,
index: index,
child: TaskTile(task: group.tasks[index]),
child: TaskTile(
task: group.tasks[index],
position: rowPosition(index, group.tasks.length),
),
)
: TaskTile(task: task);
: TaskTile(task: task, position: rowPosition(index, group.tasks.length));
Widget _section(
BuildContext context,

View file

@ -0,0 +1,32 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:halkyon_app/halkyon_app.dart';
import 'package:halkyon_ui/halkyon_ui.dart';
import 'package:material_ui/material_ui.dart';
/// [child], a list of tasks, pulled down to sync now (the developer's
/// review, 3 Nov 2026, as Learn's lists): the spinner shows until the sync
/// is over. Where [fill], [child] is not scrollable itself (a list's empty
/// message) and is pulled in a room of the list's whole height
/// ([PullRoom]).
class const TaskRefresh({
required final Widget child,
final bool fill = false,
super.key,
}) extends StatelessWidget {
@override
Widget build(BuildContext context) => AppRefreshIndicator(
onRefresh: () => syncAndWait(context.read<SyncStatusBloc>()),
child: fill ? PullRoom(child: child) : child,
);
}
/// [child], not scrollable itself (a list's empty message), in a room of
/// the list's whole height that a [TaskRefresh] around it can pull.
class const PullRoom({required final Widget child, super.key})
extends StatelessWidget {
@override
Widget build(BuildContext context) => CustomScrollView(
physics: const AlwaysScrollableScrollPhysics(),
slivers: [SliverFillRemaining(hasScrollBody: false, child: child)],
);
}

View file

@ -33,11 +33,18 @@ import 'package:material_ui/material_ui.dart';
/// Where [later], it is a later time of a repeating task (a project's
/// Calendar, D17): muted, a recurrence's mark in place of the check, not
/// chosen nor swiped; a tap opens the task's sheet.
///
/// Each row stands in a rounded box, as Todoist's (the developer's review,
/// 3 Nov 2026), the rows of one list together a group rounded at its ends
/// ([position], [taskGroup]); the box is the row's own surface, so its
/// colour while chosen stays inside it as the list scrolls under a pinned
/// heading.
class const TaskTile({
required final OpenTask task,
final bool showProject = false,
final bool selectable = true,
final bool later = false,
final TaskRowPosition position = TaskRowPosition.alone,
super.key,
}) extends StatelessWidget {
@override
@ -104,12 +111,13 @@ class const TaskTile({
},
),
];
// At the row's end (the developer's review, 3 Nov 2026), as Todoist's.
final where = showProject && project != null
? Flexible(
child: Padding(
padding: const EdgeInsets.only(left: 8),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.end,
children: [
Flexible(
child: Text(
@ -202,7 +210,7 @@ class const TaskTile({
: () => openTaskSheet(context, task.id),
);
if (selection == null) {
return tile;
return _boxed(context, tile);
}
Widget swipe(IconData icon, Color color, Alignment alignment) => ColoredBox(
color: color,
@ -214,7 +222,7 @@ class const TaskTile({
),
),
);
return Semantics(
final row = Semantics(
customSemanticsActions: {
CustomSemanticsAction(label: l10n.swipeSelect): () =>
selection.toggle(task.id),
@ -245,6 +253,32 @@ class const TaskTile({
child: tile,
),
);
return _boxed(context, row);
}
/// [row] in its rounded box, apart from the next by a hairline.
Widget _boxed(BuildContext context, Widget row) {
const outer = Radius.circular(16);
const inner = Radius.circular(4);
final top = switch (position) {
TaskRowPosition.alone || TaskRowPosition.first => outer,
_ => inner,
};
final bottom = switch (position) {
TaskRowPosition.alone || TaskRowPosition.last => outer,
_ => inner,
};
return Padding(
padding: const EdgeInsets.fromLTRB(12, 1, 12, 1),
child: Material(
color: Theme.of(context).colorScheme.surfaceContainer,
clipBehavior: Clip.antiAlias,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: top, bottom: bottom),
),
child: row,
),
);
}
/// Asks for the task's date and writes it.
@ -301,3 +335,25 @@ class const TaskCheck({required final OpenTask task, super.key})
);
}
}
/// Where a row stands in its list's group (the developer's review, 3 Nov
/// 2026): the group's box is rounded at its ends only.
enum TaskRowPosition() {
alone,
first,
middle,
last,
}
/// Where the [index]th of [length] rows stands in their group.
TaskRowPosition rowPosition(int index, int length) => switch ((index, length)) {
(_, 1) => TaskRowPosition.alone,
(0, _) => TaskRowPosition.first,
(final i, final n) when i == n - 1 => TaskRowPosition.last,
_ => TaskRowPosition.middle,
};
/// [rows] as one group, each told where it stands in it.
List<Widget> taskGroup(List<Widget Function(TaskRowPosition)> rows) => [
for (final (index, row) in rows.indexed) row(rowPosition(index, rows.length)),
];

View file

@ -120,15 +120,21 @@ class _DayAgendaState() extends State<DayAgenda> {
today.daysUntil(WeekStrip.weekOf(selected, firstWeekday).plus(6)) + 1,
);
final (:overdue, :days, :busy) = widget.calendarFor(reach);
Widget row(CalendarEntry entry) => entry.occurrence
Widget row(CalendarEntry entry, TaskRowPosition position) =>
entry.occurrence
? TaskTile(
task: entry.task,
showProject: widget.showProject,
later: true,
position: position,
)
: DraggableTask(
task: entry.task,
child: TaskTile(task: entry.task, showProject: widget.showProject),
child: TaskTile(
task: entry.task,
showProject: widget.showProject,
position: position,
),
);
return Column(
children: [
@ -150,6 +156,7 @@ class _DayAgendaState() extends State<DayAgenda> {
// few weeks of tasks, not thousands.
child: SingleChildScrollView(
controller: _scroll,
physics: const AlwaysScrollableScrollPhysics(),
padding: EdgeInsets.only(
bottom: 88 + FloatingNavigationBar.coverOf(context),
),
@ -164,8 +171,11 @@ class _DayAgendaState() extends State<DayAgenda> {
child: Text(l10n.reschedule),
),
),
for (final task in overdue)
row((task: task, occurrence: false)),
...taskGroup([
for (final task in overdue)
(position) =>
row((task: task, occurrence: false), position),
]),
],
// The add button dropped on a day adds a task for it, a
// row held and dropped there moves to it (D16).
@ -192,7 +202,10 @@ class _DayAgendaState() extends State<DayAgenda> {
),
),
),
for (final entry in entries) row(entry),
...taskGroup([
for (final entry in entries)
(position) => row(entry, position),
]),
],
),
),

View file

@ -0,0 +1,76 @@
import 'dart:async';
import 'package:clock/clock.dart';
import 'package:lucide_icons_flutter/lucide_icons.dart';
import 'package:material_ui/material_ui.dart';
/// Today's tab icon (the developer's review, 3 Nov 2026, as Todoist's): a
/// blank calendar with today's day of the month in it, as Lucide's
/// `calendar-1` holds a 1, drawn anew at midnight.
class const TodayIcon({super.key}) extends StatefulWidget {
@override
State<TodayIcon> createState() => _TodayIconState();
}
class _TodayIconState() extends State<TodayIcon> {
Timer? _midnight;
@override
void initState() {
super.initState();
_wait();
}
/// Draws the icon again just after the next midnight, and waits again.
void _wait() {
final now = clock.now();
final next = DateTime(now.year, now.month, now.day + 1);
_midnight = Timer(next.difference(now) + const Duration(seconds: 1), () {
if (mounted) {
setState(() {});
_wait();
}
});
}
@override
void dispose() {
_midnight?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
final icon = IconTheme.of(context);
final size = icon.size ?? 24;
return SizedBox.square(
dimension: size,
child: Stack(
alignment: Alignment.center,
children: [
const Icon(LucideIcons.calendar),
// Lucide's calendar: its box from 4 to 22 of 24 units, the line
// under its rings at 10; the day centred in what is below it.
Positioned(
top: size * 10 / 24,
bottom: size * 2 / 24,
left: 0,
right: 0,
child: Center(
child: Text(
'${clock.now().day}',
textScaler: TextScaler.noScaling,
style: TextStyle(
color: icon.color,
fontSize: size * 0.4,
height: 1,
fontWeight: FontWeight.w700,
),
),
),
),
],
),
);
}
}

View file

@ -9,6 +9,7 @@ import 'package:halkyon_todo_flutter/src/feature/task/model/task_views.dart';
import 'package:halkyon_todo_flutter/src/feature/task/widget/date_picker.dart';
import 'package:halkyon_todo_flutter/src/feature/task/widget/quick_add_sheet.dart';
import 'package:halkyon_todo_flutter/src/feature/task/widget/task_drag.dart';
import 'package:halkyon_todo_flutter/src/feature/task/widget/task_refresh.dart';
import 'package:halkyon_todo_flutter/src/feature/task/widget/task_tile.dart';
import 'package:halkyon_todo_flutter/src/tabs/tab_screen.dart';
import 'package:halkyon_ui/halkyon_ui.dart';
@ -16,11 +17,21 @@ import 'package:intl/intl.dart';
import 'package:lucide_icons_flutter/lucide_icons.dart';
import 'package:material_ui/material_ui.dart';
/// Today (ADR 0033, D2, D8), where the app opens: what is overdue first,
/// with Reschedule moving all of it to a day at once, then what is due
/// today, each with its project; a button adds a task for today. A reset of
/// the person's keys waits above it (`KeysResetBanner`).
class const TodayTab({super.key}) extends StatelessWidget {
/// Today (ADR 0033, D2, D8), where the app opens: at most two groups, as
/// Todoist's (the developer's review, 3 Nov 2026): what is overdue, folding
/// under its heading, with Reschedule moving all of it to a day at once,
/// then today's, headed by the day alone; each with its project. A button
/// adds a task for today. A reset of the person's keys waits above it
/// (`KeysResetBanner`).
class const TodayTab({super.key}) extends StatefulWidget {
@override
State<TodayTab> createState() => _TodayTabState();
}
class _TodayTabState() extends State<TodayTab> {
/// Whether what is overdue is folded away.
bool _overdueFolded = false;
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
@ -43,32 +54,69 @@ class const TodayTab({super.key}) extends StatelessWidget {
child: !state.loaded
? const Center(child: AppLoadingIndicator())
: overdue.isEmpty && due.isEmpty
? Center(
? TaskRefresh(
fill: true,
child: EmptyListMessage(
icon: LucideIcons.calendarCheck,
title: l10n.todayEmptyTitle,
hint: l10n.todayEmptyHint,
),
)
: ListView(
padding: EdgeInsets.only(
bottom: 88 + FloatingNavigationBar.coverOf(context),
),
children: [
if (overdue.isNotEmpty)
DayHeading(
title: l10n.overdue,
action: TextButton(
onPressed: () => reschedule(context, overdue),
child: Text(l10n.reschedule),
: TaskRefresh(
child: ListView(
physics: const AlwaysScrollableScrollPhysics(),
padding: EdgeInsets.only(
bottom: 88 + FloatingNavigationBar.coverOf(context),
),
children: [
if (overdue.isNotEmpty) ...[
DayHeading(
title: l10n.overdue,
action: Row(
mainAxisSize: MainAxisSize.min,
children: [
TextButton(
onPressed: () =>
reschedule(context, overdue),
child: Text(l10n.reschedule),
),
IconButton(
tooltip: _overdueFolded
? l10n.overdueShow
: l10n.overdueHide,
icon: Icon(
_overdueFolded
? LucideIcons.chevronDown
: LucideIcons.chevronUp,
),
onPressed: () => setState(
() => _overdueFolded = !_overdueFolded,
),
),
],
),
),
),
for (final task in overdue)
TaskTile(task: task, showProject: true),
DayHeading(title: dayHeading(context, today, today)),
for (final task in due)
TaskTile(task: task, showProject: true),
],
if (!_overdueFolded)
...taskGroup([
for (final task in overdue)
(position) => TaskTile(
task: task,
showProject: true,
position: position,
),
]),
],
DayHeading(title: dayTitle(context, today)),
...taskGroup([
for (final task in due)
(position) => TaskTile(
task: task,
showProject: true,
position: position,
),
]),
],
),
),
),
),
@ -108,13 +156,18 @@ class const DayHeading({
}
}
/// [day]'s heading, seen on [today]: its weekday, day and month, and Today
/// or Tomorrow where it is.
/// [day] as a heading names it, as Todoist's ("Tuesday, 3 Nov"; the
/// developer's review, 3 Nov 2026): its weekday, day and short month.
String dayTitle(BuildContext context, Day day) => DateFormat(
AppLocalizations.of(context).dayHeadingPattern,
Localizations.localeOf(context).toString(),
).format(day.toDate());
/// [day]'s heading, seen on [today]: [dayTitle], and Today or Tomorrow
/// where it is.
String dayHeading(BuildContext context, Day day, Day today) {
final l10n = AppLocalizations.of(context);
final date = DateFormat.MMMMEEEEd(
Localizations.localeOf(context).toString(),
).format(day.toDate());
final date = dayTitle(context, day);
return switch (today.daysUntil(day)) {
0 => l10n.dayHeadingNamed(date, l10n.dayToday),
1 => l10n.dayHeadingNamed(date, l10n.dayTomorrow),

View file

@ -4,6 +4,7 @@ import 'package:halkyon_todo_flutter/l10n/app_localizations.dart';
import 'package:halkyon_todo_flutter/src/app/todo_frame.dart';
import 'package:halkyon_todo_flutter/src/feature/task/widget/task_selection.dart';
import 'package:halkyon_todo_flutter/src/feature/task/widget/task_sheet.dart';
import 'package:halkyon_todo_flutter/src/tabs/today_icon.dart';
import 'package:halkyon_ui/halkyon_ui.dart';
import 'package:lucide_icons_flutter/lucide_icons.dart';
import 'package:material_ui/material_ui.dart';
@ -65,7 +66,7 @@ class _TodoShellState() extends State<TodoShell> {
),
(
label: l10n.shellTabToday,
icon: const Icon(LucideIcons.calendarCheck),
icon: const TodayIcon(),
selectedIcon: null,
),
(
@ -78,7 +79,7 @@ class _TodoShellState() extends State<TodoShell> {
// The cog to Settings is atop Browse: the count of accounts
// waiting for whoever operates the instance shows on its tab.
icon: const WaitingAccountsBadge(
child: Icon(LucideIcons.layoutList),
child: Icon(LucideIcons.menu),
),
selectedIcon: null,
),

View file

@ -10,6 +10,7 @@ import 'package:halkyon_todo_flutter/src/feature/task/model/task.dart';
import 'package:halkyon_todo_flutter/src/feature/task/model/task_views.dart';
import 'package:halkyon_todo_flutter/src/feature/task/widget/quick_add_sheet.dart';
import 'package:halkyon_todo_flutter/src/feature/task/widget/task_drag.dart';
import 'package:halkyon_todo_flutter/src/feature/task/widget/task_refresh.dart';
import 'package:halkyon_todo_flutter/src/tabs/day_agenda.dart';
import 'package:halkyon_todo_flutter/src/tabs/tab_screen.dart';
import 'package:halkyon_todo_flutter/src/tabs/three_day_grid.dart';
@ -72,10 +73,12 @@ class _UpcomingTabState() extends State<UpcomingTab> {
body: !state.loaded
? const Center(child: AppLoadingIndicator())
: switch (upcoming.layout) {
UpcomingLayout.agenda => DayAgenda(
showProject: true,
onSelected: select,
calendarFor: calendarFor,
UpcomingLayout.agenda => TaskRefresh(
child: DayAgenda(
showProject: true,
onSelected: select,
calendarFor: calendarFor,
),
),
UpcomingLayout.threeDay => ThreeDayGrid(
coloring: upcoming.coloring,

View file

@ -14,7 +14,8 @@ import 'package:material_ui/material_ui.dart';
/// The month's name, or its handle dragged down, unfolds the whole month in
/// place beneath it, the arrows then going by months; the handle dragged up
/// or the name again folds it (the developer's, 1 Nov, D14, as Todoist's).
/// Once another week shows, a button beside the name goes back to today.
/// Once another day than today is picked, a button beside the name goes
/// back to today (the developer's review, 3 Nov 2026).
class const WeekStrip({
required final Day selected,
required final Day today,
@ -142,7 +143,7 @@ class _WeekStripState() extends State<WeekStrip> {
onPressed: () => _setMonth(!_month),
),
),
if (start != thisWeek)
if (selected != today)
IconButton(
tooltip: l10n.weekToday,
icon: const Icon(LucideIcons.calendar1),