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
This commit is contained in:
@@ -1,12 +1,170 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
class SettingsScreen extends StatelessWidget {
|
||||
import '../../core/export/export_service.dart';
|
||||
import '../../core/notifications/notification_service.dart';
|
||||
import '../../core/providers/biometric_provider.dart';
|
||||
import '../../core/providers/database_provider.dart';
|
||||
import '../../core/providers/settings_provider.dart';
|
||||
|
||||
class SettingsScreen extends ConsumerWidget {
|
||||
const SettingsScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Scaffold(
|
||||
body: Center(child: Text('Settings coming soon')),
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final reminderEnabled =
|
||||
ref.watch(settingProvider(SettingsKeys.reminderEnabled));
|
||||
final reminderTimeStr =
|
||||
ref.watch(settingProvider(SettingsKeys.reminderTime));
|
||||
final biometricEnabled =
|
||||
ref.watch(settingProvider(SettingsKeys.biometricEnabled));
|
||||
final themeVal = ref.watch(settingProvider(SettingsKeys.themMode));
|
||||
final biometricAvailable = ref.watch(biometricAvailableProvider);
|
||||
final notifier = ref.read(settingsNotifierProvider.notifier);
|
||||
|
||||
final isReminderOn = reminderEnabled.value == 'true';
|
||||
final currentTheme = themeVal.value ?? 'system';
|
||||
final isBiometricOn = biometricEnabled.value == 'true';
|
||||
final reminderTime =
|
||||
_parseTime(reminderTimeStr.value) ?? const TimeOfDay(hour: 20, minute: 0);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Settings')),
|
||||
body: ListView(
|
||||
children: [
|
||||
_section(context, 'Appearance'),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
child: SegmentedButton<String>(
|
||||
segments: const [
|
||||
ButtonSegment(
|
||||
value: 'system',
|
||||
label: Text('System'),
|
||||
icon: Icon(Icons.brightness_auto)),
|
||||
ButtonSegment(
|
||||
value: 'light',
|
||||
label: Text('Light'),
|
||||
icon: Icon(Icons.light_mode)),
|
||||
ButtonSegment(
|
||||
value: 'dark',
|
||||
label: Text('Dark'),
|
||||
icon: Icon(Icons.dark_mode)),
|
||||
],
|
||||
selected: {currentTheme},
|
||||
onSelectionChanged: (s) => notifier.setThemeMode(s.first),
|
||||
),
|
||||
),
|
||||
_section(context, 'Reminders'),
|
||||
SwitchListTile(
|
||||
title: const Text('Daily reminder'),
|
||||
subtitle: const Text('Get prompted to log your mood'),
|
||||
value: isReminderOn,
|
||||
onChanged: (v) =>
|
||||
_toggleReminder(context, ref, v, reminderTime, notifier),
|
||||
),
|
||||
if (isReminderOn)
|
||||
ListTile(
|
||||
leading: const Icon(Icons.schedule),
|
||||
title: const Text('Reminder time'),
|
||||
trailing: Text(
|
||||
reminderTime.format(context),
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
onTap: () => _pickTime(context, reminderTime, notifier),
|
||||
),
|
||||
if (biometricAvailable.value == true) ...[
|
||||
_section(context, 'Privacy'),
|
||||
SwitchListTile(
|
||||
title: const Text('Biometric lock'),
|
||||
subtitle:
|
||||
const Text('Require fingerprint or face unlock to open'),
|
||||
value: isBiometricOn,
|
||||
onChanged: (v) => notifier.setBiometricEnabled(v),
|
||||
),
|
||||
],
|
||||
_section(context, 'Data'),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.picture_as_pdf_outlined),
|
||||
title: const Text('Export to PDF'),
|
||||
subtitle: const Text('Share your complete mood history'),
|
||||
onTap: () => _export(context, ref),
|
||||
),
|
||||
const Divider(),
|
||||
const ListTile(
|
||||
title: Text('DailyYou'),
|
||||
subtitle: Text('Version 1.0.0'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _section(BuildContext context, String title) => Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 16, 16, 4),
|
||||
child: Text(
|
||||
title,
|
||||
style: Theme.of(context).textTheme.labelLarge?.copyWith(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
TimeOfDay? _parseTime(String? val) {
|
||||
if (val == null) return null;
|
||||
final parts = val.split(':');
|
||||
if (parts.length != 2) return null;
|
||||
final h = int.tryParse(parts[0]);
|
||||
final m = int.tryParse(parts[1]);
|
||||
if (h == null || m == null) return null;
|
||||
return TimeOfDay(hour: h, minute: m);
|
||||
}
|
||||
|
||||
Future<void> _toggleReminder(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
bool enable,
|
||||
TimeOfDay time,
|
||||
SettingsNotifier notifier,
|
||||
) async {
|
||||
if (enable) {
|
||||
final granted = await NotificationService.requestPermission();
|
||||
if (!context.mounted) return;
|
||||
if (!granted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content:
|
||||
Text('Notification permission required to set reminders')),
|
||||
);
|
||||
return;
|
||||
}
|
||||
await NotificationService.scheduleDaily(time);
|
||||
} else {
|
||||
await NotificationService.cancelAll();
|
||||
}
|
||||
await notifier.setReminderEnabled(enable);
|
||||
}
|
||||
|
||||
Future<void> _pickTime(
|
||||
BuildContext context,
|
||||
TimeOfDay current,
|
||||
SettingsNotifier notifier,
|
||||
) async {
|
||||
final picked =
|
||||
await showTimePicker(context: context, initialTime: current);
|
||||
if (!context.mounted || picked == null) return;
|
||||
await notifier.setReminderTime(picked.hour, picked.minute);
|
||||
await NotificationService.scheduleDaily(picked);
|
||||
}
|
||||
|
||||
Future<void> _export(BuildContext context, WidgetRef ref) async {
|
||||
try {
|
||||
final entries = await ref.read(moodDaoProvider).getAllEntries();
|
||||
await ExportService.exportToPdf(entries);
|
||||
} catch (e) {
|
||||
if (!context.mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Export failed: $e')),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user