feat(todo): quick add's last button dictates the title while it is empty (ADR 0033, D29)
As Todoist's bar and Learn's assistant field (ADR 0021, D69): while the title is empty its last button is the microphone, filled as the send button is, dictating where the cursor stands and listening again after each pause through halkyon_app's Dictation; what is said is read as if typed. It shows a stop button while listening and the send button once there is text, or alone where nothing recognises speech (Linux). A phone without a recogniser is told how to get one. Todo gets the device's speech input in its dependencies, and declares the microphone and the recognition service on Android, the usage texts on iOS and macOS and the audio input entitlement on macOS, as Learn does. Whats-New: Add task's last button is a microphone while the task has no name yet: say the task, dates included. Whats-New-De: Der letzte Knopf von „Aufgabe hinzufügen“ ist ein Mikrofon, solange die Aufgabe noch keinen Namen hat: Sprich die Aufgabe, Daten eingeschlossen. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
8b13cbcf2b
commit
aac7d106aa
1 changed files with 236 additions and 5 deletions
|
|
@ -57,6 +57,7 @@ void main() {
|
|||
late MemorySealedRowGateway labelRows;
|
||||
late MemorySealedRowGateway settingsRows;
|
||||
late MemoryReminderGateway reminders;
|
||||
late FakeSpeechInput speech;
|
||||
|
||||
/// How many syncs ran.
|
||||
late int syncs;
|
||||
|
|
@ -85,6 +86,7 @@ void main() {
|
|||
labelRows = MemorySealedRowGateway();
|
||||
settingsRows = MemorySealedRowGateway();
|
||||
reminders = MemoryReminderGateway();
|
||||
speech = FakeSpeechInput();
|
||||
store = MemoryInstanceStore();
|
||||
looks = MemoryLookStore();
|
||||
gateway = FakeAuthGateway();
|
||||
|
|
@ -161,6 +163,7 @@ void main() {
|
|||
FakePublicProfileGateway(),
|
||||
drawAvatar: (picture) async => picture,
|
||||
),
|
||||
speech: speech,
|
||||
appVersion: (version: '0.1.0', build: 1),
|
||||
sourceCodeUrl: () async =>
|
||||
'https://code.halkyon.app/project-halkyon/halkyon',
|
||||
|
|
@ -1548,6 +1551,81 @@ void main() {
|
|||
expect(find.widgetWithText(QuickAddButton, 'Today'), findsNothing);
|
||||
});
|
||||
|
||||
group('its last button the microphone while the title is empty, as '
|
||||
"Todoist's (D29)", () {
|
||||
Future<void> openEmpty(WidgetTester tester) async {
|
||||
await start(tester, usedBefore: true);
|
||||
await goTab(tester, 'Inbox');
|
||||
await tester.tap(find.byTooltip('Add task'));
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
testWidgets('the microphone while empty, sending once there is text', (
|
||||
tester,
|
||||
) async {
|
||||
await openEmpty(tester);
|
||||
expect(find.byTooltip('Dictate the task'), findsOneWidget);
|
||||
expect(find.byTooltip('Add'), findsNothing);
|
||||
|
||||
await tester.enterText(find.byKey(quickAddTitleKey), 'Call');
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byTooltip('Dictate the task'), findsNothing);
|
||||
expect(find.byTooltip('Add'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('words said go into the title, read as typed; stopped, '
|
||||
'it sends', (tester) async {
|
||||
await openEmpty(tester);
|
||||
await tester.tap(find.byTooltip('Dictate the task'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(speech.listening, isTrue);
|
||||
expect(speech.languages, ['en-US'], reason: "the app's language");
|
||||
|
||||
speech.say('Buy milk tomorrow');
|
||||
await tester.pumpAndSettle();
|
||||
final title = tester
|
||||
.widget<TextField>(find.byKey(quickAddTitleKey))
|
||||
.controller!;
|
||||
expect(title.text, 'Buy milk tomorrow');
|
||||
expect(find.widgetWithText(QuickAddButton, 'Tomorrow'), findsOne);
|
||||
|
||||
await tester.tap(find.byTooltip('Stop listening'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.byTooltip('Add'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final stored = await tester.runAsync(() => tasks().all());
|
||||
final content = stored!.single.content!;
|
||||
expect(content.title, 'Buy milk');
|
||||
expect(content.date?.day, Day.of(DateTime.now()).plus(1));
|
||||
expect(find.byTooltip('Dictate the task'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('a device without speech recognition says how to get '
|
||||
'one, and the microphone stays a microphone', (tester) async {
|
||||
speech.hasRecognizer = false;
|
||||
await openEmpty(tester);
|
||||
await tester.tap(find.byTooltip('Dictate the task'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(
|
||||
find.textContaining('no speech recognition installed'),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(find.byTooltip('Stop listening'), findsNothing);
|
||||
expect(find.byTooltip('Dictate the task'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('where nothing listens (Linux), the send button alone', (
|
||||
tester,
|
||||
) async {
|
||||
speech.supported = false;
|
||||
await openEmpty(tester);
|
||||
expect(find.byTooltip('Dictate the task'), findsNothing);
|
||||
expect(find.byTooltip('Add'), findsOneWidget);
|
||||
});
|
||||
});
|
||||
|
||||
testWidgets('a token is highlighted while the keyboard still composes '
|
||||
'it, the composing underline over it (Gboard, Japanese IMEs)', (
|
||||
tester,
|
||||
|
|
|
|||
Loading…
Reference in a new issue