import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:uuid/uuid.dart'; import 'package:worldhopper/models/opds_server.dart'; import 'package:worldhopper/providers/server_provider.dart'; import 'package:worldhopper/services/server_software_detector.dart'; /// Screen for adding a new OPDS server class AddServerScreen extends ConsumerStatefulWidget { const AddServerScreen({super.key}); @override ConsumerState createState() => _AddServerScreenState(); } class _AddServerScreenState extends ConsumerState { final _formKey = GlobalKey(); final _nameController = TextEditingController(); final _urlController = TextEditingController(); final _usernameController = TextEditingController(); final _passwordController = TextEditingController(); bool _requiresAuth = false; bool _obscurePassword = true; bool _isLoading = false; @override void dispose() { _nameController.dispose(); _urlController.dispose(); _usernameController.dispose(); _passwordController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Add Server'), ), body: Form( key: _formKey, child: ListView( padding: const EdgeInsets.all(16), children: [ // Name field TextFormField( controller: _nameController, decoration: const InputDecoration( labelText: 'Server Name', hintText: 'My Library', prefixIcon: Icon(Icons.label), ), textInputAction: TextInputAction.next, validator: (value) { if (value == null || value.trim().isEmpty) { return 'Please enter a server name'; } return null; }, ), const SizedBox(height: 16), // URL field TextFormField( controller: _urlController, decoration: const InputDecoration( labelText: 'Server URL', hintText: 'https://example.com/opds', prefixIcon: Icon(Icons.link), ), keyboardType: TextInputType.url, textInputAction: TextInputAction.next, validator: (value) { if (value == null || value.trim().isEmpty) { return 'Please enter a server URL'; } final uri = Uri.tryParse(value.trim()); if (uri == null || !uri.hasScheme || !uri.hasAuthority) { return 'Please enter a valid URL'; } if (uri.scheme != 'http' && uri.scheme != 'https') { return 'URL must start with http:// or https://'; } return null; }, ), const SizedBox(height: 24), // Authentication toggle SwitchListTile( title: const Text('Requires Authentication'), subtitle: const Text('Enable if server requires login'), value: _requiresAuth, onChanged: (value) { setState(() { _requiresAuth = value; if (!value) { _usernameController.clear(); _passwordController.clear(); } }); }, ), const SizedBox(height: 16), // Authentication fields (conditionally shown) if (_requiresAuth) ...[ TextFormField( controller: _usernameController, decoration: const InputDecoration( labelText: 'Username', prefixIcon: Icon(Icons.person), ), textInputAction: TextInputAction.next, validator: (value) { if (_requiresAuth && (value == null || value.trim().isEmpty)) { return 'Please enter a username'; } return null; }, ), const SizedBox(height: 16), TextFormField( controller: _passwordController, decoration: InputDecoration( labelText: 'Password', prefixIcon: const Icon(Icons.lock), suffixIcon: IconButton( icon: Icon( _obscurePassword ? Icons.visibility : Icons.visibility_off, ), onPressed: () { setState(() { _obscurePassword = !_obscurePassword; }); }, ), ), obscureText: _obscurePassword, textInputAction: TextInputAction.done, validator: (value) { if (_requiresAuth && (value == null || value.isEmpty)) { return 'Please enter a password'; } return null; }, onFieldSubmitted: (_) => _saveServer(), ), const SizedBox(height: 24), ], // Help text Card( color: Theme.of(context).colorScheme.surfaceContainerHighest, child: Padding( padding: const EdgeInsets.all(16), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Icon( Icons.info_outline, color: Theme.of(context).colorScheme.primary, ), const SizedBox(width: 12), Expanded( child: Text( 'Enter the root URL of your OPDS server. Credentials will be securely stored on your device.', style: Theme.of(context).textTheme.bodySmall, ), ), ], ), ), ), const SizedBox(height: 32), // Save button FilledButton( onPressed: _isLoading ? null : _saveServer, child: _isLoading ? const SizedBox( height: 20, width: 20, child: CircularProgressIndicator(strokeWidth: 2), ) : const Text('Add Server'), ), ], ), ), ); } Future _saveServer() async { if (!_formKey.currentState!.validate()) { return; } setState(() { _isLoading = true; }); try { final url = _urlController.text.trim(); // Check if server already exists final exists = await ref .read(serverNotifierProvider.notifier) .serverExists(url); if (exists) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('A server with this URL already exists'), backgroundColor: Colors.orange, ), ); } setState(() { _isLoading = false; }); return; } // Create new server var server = OPDSServer( id: const Uuid().v4(), name: _nameController.text.trim(), url: url, username: _requiresAuth ? _usernameController.text.trim() : null, password: _requiresAuth ? _passwordController.text : null, createdAt: DateTime.now(), ); // Detect server software final detectedSoftware = await ServerSoftwareDetector().detect(server); if (detectedSoftware != null) { server = server.copyWith(serverSoftware: detectedSoftware); } // Save server await ref.read(serverNotifierProvider.notifier).addServer(server); if (mounted) { // Show success message ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('${server.name} added successfully'), backgroundColor: Colors.green, ), ); // Navigate back context.pop(); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Error adding server: $e'), backgroundColor: Colors.red, ), ); } } finally { if (mounted) { setState(() { _isLoading = false; }); } } } }