feat(todo): recurring tasks, set through a picker (ADR 0033, D7)

A task's date may recur: every n days, weeks (on which weekdays),
months (on its day, or its nth or last weekday), years, from its date
or from when it is done, until a day or for n times. Done, it moves on
to the next day after both its date and today, its deadline as far, its
time kept, its done sub-tasks open again and when it was done kept in
its payload; undo writes all of it back. The date sheet offers the rules
that fit the task's day and a rule of the person's own; rows show a
recurring date with its own icon, the task sheet the rule in words.

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-01 12:15:51 +01:00
commit aa013f5a00
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
5 changed files with 1655 additions and 25 deletions

View file

@ -94,5 +94,30 @@
"todayEmptyHint": "Aufgaben mit dem heutigen Datum und überfällige erscheinen hier.",
"lockedRows": "{count, plural, =1{1 Eintrag lässt sich auf diesem Gerät nicht öffnen.} other{{count} Einträge lassen sich auf diesem Gerät nicht öffnen.}}",
"inboxMissingTitle": "Noch kein Eingang",
"inboxMissingHint": "Dein Eingang kommt mit dem nächsten Abgleich, oder sobald dieses Gerät die Schlüssel deines Kontos hat."
"inboxMissingHint": "Dein Eingang kommt mit dem nächsten Abgleich, oder sobald dieses Gerät die Schlüssel deines Kontos hat.",
"repeatTitle": "Wiederholen",
"repeatNone": "Keine Wiederholung",
"repeatCustom": "Eigene Regel",
"repeatEveryDay": "{count, plural, =1{Jeden Tag} other{Alle {count} Tage}}",
"repeatEveryWeek": "{count, plural, =1{Jede Woche} other{Alle {count} Wochen}}",
"repeatEveryMonth": "{count, plural, =1{Jeden Monat} other{Alle {count} Monate}}",
"repeatEveryYear": "{count, plural, =1{Jedes Jahr} other{Alle {count} Jahre}}",
"repeatWeekdays": "Jeden Werktag",
"repeatOn": "{rule} am {on}",
"repeatMonthDay": "{day}.",
"repeatMonthWeek": "{nth, select, 1{ersten} 2{zweiten} 3{dritten} 4{vierten} other{letzten}} {weekday}",
"repeatFromCompletion": "{rule}, ab Erledigung",
"repeatUntil": "{rule}, bis {day}",
"repeatLeft": "{rule}, {count, plural, =0{nur noch diesmal} =1{noch einmal} other{noch {count}-mal}}",
"repeatEvery": "Alle",
"repeatUnitDays": "Tage",
"repeatUnitWeeks": "Wochen",
"repeatUnitMonths": "Monate",
"repeatUnitYears": "Jahre",
"repeatCountFromCompletion": "Ab Erledigung zählen",
"repeatCountFromCompletionHint": "Nicht ab ihrem Datum: später erledigt, kommt sie entsprechend später wieder.",
"repeatEnds": "Endet",
"repeatEndsNever": "Nie",
"repeatEndsOn": "An einem Tag",
"repeatEndsAfter": "{count, plural, =1{Nach einem Mal} other{Nach {count} Malen}}"
}

View file

