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",
|
||||
"shellTabUpcoming": "Demnächst",
|
||||
"shellTabInbox": "Eingang",
|
||||
"todayCount": "{count, plural, =1{1 Aufgabe} other{{count} Aufgaben}}",
|
||||
"browseProfile": "Profil",
|
||||
"browseProfileHint": "Dein Konto, wie du dich anmeldest, und dein öffentliches Profil",
|
||||
"shellTabBrowse": "Übersicht",
|
||||
|
|
|
|||
|
|
@ -15,6 +15,15 @@
|
|||
"shellTabToday": "Today",
|
||||
"shellTabUpcoming": "Upcoming",
|
||||
"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",
|
||||
"browseProfileHint": "Your account, how you sign in and your public profile",
|
||||
"shellTabBrowse": "Browse",
|
||||
|
|
|
|||
|
|
@ -154,6 +154,12 @@ abstract class AppLocalizations {
|
|||
/// **'Inbox'**
|
||||
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.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
|
|||
|
|
@ -38,6 +38,17 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
@override
|
||||
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
|
||||
String get browseProfile => 'Profil';
|
||||
|
||||
|
|
|
|||
|
|
@ -38,6 +38,17 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
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
|
||||
String get browseProfile => 'Profile';
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,10 @@ class const TabScreen({
|
|||
/// The tab's name, in the app bar.
|
||||
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.
|
||||
required final Widget body,
|
||||
|
||||
|
|
@ -56,9 +60,24 @@ class const TabScreen({
|
|||
onPressed: selection.end,
|
||||
)
|
||||
: null,
|
||||
title: Text(
|
||||
choosing ? l10n.selectionCount(selection.ids.length) : title,
|
||||
),
|
||||
title: switch (subtitle) {
|
||||
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
|
||||
? const []
|
||||
: [
|
||||
|
|
|
|||
|
|
@ -38,8 +38,12 @@ class _TodayTabState() extends State<TodayTab> {
|
|||
final state = context.watch<TodoCubit>().state;
|
||||
final today = Day.of(clock.now());
|
||||
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(
|
||||
title: l10n.shellTabToday,
|
||||
subtitle: state.loaded && count > 0 ? l10n.todayCount(count) : null,
|
||||
actions: const [SelectTasksMenu()],
|
||||
floatingActionButton: AddTaskButton(
|
||||
onPressed: () => showQuickAdd(context, day: today),
|
||||
|
|
|
|||
|
|
@ -1848,6 +1848,36 @@ void main() {
|
|||
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 '
|
||||
'the day picked', (tester) async {
|
||||
final today = Day.of(DateTime.now());
|
||||
|
|
|
|||
Loading…
Reference in a new issue