You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

330 lines
12 KiB

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, test, and toggle relays.
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<RelayManagementScreen> createState() => _RelayManagementScreenState();
}
class _RelayManagementScreenState extends State<RelayManagementScreen> {
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,
),
],
),
),
// Top action buttons
Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Test All and Toggle All buttons
Row(
children: [
Expanded(
child: 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.network_check),
label: const Text('Test All'),
),
),
const SizedBox(width: 8),
Expanded(
child: ElevatedButton.icon(
onPressed: widget.controller.relays.isEmpty
? null
: () async {
await widget.controller.toggleAllRelays();
},
icon: const Icon(Icons.power_settings_new),
label: Text(
widget.controller.relays.isNotEmpty &&
widget.controller.relays.every((r) => r.isEnabled)
? 'Turn All Off'
: 'Turn All On',
),
),
),
],
),
const SizedBox(height: 16),
// Add relay input
Row(
children: [
Expanded(
child: TextField(
controller: _urlController,
decoration: InputDecoration(
labelText: 'Relay URL',
hintText: 'wss://relay.example.com',
border: const OutlineInputBorder(),
),
keyboardType: TextInputType.url,
),
),
const SizedBox(width: 8),
ElevatedButton.icon(
onPressed: () async {
final url = _urlController.text.trim();
if (url.isNotEmpty) {
final success = await widget.controller.addRelay(url);
if (mounted) {
if (success) {
_urlController.clear();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Relay added and connected successfully'),
backgroundColor: Colors.green,
duration: Duration(seconds: 2),
),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
widget.controller.error ?? 'Failed to connect to relay',
),
backgroundColor: Colors.orange,
duration: const Duration(seconds: 3),
),
);
}
}
}
},
icon: const Icon(Icons.add),
label: const Text('Add'),
),
],
),
],
),
),
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,
onToggle: () async {
await widget.controller.toggleRelay(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 toggle is pressed.
final VoidCallback onToggle;
/// Callback when remove is pressed.
final VoidCallback onRemove;
const _RelayListItem({
required this.relay,
required this.onToggle,
required this.onRemove,
});
@override
Widget build(BuildContext context) {
return Card(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Relay URL and status
Row(
children: [
// Status indicator
// Enabled means connected - if it's enabled but not connected, it should be disabled
Container(
width: 12,
height: 12,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: relay.isConnected && relay.isEnabled
? Colors.green
: Colors.grey,
),
),
const SizedBox(width: 8),
Expanded(
child: Text(
relay.url,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 14,
),
),
),
],
),
const SizedBox(height: 8),
// Status text
// Enabled means connected - if it's enabled but not connected, it should be disabled
Text(
relay.isConnected && relay.isEnabled
? 'Connected'
: 'Disabled',
style: TextStyle(
fontSize: 12,
color: relay.isConnected && relay.isEnabled
? Colors.green
: Colors.grey,
),
),
const SizedBox(height: 12),
// Action buttons
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
// Toggle switch
Row(
children: [
Text(
relay.isEnabled ? 'On' : 'Off',
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
),
),
const SizedBox(width: 4),
Switch(
value: relay.isEnabled,
onChanged: (_) => onToggle(),
),
],
),
const SizedBox(width: 8),
// Remove button
IconButton(
icon: const Icon(Icons.delete, size: 20),
color: Colors.red,
tooltip: 'Remove',
onPressed: onRemove,
),
],
),
],
),
),
);
}
}

Powered by TurnKey Linux.