@ -108,5 +108,41 @@
"lockedRows": "{count, plural, =1{1 item doesn't open on this device.} other{{count} items don't open on this device.}}",
"@lockedRows": {"placeholders": {"count": {"type": "int"}}},
"inboxMissingTitle": "No Inbox yet",
"inboxMissingHint": "Your Inbox comes with the next sync, or once this device holds your account's keys."
"inboxMissingHint": "Your Inbox comes with the next sync, or once this device holds your account's keys.",
"repeatTitle": "Repeat",
"repeatNone": "Doesn't repeat",
"repeatCustom": "Custom",
"repeatEveryDay": "{count, plural, =1{Every day} other{Every {count} days}}",
"@repeatEveryDay": {"placeholders": {"count": {"type": "int"}}},
"repeatEveryWeek": "{count, plural, =1{Every week} other{Every {count} weeks}}",
"@repeatEveryWeek": {"placeholders": {"count": {"type": "int"}}},
"repeatEveryMonth": "{count, plural, =1{Every month} other{Every {count} months}}",
"@repeatEveryMonth": {"placeholders": {"count": {"type": "int"}}},
"repeatEveryYear": "{count, plural, =1{Every year} other{Every {count} years}}",
"@repeatEveryYear": {"placeholders": {"count": {"type": "int"}}},
"repeatWeekdays": "Every weekday",
"repeatOn": "{rule} on {on}",
"@repeatOn": {"placeholders": {"rule": {"type": "String"}, "on": {"type": "String"}}},
"repeatMonthDay": "day {day}",
"@repeatMonthDay": {"placeholders": {"day": {"type": "int"}}},
"repeatMonthWeek": "the {nth, select, 1{first} 2{second} 3{third} 4{fourth} other{last}} {weekday}",
"@repeatMonthWeek": {"placeholders": {"nth": {"type": "String"}, "weekday": {"type": "String"}}},
"repeatFromCompletion": "{rule}, from when it's done",
"@repeatFromCompletion": {"placeholders": {"rule": {"type": "String"}}},
"repeatUntil": "{rule}, until {day}",
"@repeatUntil": {"placeholders": {"rule": {"type": "String"}, "day": {"type": "String"}}},
"repeatLeft": "{rule}, {count, plural, =0{this time only} =1{once more} other{{count} more times}}",
"@repeatLeft": {"placeholders": {"rule": {"type": "String"}, "count": {"type": "int"}}},
"repeatEvery": "Every",
"repeatUnitDays": "Days",
"repeatUnitWeeks": "Weeks",
"repeatUnitMonths": "Months",
"repeatUnitYears": "Years",
"repeatCountFromCompletion": "Count from when it's done",
"repeatCountFromCompletionHint": "Not from its date: done late, it comes again as long after.",
"repeatEnds": "Ends",
"repeatEndsNever": "Never",
"repeatEndsOn": "On a day",
"repeatEndsAfter": "{count, plural, =1{After once} other{After {count} times}}",
"@repeatEndsAfter": {"placeholders": {"count": {"type": "int"}}}
}

View file

