CoffeeAtHome/lib/screens/settings_screen.dart
2026-03-29 08:13:38 -07:00

633 lines
22 KiB
Dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/app_state.dart';
import '../models/bean.dart';
import '../models/machine.dart';
import '../models/recipe.dart';
import '../models/drink.dart';
import '../models/journal_entry.dart';
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
class SettingsScreen extends StatefulWidget {
const SettingsScreen({super.key});
@override
State<SettingsScreen> createState() => _SettingsScreenState();
}
class _SettingsScreenState extends State<SettingsScreen> {
bool _notificationsEnabled = true;
bool _darkTheme = true;
String _selectedLanguage = 'English';
final List<String> _languages = ['English', 'Spanish', 'French', 'German', 'Italian'];
@override
void initState() {
super.initState();
_loadSettings();
}
Future<void> _loadSettings() async {
final prefs = await SharedPreferences.getInstance();
setState(() {
_notificationsEnabled = prefs.getBool('notifications_enabled') ?? true;
_darkTheme = prefs.getBool('dark_theme') ?? true;
_selectedLanguage = prefs.getString('selected_language') ?? 'English';
});
}
Future<void> _saveSettings() async {
final prefs = await SharedPreferences.getInstance();
await prefs.setBool('notifications_enabled', _notificationsEnabled);
await prefs.setBool('dark_theme', _darkTheme);
await prefs.setString('selected_language', _selectedLanguage);
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Settings', style: Theme.of(context).textTheme.headlineMedium),
const SizedBox(height: 24),
Expanded(
child: ListView(
children: [
_buildSettingsSection(context, 'Data', [
ListTile(
leading: const Icon(Icons.backup),
title: const Text('Export Data'),
subtitle: const Text('Export your coffee data to JSON'),
onTap: () => _exportData(context),
),
ListTile(
leading: const Icon(Icons.restore),
title: const Text('Import Data'),
subtitle: const Text('Import coffee data from JSON'),
onTap: () => _importData(context),
),
ListTile(
leading: const Icon(Icons.delete_forever),
title: const Text('Clear All Data'),
subtitle: const Text('Delete all your coffee data'),
onTap: () => _showClearDataDialog(context),
),
ListTile(
leading: const Icon(Icons.refresh),
title: const Text('Reset with Sample Data'),
subtitle: const Text('Clear all data and add sample entries'),
onTap: () => _showReseedDataDialog(context),
),
]),
const SizedBox(height: 24),
_buildSettingsSection(context, 'Preferences', [
SwitchListTile(
secondary: const Icon(Icons.notifications),
title: const Text('Notifications'),
subtitle: const Text('Enable app notifications'),
value: _notificationsEnabled,
onChanged: (value) {
setState(() {
_notificationsEnabled = value;
});
_saveSettings();
},
),
ListTile(
leading: const Icon(Icons.language),
title: const Text('Language'),
subtitle: Text(_selectedLanguage),
onTap: () => _showLanguageDialog(context),
),
SwitchListTile(
secondary: const Icon(Icons.palette),
title: const Text('Dark Theme'),
subtitle: const Text('Use dark color scheme'),
value: _darkTheme,
onChanged: (value) {
setState(() {
_darkTheme = value;
});
_saveSettings();
_showThemeChangeMessage(context);
},
),
]),
const SizedBox(height: 24),
_buildSettingsSection(context, 'Statistics', [
ListTile(
leading: const Icon(Icons.analytics),
title: const Text('View Statistics'),
subtitle: const Text('See your coffee data insights'),
onTap: () => _showStatisticsDialog(context),
),
ListTile(
leading: const Icon(Icons.refresh),
title: const Text('Refresh Data'),
subtitle: const Text('Reload all data from storage'),
onTap: () => _refreshData(context),
),
]),
const SizedBox(height: 24),
_buildSettingsSection(context, 'About', [
ListTile(
leading: const Icon(Icons.info),
title: const Text('About Coffee at Home'),
subtitle: const Text('Version 1.0.0'),
onTap: () => _showAboutDialog(context),
),
ListTile(
leading: const Icon(Icons.help),
title: const Text('Help & Support'),
subtitle: const Text('Get help using the app'),
onTap: () => _showHelpDialog(context),
),
ListTile(
leading: const Icon(Icons.star),
title: const Text('Rate App'),
subtitle: const Text('Rate us on the app store'),
onTap: () => _showRateDialog(context),
),
]),
],
),
),
],
),
);
}
Widget _buildSettingsSection(
BuildContext context,
String title,
List<Widget> children,
) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: Theme.of(context).textTheme.titleLarge),
const SizedBox(height: 8),
Card(child: Column(children: children)),
],
);
}
void _exportData(BuildContext context) async {
try {
final appState = Provider.of<AppState>(context, listen: false);
final data = {
'beans': appState.beans.map((e) => e.toJson()).toList(),
'machines': appState.machines.map((e) => e.toJson()).toList(),
'recipes': appState.recipes.map((e) => e.toJson()).toList(),
'drinks': appState.drinks.map((e) => e.toJson()).toList(),
'journalEntries': appState.journalEntries.map((e) => e.toJson()).toList(),
'exportDate': DateTime.now().toIso8601String(),
'version': '1.0.0',
};
final jsonString = const JsonEncoder.withIndent(' ').convert(data);
// Show the JSON data to user
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Export Data'),
content: SingleChildScrollView(
child: SelectableText(
jsonString,
style: const TextStyle(fontFamily: 'monospace', fontSize: 12),
),
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('Close'),
),
],
),
);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Data exported successfully! Copy the JSON text to save.'),
),
);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Export failed: $e')),
);
}
}
void _importData(BuildContext context) {
showDialog(
context: context,
builder: (context) {
final controller = TextEditingController();
return AlertDialog(
title: const Text('Import Data'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text('Paste your exported JSON data below:'),
const SizedBox(height: 16),
TextField(
controller: controller,
decoration: const InputDecoration(
hintText: 'Paste JSON data here...',
border: OutlineInputBorder(),
),
maxLines: 10,
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('Cancel'),
),
ElevatedButton(
onPressed: () async {
final navigator = Navigator.of(context);
final scaffoldMessenger = ScaffoldMessenger.of(context);
final appState = Provider.of<AppState>(context, listen: false);
try {
final jsonData = jsonDecode(controller.text) as Map<String, dynamic>;
// Validate the imported data structure
if (!jsonData.containsKey('beans') || !jsonData.containsKey('machines') ||
!jsonData.containsKey('recipes') || !jsonData.containsKey('journalEntries') ||
!jsonData.containsKey('drinks')) {
throw Exception('Invalid data format: Missing required fields');
}
// Clear existing data first
final prefs = await SharedPreferences.getInstance();
await prefs.clear();
// Import beans
final beansData = jsonData['beans'] as List;
for (final beanJson in beansData) {
final bean = Bean.fromJson(beanJson);
await appState.addBean(bean);
}
// Import machines
final machinesData = jsonData['machines'] as List;
for (final machineJson in machinesData) {
final machine = Machine.fromJson(machineJson);
await appState.addMachine(machine);
}
// Import recipes
final recipesData = jsonData['recipes'] as List;
for (final recipeJson in recipesData) {
final recipe = Recipe.fromJson(recipeJson);
await appState.addRecipe(recipe);
}
// Import drinks
final drinksData = jsonData['drinks'] as List;
for (final drinkJson in drinksData) {
final drink = Drink.fromJson(drinkJson);
await appState.addDrink(drink);
}
// Import journal entries
final journalData = jsonData['journalEntries'] as List;
for (final journalJson in journalData) {
final journal = JournalEntry.fromJson(journalJson);
await appState.addJournalEntry(journal);
}
if (mounted) {
navigator.pop();
scaffoldMessenger.showSnackBar(
SnackBar(
content: Text('Successfully imported ${beansData.length} beans, ${machinesData.length} machines, ${recipesData.length} recipes, ${journalData.length} journal entries!'),
backgroundColor: Colors.green,
),
);
}
} catch (e) {
if (mounted) {
scaffoldMessenger.showSnackBar(
SnackBar(
content: Text('Import failed: $e'),
backgroundColor: Colors.red,
),
);
}
}
},
child: const Text('Import'),
),
],
);
},
);
}
void _showClearDataDialog(BuildContext context) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Clear All Data'),
content: const Text(
'Are you sure you want to delete all your coffee data? This action cannot be undone.',
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('Cancel'),
),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
foregroundColor: Colors.white,
),
onPressed: () async {
final navigator = Navigator.of(context);
final scaffoldMessenger = ScaffoldMessenger.of(context);
final appState = Provider.of<AppState>(context, listen: false);
try {
// Clear all data using the app state
await appState.clearAllData();
if (mounted) {
navigator.pop();
scaffoldMessenger.showSnackBar(
const SnackBar(
content: Text('All data cleared successfully!'),
),
);
}
} catch (e) {
if (mounted) {
navigator.pop();
scaffoldMessenger.showSnackBar(
SnackBar(content: Text('Failed to clear data: $e')),
);
}
}
},
child: const Text('Clear Data'),
),
],
);
},
);
}
void _showReseedDataDialog(BuildContext context) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Reset with Sample Data'),
content: const Text(
'This will clear all your current data and add sample coffee entries to get you started. This action cannot be undone.',
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('Cancel'),
),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).primaryColor,
foregroundColor: Colors.white,
),
onPressed: () async {
final navigator = Navigator.of(context);
final scaffoldMessenger = ScaffoldMessenger.of(context);
final appState = Provider.of<AppState>(context, listen: false);
try {
// Show loading indicator
navigator.pop(); // Close dialog first
scaffoldMessenger.showSnackBar(
const SnackBar(
content: Row(
children: [
CircularProgressIndicator(strokeWidth: 2),
SizedBox(width: 16),
Text('Resetting data with samples...'),
],
),
duration: Duration(seconds: 3),
),
);
// Clear and reseed data
await appState.clearAndReseedData();
if (mounted) {
scaffoldMessenger.clearSnackBars();
scaffoldMessenger.showSnackBar(
const SnackBar(
content: Text('Data reset with sample entries!'),
backgroundColor: Colors.green,
),
);
}
} catch (e) {
if (mounted) {
scaffoldMessenger.clearSnackBars();
scaffoldMessenger.showSnackBar(
SnackBar(
content: Text('Failed to reset data: $e'),
backgroundColor: Colors.red,
),
);
}
}
},
child: const Text('Reset Data'),
),
],
);
},
);
}
void _showLanguageDialog(BuildContext context) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Select Language'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: _languages.map((language) {
return RadioListTile<String>(
title: Text(language),
value: language,
groupValue: _selectedLanguage,
onChanged: (value) {
setState(() {
_selectedLanguage = value!;
});
_saveSettings();
Navigator.of(context).pop();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Language changed to $value')),
);
},
);
}).toList(),
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('Cancel'),
),
],
),
);
}
void _showThemeChangeMessage(BuildContext context) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Theme preference saved! Restart the app to see changes.'),
),
);
}
void _showStatisticsDialog(BuildContext context) {
final appState = Provider.of<AppState>(context, listen: false);
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Statistics'),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Total Beans: ${appState.beans.length}'),
Text('Total Machines: ${appState.machines.length}'),
Text('Total Recipes: ${appState.recipes.length}'),
Text('Total Journal Entries: ${appState.journalEntries.length}'),
Text('Total Drinks: ${appState.drinks.length}'),
const SizedBox(height: 16),
Text('Preferred Beans: ${appState.preferredBeans.length}'),
Text('Preferred Drinks: ${appState.preferredDrinks.length}'),
],
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('Close'),
),
],
),
);
}
void _refreshData(BuildContext context) async {
try {
// In a real implementation, you would reload data from storage
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Data refreshed successfully!')),
);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Failed to refresh data: $e')),
);
}
}
void _showHelpDialog(BuildContext context) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Help & Support'),
content: const Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Coffee at Home - User Guide\n'),
Text('• Use the Beans section to track your coffee beans'),
Text('• Manage your equipment in the Machines section'),
Text('• Create and save brewing recipes'),
Text('• Record your daily coffee experiences in the Journal'),
Text('• Use the search feature to find anything quickly'),
Text('\nFor more help, visit our website or contact support.'),
],
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('Close'),
),
],
),
);
}
void _showRateDialog(BuildContext context) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Rate Coffee at Home'),
content: const Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.favorite, size: 48, color: Colors.red),
SizedBox(height: 16),
Text('Enjoying Coffee at Home?'),
SizedBox(height: 8),
Text('Rate us on the app store and help other coffee lovers discover our app!'),
],
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('Maybe Later'),
),
ElevatedButton(
onPressed: () {
Navigator.of(context).pop();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Thanks for your support!')),
);
},
child: const Text('Rate Now'),
),
],
),
);
}
void _showAboutDialog(BuildContext context) {
showAboutDialog(
context: context,
applicationName: 'Coffee at Home',
applicationVersion: '1.0.0',
applicationIcon: const Icon(Icons.coffee, size: 48, color: Color(0xFFD4A574)),
children: [
const Text(
'A comprehensive coffee tracking app for managing your beans, equipment, recipes, and coffee journal.\n\n'
'Features:\n'
'• Track coffee beans and their origins\n'
'• Manage coffee equipment\n'
'• Create and save brewing recipes\n'
'• Keep a coffee journal with ratings\n'
'• Search across all your data\n'
'• Export and import your data\n\n'
'Built with Flutter and lots of ☕',
),
],
);
}
}