This commit is contained in:
Courtney Arnold 2026-08-15 19:23:03 -05:00
parent 956231048e
commit 606106e5ec
10 changed files with 632 additions and 1 deletions

View file

@ -24,6 +24,7 @@ const _prefsKeyKeepReceiptPhotosLocally = 'keep_receipt_photos_locally';
const _prefsKeyStaleLockMinutes = 'stale_lock_minutes';
const _prefsKeyKeepMaxQualityReceiptPhotos = 'keep_max_quality_receipt_photos';
const _prefsKeyThemeMode = 'theme_mode';
const _prefsKeyHasSeenOnboardingTour = 'has_seen_onboarding_tour';
const defaultStaleLockMinutes = 10;
const minStaleLockMinutes = 1;
@ -89,6 +90,17 @@ class AppState extends ChangeNotifier {
ThemeMode themeMode = ThemeMode.system;
/// Whether the first-launch guided tour ([OnboardingTour]) has already
/// been shown. Defaults to `true` here (not `false`) specifically so
/// that widget tests constructing `AppState()` directly and skipping
/// [init] — the established pattern throughout this app's test suite,
/// to avoid init()'s database/platform-channel dependencies — don't get
/// an unexpected full-screen tour overlay blocking every tap. [init]
/// overwrites this from the persisted value (defaulting to `false`
/// there) for real app startups, where a missing pref genuinely means
/// "never shown before."
bool hasSeenOnboardingTour = true;
/// Set if [init] fails. The UI shows this (with a retry option) instead
/// of spinning forever — an unhandled exception here previously left
/// `isLoading` stuck at true with no feedback at all.
@ -117,6 +129,7 @@ class AppState extends ChangeNotifier {
(mode) => mode.name == prefs.getString(_prefsKeyThemeMode),
orElse: () => ThemeMode.system,
);
hasSeenOnboardingTour = prefs.getBool(_prefsKeyHasSeenOnboardingTour) ?? false;
} catch (e) {
initError = e;
isLoading = false;
@ -304,6 +317,15 @@ class AppState extends ChangeNotifier {
notifyListeners();
}
/// Called once the first-launch guided tour finishes or is skipped, so
/// it never shows again on this device.
Future<void> markOnboardingTourSeen() async {
hasSeenOnboardingTour = true;
final prefs = await SharedPreferences.getInstance();
await prefs.setBool(_prefsKeyHasSeenOnboardingTour, true);
notifyListeners();
}
/// Clamped to [minStaleLockMinutes, maxStaleLockMinutes] — see the
/// Settings "Advanced" section, which restricts the picker to that range
/// anyway; this is a defensive backstop for any other caller.