@ -633,6 +633,156 @@ abstract class AppLocalizations {
/// In en, this message translates to:
/// **'Your Inbox comes with the next sync, or once this device holds your account\'s keys.'**
String get inboxMissingHint;
/// No description provided for @repeatTitle.
///
/// In en, this message translates to:
/// **'Repeat'**
String get repeatTitle;
/// No description provided for @repeatNone.
///
/// In en, this message translates to:
/// **'Doesn\'t repeat'**
String get repeatNone;
/// No description provided for @repeatCustom.
///
/// In en, this message translates to:
/// **'Custom'**
String get repeatCustom;
/// No description provided for @repeatEveryDay.
///
/// In en, this message translates to:
/// **'{count, plural, =1{Every day} other{Every {count} days}}'**
String repeatEveryDay(int count);
/// No description provided for @repeatEveryWeek.
///
/// In en, this message translates to:
/// **'{count, plural, =1{Every week} other{Every {count} weeks}}'**
String repeatEveryWeek(int count);
/// No description provided for @repeatEveryMonth.
///
/// In en, this message translates to:
/// **'{count, plural, =1{Every month} other{Every {count} months}}'**
String repeatEveryMonth(int count);
/// No description provided for @repeatEveryYear.
///
/// In en, this message translates to:
/// **'{count, plural, =1{Every year} other{Every {count} years}}'**
String repeatEveryYear(int count);
/// No description provided for @repeatWeekdays.
///
/// In en, this message translates to:
/// **'Every weekday'**
String get repeatWeekdays;
/// No description provided for @repeatOn.
///
/// In en, this message translates to:
/// **'{rule} on {on}'**
String repeatOn(String rule, String on);
/// No description provided for @repeatMonthDay.
///
/// In en, this message translates to:
/// **'day {day}'**
String repeatMonthDay(int day);
/// No description provided for @repeatMonthWeek.
///
/// In en, this message translates to:
/// **'the {nth, select, 1{first} 2{second} 3{third} 4{fourth} other{last}} {weekday}'**
String repeatMonthWeek(String nth, String weekday);
/// No description provided for @repeatFromCompletion.
///
/// In en, this message translates to:
/// **'{rule}, from when it\'s done'**
String repeatFromCompletion(String rule);
/// No description provided for @repeatUntil.
///
/// In en, this message translates to:
/// **'{rule}, until {day}'**
String repeatUntil(String rule, String day);
/// No description provided for @repeatLeft.
///
/// In en, this message translates to:
/// **'{rule}, {count, plural, =0{this time only} =1{once more} other{{count} more times}}'**
String repeatLeft(String rule, int count);
/// No description provided for @repeatEvery.
///
/// In en, this message translates to:
/// **'Every'**
String get repeatEvery;
/// No description provided for @repeatUnitDays.
///
/// In en, this message translates to:
/// **'Days'**
String get repeatUnitDays;
/// No description provided for @repeatUnitWeeks.
///
/// In en, this message translates to:
/// **'Weeks'**
String get repeatUnitWeeks;
/// No description provided for @repeatUnitMonths.
///
/// In en, this message translates to:
/// **'Months'**
String get repeatUnitMonths;
/// No description provided for @repeatUnitYears.
///
/// In en, this message translates to:
/// **'Years'**
String get repeatUnitYears;
/// No description provided for @repeatCountFromCompletion.
///
/// In en, this message translates to:
/// **'Count from when it\'s done'**
String get repeatCountFromCompletion;
/// No description provided for @repeatCountFromCompletionHint.
///
/// In en, this message translates to:
/// **'Not from its date: done late, it comes again as long after.'**
String get repeatCountFromCompletionHint;
/// No description provided for @repeatEnds.
///
/// In en, this message translates to:
/// **'Ends'**
String get repeatEnds;
/// No description provided for @repeatEndsNever.
///
/// In en, this message translates to:
/// **'Never'**
String get repeatEndsNever;
/// No description provided for @repeatEndsOn.
///
/// In en, this message translates to:
/// **'On a day'**
String get repeatEndsOn;
/// No description provided for @repeatEndsAfter.
///
/// In en, this message translates to:
/// **'{count, plural, =1{After once} other{After {count} times}}'**
String repeatEndsAfter(int count);
}
class _AppLocalizationsDelegate

View file

