import 'package:flutter/material.dart'; import 'relay_management_controller.dart'; import '../../data/nostr/models/nostr_relay.dart'; /// Screen for managing Nostr relays. /// /// Allows users to view, add, remove, and monitor relay health, /// and trigger manual syncs. class RelayManagementScreen extends StatefulWidget { /// Controller for managing relay state. final RelayManagementController controller; /// Creates a [RelayManagementScreen] instance. const RelayManagementScreen({ super.key, required this.controller, }); @override State createState() => _RelayManagementScreenState(); } class _RelayManagementScreenState extends State { final TextEditingController _urlController = TextEditingController(); @override void dispose() { _urlController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Nostr Relay Management'), ), body: ListenableBuilder( listenable: widget.controller, builder: (context, child) { return Column( children: [ // Error message if (widget.controller.error != null) Container( width: double.infinity, padding: const EdgeInsets.all(16), color: Colors.red.shade100, child: Row( children: [ Icon(Icons.error, color: Colors.red.shade700), const SizedBox(width: 8), Expanded( child: Text( widget.controller.error!, style: TextStyle(color: Colors.red.shade700), ), ), IconButton( icon: const Icon(Icons.close), onPressed: widget.controller.clearError, color: Colors.red.shade700, ), ], ), ), // Actions section Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // Add relay input Row( children: [ Expanded( child: TextField( controller: _urlController, decoration: InputDecoration( labelText: 'Relay URL', hintText: widget.controller.relays.isNotEmpty ? widget.controller.relays.first.url : 'wss://nostrum.satoshinakamoto.win', border: const OutlineInputBorder(), ), keyboardType: TextInputType.url, ), ), const SizedBox(width: 8), ElevatedButton.icon( onPressed: () { final url = _urlController.text.trim(); if (url.isNotEmpty) { if (widget.controller.addRelay(url)) { _urlController.clear(); ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Relay added successfully'), duration: Duration(seconds: 2), ), ); } } }, icon: const Icon(Icons.add), label: const Text('Add'), ), ], ), const SizedBox(height: 16), // Action buttons Wrap( spacing: 8, runSpacing: 8, children: [ ElevatedButton.icon( onPressed: widget.controller.isCheckingHealth ? null : widget.controller.checkRelayHealth, icon: widget.controller.isCheckingHealth ? const SizedBox( width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2), ) : const Icon(Icons.health_and_safety), label: const Text('Check Health'), ), if (widget.controller.syncEngine != null) ElevatedButton.icon( onPressed: widget.controller.isSyncing ? null : () async { final success = await widget.controller.triggerManualSync(); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( success ? 'Sync triggered successfully' : 'Sync failed: ${widget.controller.error ?? "Unknown error"}', ), duration: const Duration(seconds: 2), ), ); } }, icon: widget.controller.isSyncing ? const SizedBox( width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2), ) : const Icon(Icons.sync), label: const Text('Manual Sync'), ), ], ), ], ), ), const Divider(), // Relay list Expanded( child: widget.controller.relays.isEmpty ? Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.cloud_off, size: 64, color: Colors.grey.shade400, ), const SizedBox(height: 16), Text( 'No relays configured', style: Theme.of(context).textTheme.titleLarge?.copyWith( color: Colors.grey, ), ), const SizedBox(height: 8), Text( 'Add a relay to get started', style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Colors.grey, ), ), ], ), ) : ListView.builder( itemCount: widget.controller.relays.length, itemBuilder: (context, index) { final relay = widget.controller.relays[index]; return _RelayListItem( relay: relay, onConnect: () => widget.controller.connectRelay(relay.url), onDisconnect: () => widget.controller.disconnectRelay(relay.url), onRemove: () { widget.controller.removeRelay(relay.url); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Relay ${relay.url} removed'), duration: const Duration(seconds: 2), ), ); }, ); }, ), ), ], ); }, ), ); } } /// Widget for displaying a single relay in the list. class _RelayListItem extends StatelessWidget { /// The relay to display. final NostrRelay relay; /// Callback when connect is pressed. final VoidCallback onConnect; /// Callback when disconnect is pressed. final VoidCallback onDisconnect; /// Callback when remove is pressed. final VoidCallback onRemove; const _RelayListItem({ required this.relay, required this.onConnect, required this.onDisconnect, required this.onRemove, }); @override Widget build(BuildContext context) { return Card( margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: ListTile( leading: CircleAvatar( backgroundColor: relay.isConnected ? Colors.green : Colors.grey, child: Icon( relay.isConnected ? Icons.check : Icons.close, color: Colors.white, size: 20, ), ), title: Text( relay.url, style: const TextStyle(fontWeight: FontWeight.bold), ), subtitle: Text( relay.isConnected ? 'Connected' : 'Disconnected', style: TextStyle( color: relay.isConnected ? Colors.green : Colors.grey, ), ), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ IconButton( icon: Icon( relay.isConnected ? Icons.link_off : Icons.link, color: relay.isConnected ? Colors.orange : Colors.green, ), tooltip: relay.isConnected ? 'Disconnect' : 'Connect', onPressed: relay.isConnected ? onDisconnect : onConnect, ), IconButton( icon: const Icon(Icons.delete, color: Colors.red), tooltip: 'Remove', onPressed: onRemove, ), ], ), ), ); } }