mirror of
https://github.com/kuhyx/diet-guard.git
synced 2026-11-10 19:27:14 +01:00
Haptic on meal log, VIBRATE permission
Some checks are pending
dependency freshness / dependencies on newest stable (push) Waiting to run
file-length / file length <= 250 lines (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
pre-commit / pre-commit (${{ matrix.group }}) (mypy, mypy, ) (push) Waiting to run
pre-commit / pre-commit (${{ matrix.group }}) (pylint, pylint, ) (push) Waiting to run
pre-commit / pre-commit (${{ matrix.group }}) (rest, , mypy,pylint) (push) Waiting to run
Tests / test (push) Waiting to run
release-apk / release (APK) (push) Waiting to run
repo-contract / repo contract (push) Waiting to run
Some checks are pending
dependency freshness / dependencies on newest stable (push) Waiting to run
file-length / file length <= 250 lines (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
pre-commit / pre-commit (${{ matrix.group }}) (mypy, mypy, ) (push) Waiting to run
pre-commit / pre-commit (${{ matrix.group }}) (pylint, pylint, ) (push) Waiting to run
pre-commit / pre-commit (${{ matrix.group }}) (rest, , mypy,pylint) (push) Waiting to run
Tests / test (push) Waiting to run
release-apk / release (APK) (push) Waiting to run
repo-contract / repo contract (push) Waiting to run
Fires on the tap before the write; rejected logs stay silent. The dopamine-ux TODO now lists only the remaining work. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzxdWVUTaKFW8m7YrvWbzV
This commit is contained in:
parent
2a674774bb
commit
a30628ab96
@ -1,6 +1,8 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<!-- Sync (github_client.dart) needs raw socket access. -->
|
<!-- Sync (github_client.dart) needs raw socket access. -->
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
<!-- HapticFeedback on a successful meal log (log_meal_screen.dart). -->
|
||||||
|
<uses-permission android:name="android.permission.VIBRATE"/>
|
||||||
<!-- Android 13+ requires runtime opt-in to post notifications; requested
|
<!-- Android 13+ requires runtime opt-in to post notifications; requested
|
||||||
at launch via NotificationService.requestPermission(). -->
|
at launch via NotificationService.requestPermission(). -->
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||||
|
|||||||
@ -25,6 +25,7 @@ import 'package:diet_guard_app/widgets/macro_input_row.dart';
|
|||||||
import 'package:diet_guard_app/widgets/sync_health_banner.dart';
|
import 'package:diet_guard_app/widgets/sync_health_banner.dart';
|
||||||
import 'package:diet_guard_app/widgets/today_progress_card.dart';
|
import 'package:diet_guard_app/widgets/today_progress_card.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
/// Lets the user log one food item, with food-bank autocomplete and
|
/// Lets the user log one food item, with food-bank autocomplete and
|
||||||
@ -146,6 +147,8 @@ class _LogMealScreenState extends State<LogMealScreen>
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// On the tap, before the write/sync: waiting for them is just latency.
|
||||||
|
unawaited(HapticFeedback.mediumImpact());
|
||||||
await LogStorageService.instance.logMeal(
|
await LogStorageService.instance.logMeal(
|
||||||
desc,
|
desc,
|
||||||
nutritionFromControllers(_macros, _source),
|
nutritionFromControllers(_macros, _source),
|
||||||
|
|||||||
@ -2,7 +2,7 @@ name: diet_guard_app
|
|||||||
description: "Companion phone app for diet_guard: log meals on the go."
|
description: "Companion phone app for diet_guard: log meals on the go."
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
|
|
||||||
version: 1.0.127+1
|
version: 1.0.128+1
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.12.2
|
sdk: ^3.12.2
|
||||||
|
|||||||
79
app/test/screens/log_meal_haptic_test.dart
Normal file
79
app/test/screens/log_meal_haptic_test.dart
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:diet_guard_app/models/meal_schedule.dart';
|
||||||
|
import 'package:diet_guard_app/screens/log_meal_screen.dart';
|
||||||
|
import 'package:diet_guard_app/services/app_settings_service.dart';
|
||||||
|
import 'package:diet_guard_app/services/budget_history_service.dart';
|
||||||
|
import 'package:diet_guard_app/services/document_store_io.dart';
|
||||||
|
import 'package:diet_guard_app/services/foodbank_service.dart';
|
||||||
|
import 'package:diet_guard_app/services/log_storage_service.dart';
|
||||||
|
import 'package:diet_guard_app/services/meal_schedule_service.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
late Directory dir;
|
||||||
|
final haptics = <String>[];
|
||||||
|
|
||||||
|
setUp(() async {
|
||||||
|
haptics.clear();
|
||||||
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
||||||
|
.setMockMethodCallHandler(SystemChannels.platform, (call) async {
|
||||||
|
if (call.method == 'HapticFeedback.vibrate') {
|
||||||
|
haptics.add(call.arguments as String);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
dir = await Directory.systemTemp.createTemp('diet_guard_haptic_');
|
||||||
|
LogStorageService.resetForTesting(store: FileDocumentStore(dir));
|
||||||
|
FoodBankService.resetForTesting(store: FileDocumentStore(dir));
|
||||||
|
AppSettingsService.resetForTesting(store: FileDocumentStore(dir));
|
||||||
|
BudgetHistoryService.resetForTesting(store: FileDocumentStore(dir));
|
||||||
|
await MealScheduleService.initForTesting(FileDocumentStore(dir));
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDown(() async {
|
||||||
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
||||||
|
.setMockMethodCallHandler(SystemChannels.platform, null);
|
||||||
|
LogStorageService.resetForTesting();
|
||||||
|
FoodBankService.resetForTesting();
|
||||||
|
AppSettingsService.resetForTesting();
|
||||||
|
BudgetHistoryService.resetForTesting();
|
||||||
|
MealScheduleService.resetForTesting();
|
||||||
|
await dir.delete(recursive: true);
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<void> settle(WidgetTester tester) async {
|
||||||
|
await Future<void>.delayed(const Duration(milliseconds: 200));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
}
|
||||||
|
|
||||||
|
testWidgets('logging a meal buzzes once, on the tap', (tester) async {
|
||||||
|
await tester.runAsync(() async {
|
||||||
|
await tester.pumpWidget(const MaterialApp(home: LogMealScreen()));
|
||||||
|
await settle(tester);
|
||||||
|
await tester.enterText(find.byType(TextField).at(0), 'toast');
|
||||||
|
await settle(tester);
|
||||||
|
await tester.tap(find.byTooltip('Log meal'));
|
||||||
|
// One frame, no I/O wait: the haptic must not wait for the write.
|
||||||
|
await tester.pump();
|
||||||
|
expect(haptics, ['HapticFeedbackType.mediumImpact']);
|
||||||
|
await settle(tester);
|
||||||
|
expect(haptics, hasLength(1));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('a rejected log (empty description) does not buzz', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
|
await tester.runAsync(() async {
|
||||||
|
await tester.pumpWidget(const MaterialApp(home: LogMealScreen()));
|
||||||
|
await settle(tester);
|
||||||
|
await tester.tap(find.byTooltip('Log meal'));
|
||||||
|
await settle(tester);
|
||||||
|
expect(find.text('Type what you ate first.'), findsOneWidget);
|
||||||
|
expect(haptics, isEmpty);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -36,6 +36,22 @@ from the true state. Partial completion must never be recorded as DONE.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## status 2026-11-02 (audit session)
|
||||||
|
|
||||||
|
DONE and committed: the haptic (`HapticFeedback.mediumImpact()` fired on the
|
||||||
|
tap, before the write, only for a log that passed validation; test
|
||||||
|
`app/test/screens/log_meal_haptic_test.dart`) and the VIBRATE permission.
|
||||||
|
|
||||||
|
REMAINING (everything below this block that is not the haptic/VIBRATE):
|
||||||
|
- needs kuhy: approved text for the explicit confirmation (the old
|
||||||
|
`Logged "<meal>".` line was removed on purpose 2026-08-16, so the wording
|
||||||
|
and whether to bring it back is his call), and the sound toggle label.
|
||||||
|
- animated progress card / count-up numbers using motion tokens
|
||||||
|
(`~/src/utils/unified-design-system/DOCS-motion.md`), honouring
|
||||||
|
`disableAnimations`.
|
||||||
|
- step 2 sound: audio dependency, asset, opt-out `SwitchListTile`.
|
||||||
|
- on-phone check (haptic feel, remove-animations toggle): needs kuhy.
|
||||||
|
|
||||||
Make logging a meal feel satisfying in diet_guard_app
|
Make logging a meal feel satisfying in diet_guard_app
|
||||||
|
|
||||||
## findings (4, ranked by value over effort)
|
## findings (4, ranked by value over effort)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user