import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../models/sync_enums.dart'; import '../providers/sync_provider.dart'; /// Screen for configuring WebDAV sync settings class SyncSettingsScreen extends StatefulWidget { const SyncSettingsScreen({super.key}); @override State createState() => _SyncSettingsScreenState(); } class _SyncSettingsScreenState extends State { final _formKey = GlobalKey(); final _serverUrlController = TextEditingController(); final _usernameController = TextEditingController(); final _passwordController = TextEditingController(); final _deviceNameController = TextEditingController(); final _syncFolderController = TextEditingController(); bool _isPasswordVisible = false; bool _isTestingConnection = false; bool _isConfiguring = false; @override void initState() { super.initState(); _loadCurrentSettings(); } void _loadCurrentSettings() { final syncProvider = context.read(); _serverUrlController.text = syncProvider.serverUrl ?? ''; _usernameController.text = syncProvider.username ?? ''; _syncFolderController.text = syncProvider.syncFolderName ?? 'Supplements'; // Note: We don't load the password for security reasons } @override void dispose() { _serverUrlController.dispose(); _usernameController.dispose(); _passwordController.dispose(); _deviceNameController.dispose(); _syncFolderController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Cloud Sync Settings'), actions: [ Consumer( builder: (context, syncProvider, child) { if (!syncProvider.isConfigured) return const SizedBox.shrink(); return PopupMenuButton( onSelected: (value) { switch (value) { case 'test': _testConnection(); break; case 'sync': _performSync(); break; case 'clear': _showClearConfigDialog(); break; } }, itemBuilder: (context) => [ const PopupMenuItem( value: 'test', child: ListTile( leading: Icon(Icons.wifi_protected_setup), title: Text('Test Connection'), ), ), const PopupMenuItem( value: 'sync', child: ListTile( leading: Icon(Icons.sync), title: Text('Sync Now'), ), ), const PopupMenuItem( value: 'clear', child: ListTile( leading: Icon(Icons.clear), title: Text('Clear Configuration'), ), ), ], ); }, ), ], ), body: Consumer( builder: (context, syncProvider, child) { return SingleChildScrollView( padding: const EdgeInsets.all(16), child: Form( key: _formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildStatusCard(syncProvider), const SizedBox(height: 24), _buildDeviceInfoSection(syncProvider), const SizedBox(height: 24), _buildConfigurationSection(syncProvider), const SizedBox(height: 24), _buildSyncSettingsSection(syncProvider), if (syncProvider.hasPendingConflicts) ...[ const SizedBox(height: 24), _buildConflictsSection(syncProvider), ], ], ), ), ); }, ), ); } Widget _buildStatusCard(SyncProvider syncProvider) { Color statusColor; IconData statusIcon; switch (syncProvider.status) { case SyncOperationStatus.success: statusColor = Colors.green; statusIcon = Icons.check_circle; break; case SyncOperationStatus.syncing: statusColor = Colors.blue; statusIcon = Icons.sync; break; case SyncOperationStatus.networkError: case SyncOperationStatus.authenticationError: case SyncOperationStatus.serverError: statusColor = Colors.red; statusIcon = Icons.error; break; case SyncOperationStatus.conflictsDetected: statusColor = Colors.orange; statusIcon = Icons.warning; break; default: statusColor = Colors.grey; statusIcon = Icons.cloud_off; } return Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon(statusIcon, color: statusColor, size: 24), const SizedBox(width: 12), Expanded( child: Text( syncProvider.statusMessage, style: Theme.of(context).textTheme.titleMedium, ), ), ], ), if (syncProvider.lastSyncTime != null) ...[ const SizedBox(height: 8), Text( 'Last sync: ${syncProvider.formattedLastSyncTime}', style: Theme.of(context).textTheme.bodySmall, ), ], if (syncProvider.isConfigured && syncProvider.detectedServerType != null) ...[ const SizedBox(height: 8), Row( children: [ Icon(Icons.check_circle, size: 16, color: Colors.green), const SizedBox(width: 4), Text( 'Detected: ${syncProvider.detectedServerType}', style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Colors.green[700], fontWeight: FontWeight.w500, ), ), ], ), ], if (syncProvider.hasError) ...[ const SizedBox(height: 8), Text( syncProvider.currentError!, style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Colors.red, ), ), ], if (syncProvider.isSyncing) ...[ const SizedBox(height: 12), LinearProgressIndicator( value: syncProvider.syncProgress, ), ], ], ), ), ); } Widget _buildConfigurationSection(SyncProvider syncProvider) { return Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'WebDAV Configuration', style: Theme.of(context).textTheme.titleMedium, ), const SizedBox(height: 16), TextFormField( controller: _serverUrlController, decoration: const InputDecoration( labelText: 'Server URL', hintText: 'cloud.example.com or drive.mydomain.com', prefixIcon: Icon(Icons.cloud), helperText: 'Just enter your server domain - we\'ll auto-detect the rest!', helperMaxLines: 2, ), validator: (value) { if (value?.isEmpty ?? true) { return 'Please enter server URL'; } return null; }, ), const SizedBox(height: 8), Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.blue.withOpacity(0.1), borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.blue.withOpacity(0.3)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon(Icons.lightbulb_outline, size: 16, color: Colors.blue[700]), const SizedBox(width: 8), Text( 'Smart URL Detection', style: Theme.of(context).textTheme.titleSmall?.copyWith( color: Colors.blue[700], fontWeight: FontWeight.bold, ), ), ], ), const SizedBox(height: 8), Text( 'You can enter simple URLs like:', style: Theme.of(context).textTheme.bodySmall, ), const SizedBox(height: 4), Text( '• cloud.example.com\n' '• drive.mydomain.com\n' '• nextcloud.company.org\n' '• my-server.duckdns.org:8080', style: Theme.of(context).textTheme.bodySmall?.copyWith( fontFamily: 'monospace', ), ), const SizedBox(height: 8), Text( 'We\'ll automatically detect if it\'s Nextcloud, ownCloud, or generic WebDAV and build the correct URL for you!', style: Theme.of(context).textTheme.bodySmall?.copyWith( fontStyle: FontStyle.italic, ), ), ], ), ), const SizedBox(height: 16), TextFormField( controller: _usernameController, decoration: const InputDecoration( labelText: 'Username', prefixIcon: Icon(Icons.person), ), validator: (value) { if (value?.isEmpty ?? true) { return 'Please enter username'; } return null; }, ), const SizedBox(height: 16), TextFormField( controller: _passwordController, obscureText: !_isPasswordVisible, decoration: InputDecoration( labelText: 'Password / App Password', prefixIcon: const Icon(Icons.lock), suffixIcon: IconButton( icon: Icon( _isPasswordVisible ? Icons.visibility : Icons.visibility_off, ), onPressed: () { setState(() { _isPasswordVisible = !_isPasswordVisible; }); }, ), helperText: 'Use app passwords for better security', ), validator: (value) { if (value?.isEmpty ?? true) { return 'Please enter password'; } return null; }, ), const SizedBox(height: 16), TextFormField( controller: _syncFolderController, decoration: const InputDecoration( labelText: 'Sync Folder Name', prefixIcon: Icon(Icons.folder), hintText: 'Supplements', helperText: 'Folder name on your cloud server for syncing data', ), validator: (value) { if (value?.isEmpty ?? true) { return 'Please enter folder name'; } if (value!.contains('/') || value.contains('\\')) { return 'Folder name cannot contain slashes'; } return null; }, ), const SizedBox(height: 16), TextFormField( controller: _deviceNameController, decoration: const InputDecoration( labelText: 'Device Name (Optional)', prefixIcon: Icon(Icons.phone_android), hintText: 'My Phone', ), ), const SizedBox(height: 24), Row( children: [ Expanded( child: ElevatedButton.icon( onPressed: _isConfiguring || syncProvider.isSyncing ? null : () => _configureWebDAV(syncProvider), icon: _isConfiguring ? const SizedBox( width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2), ) : const Icon(Icons.save), label: Text(syncProvider.isConfigured ? 'Update' : 'Configure'), ), ), if (syncProvider.isConfigured) ...[ const SizedBox(width: 12), ElevatedButton.icon( onPressed: _isTestingConnection || syncProvider.isSyncing ? null : _testConnection, icon: _isTestingConnection ? const SizedBox( width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2), ) : const Icon(Icons.wifi_protected_setup), label: const Text('Test'), ), ], ], ), ], ), ), ); } Widget _buildSyncSettingsSection(SyncProvider syncProvider) { if (!syncProvider.isConfigured) return const SizedBox.shrink(); return Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Sync Settings', style: Theme.of(context).textTheme.titleMedium, ), const SizedBox(height: 16), SwitchListTile( title: const Text('Auto Sync on Data Changes'), subtitle: const Text('Automatically sync when you add, modify, or take supplements'), value: syncProvider.autoSyncOnDataChanges, onChanged: syncProvider.setAutoSyncOnDataChanges, ), const SizedBox(height: 8), ListTile( title: const Text('Conflict Resolution'), subtitle: Text(_getConflictStrategyDescription(syncProvider.conflictStrategy)), trailing: DropdownButton( value: syncProvider.conflictStrategy, onChanged: (strategy) { if (strategy != null) { syncProvider.setConflictResolutionStrategy(strategy); } }, items: ConflictResolutionStrategy.values .map((strategy) => DropdownMenuItem( value: strategy, child: Text(_getConflictStrategyName(strategy)), )) .toList(), ), ), const SizedBox(height: 16), SizedBox( width: double.infinity, child: ElevatedButton.icon( onPressed: syncProvider.isSyncing ? null : _performSync, icon: syncProvider.isSyncing ? const SizedBox( width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2), ) : const Icon(Icons.sync), label: const Text('Sync Now'), ), ), ], ), ), ); } Widget _buildConflictsSection(SyncProvider syncProvider) { return Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const Icon(Icons.warning, color: Colors.orange), const SizedBox(width: 8), Text( 'Sync Conflicts', style: Theme.of(context).textTheme.titleMedium, ), ], ), const SizedBox(height: 16), Text( '${syncProvider.pendingConflicts.length} conflicts need your attention', style: Theme.of(context).textTheme.bodyMedium, ), const SizedBox(height: 16), Row( children: [ Expanded( child: ElevatedButton( onPressed: () => _showConflictsDialog(syncProvider), child: const Text('Review Conflicts'), ), ), const SizedBox(width: 12), ElevatedButton( onPressed: syncProvider.resolveAllConflicts, child: const Text('Auto Resolve'), ), ], ), ], ), ), ); } Widget _buildDeviceInfoSection(SyncProvider syncProvider) { return Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Connection Information', style: Theme.of(context).textTheme.titleMedium, ), const SizedBox(height: 16), FutureBuilder>( future: syncProvider.getDeviceInfo(), builder: (context, snapshot) { if (!snapshot.hasData) { return const Center(child: CircularProgressIndicator()); } final deviceInfo = snapshot.data!; return Column( children: [ if (syncProvider.detectedServerType != null) ListTile( leading: const Icon(Icons.cloud_done), title: const Text('Server Type'), subtitle: Text(syncProvider.detectedServerType!), ), if (syncProvider.finalWebdavUrl != null) ListTile( leading: const Icon(Icons.link), title: const Text('WebDAV URL'), subtitle: Text( syncProvider.finalWebdavUrl!, style: const TextStyle(fontFamily: 'monospace', fontSize: 12), ), ), ListTile( leading: const Icon(Icons.fingerprint), title: const Text('Device ID'), subtitle: Text(deviceInfo['deviceId'] ?? 'Unknown'), ), ListTile( leading: const Icon(Icons.devices), title: const Text('Device Name'), subtitle: Text(deviceInfo['deviceName'] ?? 'Unknown'), ), ], ); }, ), ], ), ), ); } String _getConflictStrategyName(ConflictResolutionStrategy strategy) { switch (strategy) { case ConflictResolutionStrategy.preferLocal: return 'Prefer Local'; case ConflictResolutionStrategy.preferRemote: return 'Prefer Remote'; case ConflictResolutionStrategy.preferNewer: return 'Prefer Newer'; case ConflictResolutionStrategy.manual: return 'Manual'; } } String _getConflictStrategyDescription(ConflictResolutionStrategy strategy) { switch (strategy) { case ConflictResolutionStrategy.preferLocal: return 'Always keep local changes'; case ConflictResolutionStrategy.preferRemote: return 'Always keep remote changes'; case ConflictResolutionStrategy.preferNewer: return 'Keep most recent changes'; case ConflictResolutionStrategy.manual: return 'Review each conflict manually'; } } Future _configureWebDAV(SyncProvider syncProvider) async { // For updates, allow empty password to keep existing one if (syncProvider.isConfigured && _passwordController.text.isEmpty) { // Skip validation for password on updates if (_serverUrlController.text.trim().isEmpty || _usernameController.text.trim().isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Please fill in server URL and username'), backgroundColor: Colors.red, ), ); return; } } else if (!_formKey.currentState!.validate()) { return; } setState(() => _isConfiguring = true); final success = await syncProvider.configure( serverUrl: _serverUrlController.text.trim(), username: _usernameController.text.trim(), password: _passwordController.text.isEmpty ? null : _passwordController.text, deviceName: _deviceNameController.text.trim().isEmpty ? null : _deviceNameController.text.trim(), syncFolderName: _syncFolderController.text.trim().isEmpty ? 'Supplements' : _syncFolderController.text.trim(), ); setState(() => _isConfiguring = false); if (mounted) { final message = success ? 'WebDAV configured successfully!' : 'Failed to configure WebDAV'; final detectionInfo = success && syncProvider.detectedServerType != null ? '\nDetected: ${syncProvider.detectedServerType}' : ''; ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('$message$detectionInfo'), backgroundColor: success ? Colors.green : Colors.red, duration: Duration(seconds: success ? 4 : 3), ), ); if (success) { _passwordController.clear(); // Clear password for security } } } Future _testConnection() async { setState(() => _isTestingConnection = true); final syncProvider = context.read(); final success = await syncProvider.testConnection(); setState(() => _isTestingConnection = false); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(success ? 'Connection test successful!' : 'Connection test failed'), backgroundColor: success ? Colors.green : Colors.red, ), ); } } Future _performSync() async { final syncProvider = context.read(); final success = await syncProvider.performManualSync(); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(success ? 'Sync completed successfully!' : 'Sync failed'), backgroundColor: success ? Colors.green : Colors.red, ), ); } } Future _showClearConfigDialog() async { final confirmed = await showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Clear Configuration'), content: const Text( 'Are you sure you want to clear the WebDAV configuration? ' 'This will disable cloud sync.', ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(false), child: const Text('Cancel'), ), TextButton( onPressed: () => Navigator.of(context).pop(true), child: const Text('Clear'), ), ], ), ); if (confirmed == true) { final syncProvider = context.read(); await syncProvider.clearConfiguration(); _serverUrlController.clear(); _usernameController.clear(); _passwordController.clear(); _deviceNameController.clear(); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Configuration cleared'), backgroundColor: Colors.orange, ), ); } } } Future _showConflictsDialog(SyncProvider syncProvider) async { await showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Sync Conflicts'), content: SizedBox( width: double.maxFinite, height: 300, child: ListView.builder( itemCount: syncProvider.pendingConflicts.length, itemBuilder: (context, index) { final conflict = syncProvider.pendingConflicts[index]; return Card( child: ListTile( title: Text(conflict.type.name), subtitle: Text(conflict.description), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ TextButton( onPressed: () { syncProvider.resolveConflict( conflict.syncId, ConflictResolution.useLocal, ); }, child: const Text('Local'), ), TextButton( onPressed: () { syncProvider.resolveConflict( conflict.syncId, ConflictResolution.useRemote, ); }, child: const Text('Remote'), ), ], ), ), ); }, ), ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('Close'), ), ], ), ); } }