import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mirror_ui/mirror_ui.dart'; import '../providers/settings_provider.dart'; import '../providers/connection_provider.dart'; import 'dashboard_editor_screen.dart'; class SettingsScreen extends ConsumerStatefulWidget { const SettingsScreen({super.key}); @override ConsumerState createState() => _SettingsScreenState(); } class _SettingsScreenState extends ConsumerState { late TextEditingController _mirrorHostCtrl; late TextEditingController _mirrorPortCtrl; late TextEditingController _workoutUrlCtrl; late TextEditingController _plexHostCtrl; late TextEditingController _plexPortCtrl; late TextEditingController _plexTokenCtrl; late TextEditingController _haUrlCtrl; late TextEditingController _haTokenCtrl; late TextEditingController _spotifyClientIdCtrl; bool _initialized = false; @override void initState() { super.initState(); _mirrorHostCtrl = TextEditingController(); _mirrorPortCtrl = TextEditingController(); _workoutUrlCtrl = TextEditingController(); _plexHostCtrl = TextEditingController(); _plexPortCtrl = TextEditingController(); _plexTokenCtrl = TextEditingController(); _haUrlCtrl = TextEditingController(); _haTokenCtrl = TextEditingController(); _spotifyClientIdCtrl = TextEditingController(); } void _loadFromSettings(AppSettings settings) { if (_initialized) return; _initialized = true; _mirrorHostCtrl.text = settings.mirrorHost; _mirrorPortCtrl.text = settings.mirrorPort.toString(); _workoutUrlCtrl.text = settings.workoutPlayerUrl; _plexHostCtrl.text = settings.plexHost ?? ''; _plexPortCtrl.text = settings.plexPort.toString(); _plexTokenCtrl.text = settings.plexToken ?? ''; _haUrlCtrl.text = settings.haBaseUrl ?? ''; _haTokenCtrl.text = settings.haToken ?? ''; _spotifyClientIdCtrl.text = settings.spotifyClientId ?? ''; } @override void dispose() { _mirrorHostCtrl.dispose(); _mirrorPortCtrl.dispose(); _workoutUrlCtrl.dispose(); _plexHostCtrl.dispose(); _plexPortCtrl.dispose(); _plexTokenCtrl.dispose(); _haUrlCtrl.dispose(); _haTokenCtrl.dispose(); _spotifyClientIdCtrl.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final settingsAsync = ref.watch(settingsProvider); return Scaffold( appBar: AppBar( title: const Text('Settings'), backgroundColor: MirrorTheme.background, actions: [ TextButton( onPressed: _save, child: const Text('Save'), ), ], ), backgroundColor: MirrorTheme.background, body: settingsAsync.when( loading: () => const Center(child: CircularProgressIndicator()), error: (e, _) => Center(child: Text('Error: $e')), data: (settings) { _loadFromSettings(settings); return _buildForm(context); }, ), ); } Widget _buildForm(BuildContext context) { return ListView( padding: const EdgeInsets.all(16), children: [ _SectionHeader('Mirror Connection'), _SettingsField( controller: _mirrorHostCtrl, label: 'Host', hint: 'mirror.local or 192.168.x.x', icon: Icons.cast_connected, ), _SettingsField( controller: _mirrorPortCtrl, label: 'Port', hint: '9090', icon: Icons.numbers, keyboardType: TextInputType.number, ), const SizedBox(height: 24), _SectionHeader('Workout Player'), _SettingsField( controller: _workoutUrlCtrl, label: 'URL', hint: 'http://192.168.86.172:8091', icon: Icons.fitness_center, ), const SizedBox(height: 24), _SectionHeader('Home Assistant'), _SettingsField( controller: _haUrlCtrl, label: 'URL', hint: 'http://homeassistant.local:8123', icon: Icons.home, ), _SettingsField( controller: _haTokenCtrl, label: 'Long-Lived Access Token', hint: 'eyJ...', icon: Icons.key, obscure: true, ), const SizedBox(height: 24), _SectionHeader('Dashboard Widgets'), Card( color: MirrorTheme.surface, child: ListTile( leading: const Icon(Icons.dashboard, color: MirrorTheme.accent), title: const Text('Dashboard Layout'), subtitle: const Text('Reorder and configure widgets'), trailing: const Icon(Icons.chevron_right), onTap: () { Navigator.of(context).push( MaterialPageRoute(builder: (_) => const DashboardEditorScreen()), ); }, ), ), const SizedBox(height: 24), _SectionHeader('Plex'), _SettingsField( controller: _plexHostCtrl, label: 'Host', hint: '192.168.86.x', icon: Icons.movie, ), _SettingsField( controller: _plexPortCtrl, label: 'Port', hint: '32400', icon: Icons.numbers, keyboardType: TextInputType.number, ), _SettingsField( controller: _plexTokenCtrl, label: 'Token', hint: 'X-Plex-Token', icon: Icons.key, obscure: true, ), const SizedBox(height: 24), _SectionHeader('Spotify'), _SettingsField( controller: _spotifyClientIdCtrl, label: 'Client ID', hint: 'From developer.spotify.com', icon: Icons.music_note, ), const SizedBox(height: 12), SizedBox( width: double.infinity, child: OutlinedButton.icon( onPressed: () { // Spotify OAuth flow would go here ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Spotify OAuth not yet implemented')), ); }, icon: const Icon(Icons.login), label: const Text('Connect Spotify'), ), ), const SizedBox(height: 32), SizedBox( width: double.infinity, child: FilledButton.icon( onPressed: _save, icon: const Icon(Icons.save), label: const Text('Save Settings'), ), ), const SizedBox(height: 16), SizedBox( width: double.infinity, child: OutlinedButton.icon( onPressed: () { ref.read(connectionProvider.notifier).disconnect(); Navigator.of(context).pop(); }, icon: const Icon(Icons.link_off, color: MirrorTheme.danger), label: const Text('Disconnect from Mirror', style: TextStyle(color: MirrorTheme.danger)), ), ), const SizedBox(height: 40), ], ); } void _save() { final settings = AppSettings( mirrorHost: _mirrorHostCtrl.text.trim(), mirrorPort: int.tryParse(_mirrorPortCtrl.text.trim()) ?? 9090, workoutPlayerUrl: _workoutUrlCtrl.text.trim(), plexHost: _plexHostCtrl.text.trim().isEmpty ? null : _plexHostCtrl.text.trim(), plexPort: int.tryParse(_plexPortCtrl.text.trim()) ?? 32400, plexToken: _plexTokenCtrl.text.trim().isEmpty ? null : _plexTokenCtrl.text.trim(), haBaseUrl: _haUrlCtrl.text.trim().isEmpty ? null : _haUrlCtrl.text.trim(), haToken: _haTokenCtrl.text.trim().isEmpty ? null : _haTokenCtrl.text.trim(), spotifyClientId: _spotifyClientIdCtrl.text.trim().isEmpty ? null : _spotifyClientIdCtrl.text.trim(), ); ref.read(settingsProvider.notifier).save(settings); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Settings saved')), ); Navigator.of(context).pop(); } } class _SectionHeader extends StatelessWidget { final String title; const _SectionHeader(this.title); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.only(bottom: 12), child: Text( title, style: Theme.of(context).textTheme.labelLarge?.copyWith( color: MirrorTheme.accent, letterSpacing: 1, ), ), ); } } class _SettingsField extends StatelessWidget { final TextEditingController controller; final String label; final String hint; final IconData icon; final bool obscure; final TextInputType? keyboardType; const _SettingsField({ required this.controller, required this.label, required this.hint, required this.icon, this.obscure = false, this.keyboardType, }); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.only(bottom: 12), child: TextField( controller: controller, obscureText: obscure, keyboardType: keyboardType, decoration: InputDecoration( labelText: label, hintText: hint, filled: true, fillColor: MirrorTheme.surface, border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none, ), prefixIcon: Icon(icon, size: 20), contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), ), ), ); } }