feat(todo): Today counts its tasks under its title
As Todoist's, Today's app bar says how many tasks it holds, those
overdue and those due today together ("3 tasks"). TabScreen takes an
optional subtitle for it, left out while tasks are being chosen, when
the title counts those instead.
Whats-New: Today says how many tasks are overdue or due today under its title.
Whats-New-De: „Heute“ zeigt unter dem Titel, wie viele Aufgaben überfällig oder heute fällig sind.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
b043d5ef9e
commit
8a677e0845
8 changed files with 94 additions and 3 deletions
|
|
@ -9,6 +9,7 @@
|
||||||
"shellTabToday": "Heute",
|
"shellTabToday": "Heute",
|
||||||
"shellTabUpcoming": "Demnächst",
|
"shellTabUpcoming": "Demnächst",
|
||||||
"shellTabInbox": "Eingang",
|
"shellTabInbox": "Eingang",
|
||||||
|
"todayCount": "{count, plural, =1{1 Aufgabe} other{{count} Aufgaben}}",
|
||||||
"browseProfile": "Profil",
|
"browseProfile": "Profil",
|
||||||
"browseProfileHint": "Dein Konto, wie du dich anmeldest, und dein öffentliches Profil",
|
"browseProfileHint": "Dein Konto, wie du dich anmeldest, und dein öffentliches Profil",
|
||||||
"shellTabBrowse": "Übersicht",
|
"shellTabBrowse": "Übersicht",
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,15 @@
|
||||||
"shellTabToday": "Today",
|
"shellTabToday": "Today",
|
||||||
"shellTabUpcoming": "Upcoming",
|
"shellTabUpcoming": "Upcoming",
|
||||||
"shellTabInbox": "Inbox",
|
"shellTabInbox": "Inbox",
|
||||||
|
"todayCount": "{count, plural, =1{1 task} other{{count} tasks}}",
|
||||||
|
"@todayCount": {
|
||||||
|
"description": "Under Today's title: how many tasks are overdue or due today.",
|
||||||
|
"placeholders": {
|
||||||
|
"count": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"browseProfile": "Profile",
|
"browseProfile": "Profile",
|
||||||
"browseProfileHint": "Your account, how you sign in and your public profile",
|
"browseProfileHint": "Your account, how you sign in and your public profile",
|
||||||
"shellTabBrowse": "Browse",
|
"shellTabBrowse": "Browse",
|
||||||
|
|
|
||||||
|
|
@ -154,6 +154,12 @@ abstract class AppLocalizations {
|
||||||
/// **'Inbox'**
|
/// **'Inbox'**
|
||||||
String get shellTabInbox;
|
String get shellTabInbox;
|
||||||
|
|
||||||
|
/// Under Today's title: how many tasks are overdue or due today.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'{count, plural, =1{1 task} other{{count} tasks}}'**
|
||||||
|
String todayCount(int count);
|
||||||
|
|
||||||
/// No description provided for @browseProfile.
|
/// No description provided for @browseProfile.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,17 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
@override
|
@override
|
||||||
String get shellTabInbox => 'Eingang';
|
String get shellTabInbox => 'Eingang';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String todayCount(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: '$count Aufgaben',
|
||||||
|
one: '1 Aufgabe',
|
||||||
|
);
|
||||||
|
return '$_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get browseProfile => 'Profil';
|
String get browseProfile => 'Profil';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,17 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||||
@override
|
@override
|
||||||
String get shellTabInbox => 'Inbox';
|
String get shellTabInbox => 'Inbox';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String todayCount(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: '$count tasks',
|
||||||
|
one: '1 task',
|
||||||
|
);
|
||||||
|
return '$_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get browseProfile => 'Profile';
|
String get browseProfile => 'Profile';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,10 @@ class const TabScreen({
|
||||||
/// The tab's name, in the app bar.
|
/// The tab's name, in the app bar.
|
||||||
required final String title,
|
required final String title,
|
||||||
|
|
||||||
|
/// A line under [title], such as how many tasks the tab holds (Today's,
|
||||||
|
/// as Todoist's); hidden while tasks are being chosen.
|
||||||
|
final String? subtitle,
|
||||||
|
|
||||||
/// The tab's content.
|
/// The tab's content.
|
||||||
required final Widget body,
|
required final Widget body,
|
||||||
|
|
||||||
|
|
@ -56,9 +60,24 @@ class const TabScreen({
|
||||||
onPressed: selection.end,
|
onPressed: selection.end,
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
title: Text(
|
title: switch (subtitle) {
|
||||||
choosing ? l10n.selectionCount(selection.ids.length) : title,
|
final subtitle? when !choosing => Column(
|
||||||
),
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(title),
|
||||||
|
Text(
|
||||||
|
subtitle,
|
||||||
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
_ => Text(
|
||||||
|
choosing ? l10n.selectionCount(selection.ids.length) : title,
|
||||||
|
),
|
||||||
|
},
|
||||||
actions: choosing
|
actions: choosing
|
||||||
? const []
|
? const []
|
||||||
: [
|
: [
|
||||||
|
|
|
||||||
|
|
@ -38,8 +38,12 @@ class _TodayTabState() extends State<TodayTab> {
|
||||||
final state = context.watch<TodoCubit>().state;
|
final state = context.watch<TodoCubit>().state;
|
||||||
final today = Day.of(clock.now());
|
final today = Day.of(clock.now());
|
||||||
final (:overdue, :due) = todayOf(state.tasks, today);
|
final (:overdue, :due) = todayOf(state.tasks, today);
|
||||||
|
// Overdue and today's counted together, as Todoist's (the developer's
|
||||||
|
// review, 4 Nov 2026).
|
||||||
|
final count = overdue.length + due.length;
|
||||||
return TabScreen(
|
return TabScreen(
|
||||||
title: l10n.shellTabToday,
|
title: l10n.shellTabToday,
|
||||||
|
subtitle: state.loaded && count > 0 ? l10n.todayCount(count) : null,
|
||||||
actions: const [SelectTasksMenu()],
|
actions: const [SelectTasksMenu()],
|
||||||
floatingActionButton: AddTaskButton(
|
floatingActionButton: AddTaskButton(
|
||||||
onPressed: () => showQuickAdd(context, day: today),
|
onPressed: () => showQuickAdd(context, day: today),
|
||||||
|
|
|
||||||
|
|
@ -1848,6 +1848,36 @@ void main() {
|
||||||
expect(find.text('Pay the bill'), findsOneWidget);
|
expect(find.text('Pay the bill'), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('Today counts what is overdue and due today under its '
|
||||||
|
"title, as Todoist's; not while tasks are chosen (the developer's "
|
||||||
|
'review, 4 Nov 2026)', (tester) async {
|
||||||
|
final today = Day.of(DateTime.now());
|
||||||
|
await start(tester, usedBefore: true);
|
||||||
|
expect(find.textContaining('task'), findsNothing, reason: 'none yet');
|
||||||
|
for (final (title, day) in [
|
||||||
|
('Pay the bill', today.plus(-2)),
|
||||||
|
('Water plants', today),
|
||||||
|
('Call mum', today),
|
||||||
|
('Pack', today.plus(1)),
|
||||||
|
]) {
|
||||||
|
await seed(
|
||||||
|
tester,
|
||||||
|
TaskContent(title: title, projectId: inbox(), date: TaskDate(day)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final count = find.descendant(
|
||||||
|
of: find.byType(AppBar),
|
||||||
|
matching: find.text('3 tasks'),
|
||||||
|
);
|
||||||
|
expect(count, findsOneWidget);
|
||||||
|
await tester.tap(find.byTooltip('More'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
await tester.tap(find.text('Select tasks'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(count, findsNothing);
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets('Today puts what is overdue first; Reschedule moves it to '
|
testWidgets('Today puts what is overdue first; Reschedule moves it to '
|
||||||
'the day picked', (tester) async {
|
'the day picked', (tester) async {
|
||||||
final today = Day.of(DateTime.now());
|
final today = Day.of(DateTime.now());
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue