feat(todo): pick a place on a map, for quick add and a task's Where (ADR 0033, D32)

The location picker searches by city or district and name through
PlaceSearchGateway, its results a list and pins on Train's OpenFreeMap map,
or takes a pin placed by hand; recent places, and None, Arrive or Leave.
TodoDependencies.places is the seam for the instance's search; until then
komoot's public Photon, in a debug build asked for it alone. Quick add has
`+` suggestions, an entry in its + menu and a pin button; the task sheet a
Where box; a task's row the place's name.

ReminderScheduler plans the places to watch into LocationReminderGateway,
a no-op off Android, which TodoPerson.dispose clears, and Settings' Reminders
says while the device may not watch them.

Whats-New: Tasks can have locations: type + or use the + menu, search by city and name or pick on the map, and be reminded on arriving or leaving (Android).
Whats-New-De: Aufgaben können Orte haben: Tippe + oder nutze das +-Menü, suche nach Stadt und Name oder wähle auf der Karte, und lass dich beim Ankommen oder Verlassen erinnern (Android).
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-05 15:23:53 +01:00
commit 77d6a5528c
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
5 changed files with 2540 additions and 18 deletions

View file

@ -273,5 +273,30 @@
"planGroupShow": "{group} einblenden",
"planAllProjects": "Alle Projekte",
"planProjectsCount": "{count, plural, =1{1 Projekt} other{{count} Projekte}}",
"planFilterTitle": "Nach Projekt filtern"
"planFilterTitle": "Nach Projekt filtern",
"locationTitle": "Ort",
"locationAdd": "Ort hinzufügen",
"locationWhere": "Wo",
"locationAnyOf": "Einer davon",
"locationSearchHint": "Stadt oder Stadtteil, dann der Name",
"locationSearchAny": "Ort suchen",
"locationSearchFor": "„{query}“ suchen",
"locationSearchUnavailable": "Die Suche ist hier nicht verfügbar. Verschiebe die Karte und tippe, um einen Pin zu setzen.",
"locationSearchFailed": "Die Suche hat nicht geklappt. Versuch es noch einmal.",
"locationNoResults": "Nichts gefunden",
"locationRecent": "Zuletzt verwendet",
"locationPin": "Gesetzter Ort",
"locationName": "Name",
"locationMine": "Mein Standort",
"locationRemindNone": "Keine Erinnerung",
"locationRemindArrive": "Beim Ankommen",
"locationRemindLeave": "Beim Verlassen",
"locationRemove": "Entfernen",
"locationAccessTitle": "Standort immer erlauben",
"locationAccessHint": "Damit Halkyon Todo dich beim Ankommen oder Verlassen erinnert, braucht es deinen Standort auch, wenn es geschlossen ist. Er bleibt auf diesem Gerät. Wähle als Nächstes „Immer zulassen“.",
"locationAccessGo": "Weiter",
"locationOffTitle": "Standort ist für Erinnerungen aus",
"locationOffHint": "Erinnerungen an einem Ort brauchen „Immer zulassen“.",
"locationArrived": "Du bist bei {name}",
"locationLeft": "Du hast {name} verlassen"
}

View file

@ -910,5 +910,120 @@
"planFilterTitle": "Filter by project",
"@planFilterTitle": {
"description": "Today's Plan's project filter (ADR 0033, D30): its search field's hint and its name."
},
"locationTitle": "Location",
"@locationTitle": {
"description": "Quick add's + menu and its button (ADR 0033, D32)."
},
"locationAdd": "Add location",
"@locationAdd": {
"description": "A task's Where, and the picker's button."
},
"locationWhere": "Where",
"@locationWhere": {
"description": "The heading of a task's places."
},
"locationAnyOf": "Any of these",
"@locationAnyOf": {
"description": "Under a task's Where, with several places: they are alternatives."
},
"locationSearchHint": "City or district, then the name",
"@locationSearchHint": {
"description": "The location picker's search field."
},
"locationSearchAny": "Search for a place",
"@locationSearchAny": {
"description": "Quick add, as + is typed."
},
"locationSearchFor": "Search for “{query}”",
"@locationSearchFor": {
"description": "Quick add, as +something is typed.",
"placeholders": {
"query": {
"type": "String"
}
}
},
"locationSearchUnavailable": "Search isn't available here. Move the map and tap to place a pin.",
"@locationSearchUnavailable": {
"description": "The location picker without a place search."
},
"locationSearchFailed": "The search didn't work. Try again.",
"@locationSearchFailed": {
"description": "The location picker after a failed search."
},
"locationNoResults": "Nothing found",
"@locationNoResults": {
"description": "The location picker, a search without results."
},
"locationRecent": "Recent",
"@locationRecent": {
"description": "The location picker's places of the person's tasks."
},
"locationPin": "Pinned place",
"@locationPin": {
"description": "The name of a pin placed by hand where nothing names the spot."
},
"locationName": "Name",
"@locationName": {
"description": "The name field of a pin placed by hand."
},
"locationMine": "My position",
"@locationMine": {
"description": "The map's button."
},
"locationRemindNone": "No reminder",
"@locationRemindNone": {
"description": "A location's reminder."
},
"locationRemindArrive": "On arriving",
"@locationRemindArrive": {
"description": "A location's reminder."
},
"locationRemindLeave": "On leaving",
"@locationRemindLeave": {
"description": "A location's reminder."
},
"locationRemove": "Remove",
"@locationRemove": {
"description": "A task's location's menu."
},
"locationAccessTitle": "Allow location all the time",
"@locationAccessTitle": {
"description": "Before asking for the position in the background."
},
"locationAccessHint": "To remind you when you arrive or leave, Halkyon Todo needs your position while it is closed too. It stays on this device. Next, choose “Allow all the time”.",
"@locationAccessHint": {
"description": "Before asking for the position in the background."
},
"locationAccessGo": "Continue",
"@locationAccessGo": {
"description": "Before asking for the position in the background."
},
"locationOffTitle": "Location is off for reminders",
"@locationOffTitle": {
"description": "Settings' Reminders while places are planned and may not be watched."
},
"locationOffHint": "Reminders at a place need “Allow all the time”.",
"@locationOffHint": {
"description": "Settings' Reminders while places are planned and may not be watched."
},
"locationArrived": "You're at {name}",
"@locationArrived": {
"description": "A reminder on arriving, below the task's title.",
"placeholders": {
"name": {
"type": "String"
}
}
},
"locationLeft": "You've left {name}",
"@locationLeft": {
"description": "A reminder on leaving, below the task's title.",
"placeholders": {
"name": {
"type": "String"
}
}
}
}

View file

@ -1743,6 +1743,156 @@ abstract class AppLocalizations {
/// In en, this message translates to:
/// **'Filter by project'**
String get planFilterTitle;
/// Quick add's + menu and its button (ADR 0033, D32).
///
/// In en, this message translates to:
/// **'Location'**
String get locationTitle;
/// A task's Where, and the picker's button.
///
/// In en, this message translates to:
/// **'Add location'**
String get locationAdd;
/// The heading of a task's places.
///
/// In en, this message translates to:
/// **'Where'**
String get locationWhere;
/// Under a task's Where, with several places: they are alternatives.
///
/// In en, this message translates to:
/// **'Any of these'**
String get locationAnyOf;
/// The location picker's search field.
///
/// In en, this message translates to:
/// **'City or district, then the name'**
String get locationSearchHint;
/// Quick add, as + is typed.
///
/// In en, this message translates to:
/// **'Search for a place'**
String get locationSearchAny;
/// Quick add, as +something is typed.
///
/// In en, this message translates to:
/// **'Search for “{query}”'**
String locationSearchFor(String query);
/// The location picker without a place search.
///
/// In en, this message translates to:
/// **'Search isn\'t available here. Move the map and tap to place a pin.'**
String get locationSearchUnavailable;
/// The location picker after a failed search.
///
/// In en, this message translates to:
/// **'The search didn\'t work. Try again.'**
String get locationSearchFailed;
/// The location picker, a search without results.
///
/// In en, this message translates to:
/// **'Nothing found'**
String get locationNoResults;
/// The location picker's places of the person's tasks.
///
/// In en, this message translates to:
/// **'Recent'**
String get locationRecent;
/// The name of a pin placed by hand where nothing names the spot.
///
/// In en, this message translates to:
/// **'Pinned place'**
String get locationPin;
/// The name field of a pin placed by hand.
///
/// In en, this message translates to:
/// **'Name'**
String get locationName;
/// The map's button.
///
/// In en, this message translates to:
/// **'My position'**
String get locationMine;
/// A location's reminder.
///
/// In en, this message translates to:
/// **'No reminder'**
String get locationRemindNone;
/// A location's reminder.
///
/// In en, this message translates to:
/// **'On arriving'**
String get locationRemindArrive;
/// A location's reminder.
///
/// In en, this message translates to:
/// **'On leaving'**
String get locationRemindLeave;
/// A task's location's menu.
///
/// In en, this message translates to:
/// **'Remove'**
String get locationRemove;
/// Before asking for the position in the background.
///
/// In en, this message translates to:
/// **'Allow location all the time'**
String get locationAccessTitle;
/// Before asking for the position in the background.
///
/// In en, this message translates to:
/// **'To remind you when you arrive or leave, Halkyon Todo needs your position while it is closed too. It stays on this device. Next, choose “Allow all the time”.'**
String get locationAccessHint;
/// Before asking for the position in the background.
///
/// In en, this message translates to:
/// **'Continue'**
String get locationAccessGo;
/// Settings' Reminders while places are planned and may not be watched.
///
/// In en, this message translates to:
/// **'Location is off for reminders'**
String get locationOffTitle;
/// Settings' Reminders while places are planned and may not be watched.
///
/// In en, this message translates to:
/// **'Reminders at a place need “Allow all the time”.'**
String get locationOffHint;
/// A reminder on arriving, below the task's title.
///
/// In en, this message translates to:
/// **'You\'re at {name}'**
String locationArrived(String name);
/// A reminder on leaving, below the task's title.
///
/// In en, this message translates to:
/// **'You\'ve left {name}'**
String locationLeft(String name);
}
class _AppLocalizationsDelegate

View file

@ -1082,4 +1082,89 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get planFilterTitle => 'Nach Projekt filtern';
@override
String get locationTitle => 'Ort';
@override
String get locationAdd => 'Ort hinzufügen';
@override
String get locationWhere => 'Wo';
@override
String get locationAnyOf => 'Einer davon';
@override
String get locationSearchHint => 'Stadt oder Stadtteil, dann der Name';
@override
String get locationSearchAny => 'Ort suchen';
@override
String locationSearchFor(String query) {
return '„$query“ suchen';
}
@override
String get locationSearchUnavailable =>
'Die Suche ist hier nicht verfügbar. Verschiebe die Karte und tippe, um einen Pin zu setzen.';
@override
String get locationSearchFailed =>
'Die Suche hat nicht geklappt. Versuch es noch einmal.';
@override
String get locationNoResults => 'Nichts gefunden';
@override
String get locationRecent => 'Zuletzt verwendet';
@override
String get locationPin => 'Gesetzter Ort';
@override
String get locationName => 'Name';
@override
String get locationMine => 'Mein Standort';
@override
String get locationRemindNone => 'Keine Erinnerung';
@override
String get locationRemindArrive => 'Beim Ankommen';
@override
String get locationRemindLeave => 'Beim Verlassen';
@override
String get locationRemove => 'Entfernen';
@override
String get locationAccessTitle => 'Standort immer erlauben';
@override
String get locationAccessHint =>
'Damit Halkyon Todo dich beim Ankommen oder Verlassen erinnert, braucht es deinen Standort auch, wenn es geschlossen ist. Er bleibt auf diesem Gerät. Wähle als Nächstes „Immer zulassen“.';
@override
String get locationAccessGo => 'Weiter';
@override
String get locationOffTitle => 'Standort ist für Erinnerungen aus';
@override
String get locationOffHint =>
'Erinnerungen an einem Ort brauchen „Immer zulassen“.';
@override
String locationArrived(String name) {
return 'Du bist bei $name';
}
@override
String locationLeft(String name) {
return 'Du hast $name verlassen';
}
}

View file

@ -1082,6 +1082,90 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get planFilterTitle => 'Filter by project';
@override
String get locationTitle => 'Location';
@override
String get locationAdd => 'Add location';
@override
String get locationWhere => 'Where';
@override
String get locationAnyOf => 'Any of these';
@override
String get locationSearchHint => 'City or district, then the name';
@override
String get locationSearchAny => 'Search for a place';
@override
String locationSearchFor(String query) {
return 'Search for “$query”';
}
@override
String get locationSearchUnavailable =>
'Search isn\'t available here. Move the map and tap to place a pin.';
@override
String get locationSearchFailed => 'The search didn\'t work. Try again.';
@override
String get locationNoResults => 'Nothing found';
@override
String get locationRecent => 'Recent';
@override
String get locationPin => 'Pinned place';
@override
String get locationName => 'Name';
@override
String get locationMine => 'My position';
@override
String get locationRemindNone => 'No reminder';
@override
String get locationRemindArrive => 'On arriving';
@override
String get locationRemindLeave => 'On leaving';
@override
String get locationRemove => 'Remove';
@override
String get locationAccessTitle => 'Allow location all the time';
@override
String get locationAccessHint =>
'To remind you when you arrive or leave, Halkyon Todo needs your position while it is closed too. It stays on this device. Next, choose “Allow all the time”.';
@override
String get locationAccessGo => 'Continue';
@override
String get locationOffTitle => 'Location is off for reminders';
@override
String get locationOffHint =>
'Reminders at a place need “Allow all the time”.';
@override
String locationArrived(String name) {
return 'You\'re at $name';
}
@override
String locationLeft(String name) {
return 'You\'ve left $name';
}
}
/// The translations for English, as used in the United Kingdom (`en_GB`).