Files
stefwill 78aa032c1b feat: implement settings screen with reminders, biometrics, theme, and PDF export
- Daily reminder scheduling via flutter_local_notifications (inexact, repeating at chosen time)
- Biometric lock gate on app launch with auto-prompt and manual unlock button
- Theme mode selector (System/Light/Dark) persisted to settings DB
- PDF export of full mood history shared via share_plus
- Android: POST_NOTIFICATIONS, USE_BIOMETRIC permissions; switched to FlutterFragmentActivity
2026-04-26 15:49:52 +10:00

110 lines
3.3 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'core/providers/biometric_provider.dart';
import 'core/providers/model_download_provider.dart';
import 'core/providers/settings_provider.dart';
import 'core/providers/theme_provider.dart';
import 'features/model_download/model_download_screen.dart';
import 'shell.dart';
class App extends ConsumerWidget {
const App({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final skipped = ref.watch(modelSkippedProvider);
final modelPresentAsync = ref.watch(modelPresentProvider);
final themeModeAsync = ref.watch(themeModeProvider);
final biometricEnabled =
ref.watch(settingProvider(SettingsKeys.biometricEnabled));
final biometricUnlocked = ref.watch(biometricUnlockedProvider);
final themeMode = themeModeAsync.value ?? ThemeMode.system;
Widget mainContent() {
if (biometricEnabled.value == 'true' && !biometricUnlocked) {
return const _BiometricLockScreen();
}
return const AppShell();
}
return MaterialApp(
title: 'DailyYou',
debugShowCheckedModeBanner: false,
theme: _lightTheme(),
darkTheme: _darkTheme(),
themeMode: themeMode,
home: skipped
? mainContent()
: modelPresentAsync.when(
data: (present) =>
present ? mainContent() : const ModelDownloadScreen(),
loading: () => const Scaffold(
body: Center(child: CircularProgressIndicator()),
),
error: (_, __) => const ModelDownloadScreen(),
),
);
}
ThemeData _lightTheme() => ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(
seedColor: const Color(0xFF6B8CFF),
brightness: Brightness.light,
),
);
ThemeData _darkTheme() => ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(
seedColor: const Color(0xFF6B8CFF),
brightness: Brightness.dark,
),
);
}
class _BiometricLockScreen extends ConsumerStatefulWidget {
const _BiometricLockScreen();
@override
ConsumerState<_BiometricLockScreen> createState() =>
_BiometricLockScreenState();
}
class _BiometricLockScreenState extends ConsumerState<_BiometricLockScreen> {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
ref.read(biometricUnlockedProvider.notifier).tryUnlock();
});
}
@override
Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.lock_outline, size: 64, color: cs.primary),
const SizedBox(height: 24),
Text('DailyYou is locked',
style: Theme.of(context).textTheme.titleLarge),
const SizedBox(height: 16),
FilledButton.icon(
onPressed: () =>
ref.read(biometricUnlockedProvider.notifier).tryUnlock(),
icon: const Icon(Icons.fingerprint),
label: const Text('Unlock'),
),
],
),
),
);
}
}