@ -322,4 +322,149 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get inboxMissingHint =>
'Dein Eingang kommt mit dem nächsten Abgleich, oder sobald dieses Gerät die Schlüssel deines Kontos hat.';
@override
String get repeatTitle => 'Wiederholen';
@override
String get repeatNone => 'Keine Wiederholung';
@override
String get repeatCustom => 'Eigene Regel';
@override
String repeatEveryDay(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: 'Alle $count Tage',
one: 'Jeden Tag',
);
return '$_temp0';
}
@override
String repeatEveryWeek(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: 'Alle $count Wochen',
one: 'Jede Woche',
);
return '$_temp0';
}
@override
String repeatEveryMonth(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: 'Alle $count Monate',
one: 'Jeden Monat',
);
return '$_temp0';
}
@override
String repeatEveryYear(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: 'Alle $count Jahre',
one: 'Jedes Jahr',
);
return '$_temp0';
}
@override
String get repeatWeekdays => 'Jeden Werktag';
@override
String repeatOn(String rule, String on) {
return '$rule am $on';
}
@override
String repeatMonthDay(int day) {
return '$day.';
}
@override
String repeatMonthWeek(String nth, String weekday) {
String _temp0 = intl.Intl.selectLogic(
nth,
{
'1': 'ersten',
'2': 'zweiten',
'3': 'dritten',
'4': 'vierten',
'other': 'letzten',
},
);
return '$_temp0 $weekday';
}
@override
String repeatFromCompletion(String rule) {
return '$rule, ab Erledigung';
}
@override
String repeatUntil(String rule, String day) {
return '$rule, bis $day';
}
@override
String repeatLeft(String rule, int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: 'noch $count-mal',
one: 'noch einmal',
zero: 'nur noch diesmal',
);
return '$rule, $_temp0';
}
@override
String get repeatEvery => 'Alle';
@override
String get repeatUnitDays => 'Tage';
@override
String get repeatUnitWeeks => 'Wochen';
@override
String get repeatUnitMonths => 'Monate';
@override
String get repeatUnitYears => 'Jahre';
@override
String get repeatCountFromCompletion => 'Ab Erledigung zählen';
@override
String get repeatCountFromCompletionHint =>
'Nicht ab ihrem Datum: später erledigt, kommt sie entsprechend später wieder.';
@override
String get repeatEnds => 'Endet';
@override
String get repeatEndsNever => 'Nie';
@override
String get repeatEndsOn => 'An einem Tag';
@override
String repeatEndsAfter(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: 'Nach $count Malen',
one: 'Nach einem Mal',
);
return '$_temp0';
}
}

View file

@ -322,6 +322,151 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get inboxMissingHint =>
'Your Inbox comes with the next sync, or once this device holds your account\'s keys.';
@override
String get repeatTitle => 'Repeat';
@override
String get repeatNone => 'Doesn\'t repeat';
@override
String get repeatCustom => 'Custom';
@override
String repeatEveryDay(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: 'Every $count days',
one: 'Every day',
);
return '$_temp0';
}
@override
String repeatEveryWeek(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: 'Every $count weeks',
one: 'Every week',
);
return '$_temp0';
}
@override
String repeatEveryMonth(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: 'Every $count months',
one: 'Every month',
);
return '$_temp0';
}
@override
String repeatEveryYear(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: 'Every $count years',
one: 'Every year',
);
return '$_temp0';
}
@override
String get repeatWeekdays => 'Every weekday';
@override
String repeatOn(String rule, String on) {
return '$rule on $on';
}
@override
String repeatMonthDay(int day) {
return 'day $day';
}
@override
String repeatMonthWeek(String nth, String weekday) {
String _temp0 = intl.Intl.selectLogic(
nth,
{
'1': 'first',
'2': 'second',
'3': 'third',
'4': 'fourth',
'other': 'last',
},
);
return 'the $_temp0 $weekday';
}
@override
String repeatFromCompletion(String rule) {
return '$rule, from when it\'s done';
}
@override
String repeatUntil(String rule, String day) {
return '$rule, until $day';
}
@override
String repeatLeft(String rule, int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count more times',
one: 'once more',
zero: 'this time only',
);
return '$rule, $_temp0';
}
@override
String get repeatEvery => 'Every';
@override
String get repeatUnitDays => 'Days';
@override
String get repeatUnitWeeks => 'Weeks';
@override
String get repeatUnitMonths => 'Months';
@override
String get repeatUnitYears => 'Years';
@override
String get repeatCountFromCompletion => 'Count from when it\'s done';
@override
String get repeatCountFromCompletionHint =>
'Not from its date: done late, it comes again as long after.';
@override
String get repeatEnds => 'Ends';
@override
String get repeatEndsNever => 'Never';
@override
String get repeatEndsOn => 'On a day';
@override
String repeatEndsAfter(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: 'After $count times',
one: 'After once',
);
return '$_temp0';
}
}
/// The translations for English, as used in the United Kingdom (`en_GB`).