master
gitea 2 months ago
parent 9650fc78a8
commit d8c90cb105

@ -1,9 +1,12 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../data/nostr/nostr_service.dart'; import '../../data/nostr/nostr_service.dart';
import '../../data/nostr/models/nostr_keypair.dart';
import '../../data/nostr/models/nostr_event.dart';
import '../../data/nostr/models/nostr_relay.dart';
import '../../data/sync/sync_engine.dart'; import '../../data/sync/sync_engine.dart';
/// Screen for displaying Nostr events (placeholder). /// Screen for displaying and testing Nostr events.
class NostrEventsScreen extends StatelessWidget { class NostrEventsScreen extends StatefulWidget {
final NostrService? nostrService; final NostrService? nostrService;
final SyncEngine? syncEngine; final SyncEngine? syncEngine;
@ -13,45 +16,396 @@ class NostrEventsScreen extends StatelessWidget {
this.syncEngine, this.syncEngine,
}); });
@override
State<NostrEventsScreen> createState() => _NostrEventsScreenState();
}
class _NostrEventsScreenState extends State<NostrEventsScreen> {
NostrKeyPair? _keyPair;
List<NostrRelay> _relays = [];
Map<String, bool> _connectionStatus = {};
List<String> _events = [];
bool _isLoading = false;
@override
void initState() {
super.initState();
_loadRelays();
_generateKeyPair();
}
void _loadRelays() {
if (widget.nostrService == null) return;
setState(() {
_relays = widget.nostrService!.getRelays();
_connectionStatus = {
for (var relay in _relays) relay.url: relay.isConnected
};
});
}
void _generateKeyPair() {
if (widget.nostrService == null) return;
setState(() {
_keyPair = widget.nostrService!.generateKeyPair();
});
}
Future<void> _connectToRelay(String relayUrl) async {
if (widget.nostrService == null) return;
setState(() {
_isLoading = true;
});
try {
await widget.nostrService!.connectRelay(relayUrl).timeout(
const Duration(seconds: 5),
onTimeout: () {
throw Exception('Connection timeout');
},
);
setState(() {
_connectionStatus[relayUrl] = true;
_isLoading = false;
});
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Connected to $relayUrl'),
backgroundColor: Colors.green,
),
);
}
_loadRelays();
} catch (e) {
setState(() {
_connectionStatus[relayUrl] = false;
_isLoading = false;
});
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Failed to connect: ${e.toString()}'),
backgroundColor: Colors.red,
),
);
}
}
}
Future<void> _disconnectFromRelay(String relayUrl) async {
if (widget.nostrService == null) return;
widget.nostrService!.disconnectRelay(relayUrl);
setState(() {
_connectionStatus[relayUrl] = false;
});
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Disconnected from $relayUrl'),
),
);
}
_loadRelays();
}
Future<void> _publishTestEvent() async {
if (widget.nostrService == null || _keyPair == null) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Nostr service or keypair not available'),
backgroundColor: Colors.red,
),
);
}
return;
}
if (_relays.isEmpty) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('No relays configured. Add relays in Settings.'),
backgroundColor: Colors.orange,
),
);
}
return;
}
setState(() {
_isLoading = true;
});
try {
// Create a test event
final event = NostrEvent.create(
content: 'Test event from Flutter app - ${DateTime.now().toIso8601String()}',
kind: 1, // Text note
privateKey: _keyPair!.privateKey,
);
// Publish to all connected relays
final results = await widget.nostrService!.publishEventToAllRelays(event);
setState(() {
_isLoading = false;
_events.insert(0, 'Event published: ${event.id.substring(0, 8)}...');
});
final successCount = results.values.where((v) => v == true).length;
final totalCount = results.length;
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Published to $successCount/$totalCount relays'),
backgroundColor: successCount > 0 ? Colors.green : Colors.orange,
),
);
}
} catch (e) {
setState(() {
_isLoading = false;
});
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Failed to publish: ${e.toString()}'),
backgroundColor: Colors.red,
),
);
}
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('Nostr Events'), title: const Text('Nostr Events'),
actions: [
IconButton(
icon: const Icon(Icons.refresh),
onPressed: () {
_loadRelays();
_generateKeyPair();
},
tooltip: 'Refresh',
),
],
), ),
body: const Center( body: _isLoading
? const Center(child: CircularProgressIndicator())
: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildKeyPairSection(),
const SizedBox(height: 24),
_buildRelaysSection(),
const SizedBox(height: 24),
_buildActionsSection(),
if (_events.isNotEmpty) ...[
const SizedBox(height: 24),
_buildEventsSection(),
],
],
),
),
);
}
Widget _buildKeyPairSection() {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Icon( const Text(
Icons.cloud_outlined, 'Keypair',
size: 64, style: TextStyle(
color: Colors.grey, fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 12),
if (_keyPair != null) ...[
Text(
'Public Key: ${_keyPair!.publicKey.substring(0, 16)}...',
style: const TextStyle(fontSize: 12, fontFamily: 'monospace'),
),
const SizedBox(height: 4),
Text(
'Private Key: ${_keyPair!.privateKey.substring(0, 16)}...',
style: const TextStyle(fontSize: 12, fontFamily: 'monospace'),
),
] else ...[
const Text('No keypair generated'),
],
const SizedBox(height: 12),
ElevatedButton.icon(
onPressed: _generateKeyPair,
icon: const Icon(Icons.refresh),
label: const Text('Generate New Keypair'),
), ),
SizedBox(height: 16), ],
Text( ),
'Nostr Events', ),
);
}
Widget _buildRelaysSection() {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Relays',
style: TextStyle( style: TextStyle(
fontSize: 20, fontSize: 18,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
SizedBox(height: 8), const SizedBox(height: 12),
Text( if (_relays.isEmpty)
'This screen will display Nostr events', const Text(
'No relays configured.\nAdd relays in Settings → Relay Management.',
style: TextStyle(color: Colors.grey),
)
else
..._relays.map((relay) => _buildRelayItem(relay)),
],
),
),
);
}
Widget _buildRelayItem(NostrRelay relay) {
final isConnected = _connectionStatus[relay.url] ?? false;
return Card(
margin: const EdgeInsets.only(bottom: 8),
color: isConnected ? Colors.green.shade50 : Colors.grey.shade50,
child: ListTile(
title: Text(
relay.url,
style: const TextStyle(fontSize: 14, fontFamily: 'monospace'),
),
subtitle: Text(
isConnected ? 'Connected' : 'Disconnected',
style: TextStyle(
color: isConnected ? Colors.green : Colors.grey,
),
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
isConnected ? Icons.check_circle : Icons.cancel,
color: isConnected ? Colors.green : Colors.grey,
),
const SizedBox(width: 8),
if (isConnected)
IconButton(
icon: const Icon(Icons.close, size: 20),
onPressed: () => _disconnectFromRelay(relay.url),
tooltip: 'Disconnect',
)
else
IconButton(
icon: const Icon(Icons.play_arrow, size: 20),
onPressed: () => _connectToRelay(relay.url),
tooltip: 'Connect',
),
],
),
),
);
}
Widget _buildActionsSection() {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Text(
'Actions',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 18,
color: Colors.grey, fontWeight: FontWeight.bold,
), ),
), ),
SizedBox(height: 24), const SizedBox(height: 12),
Text( ElevatedButton.icon(
'Placeholder: Add your Nostr events UI here', onPressed: _publishTestEvent,
icon: const Icon(Icons.send),
label: const Text('Publish Test Event'),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16),
),
),
const SizedBox(height: 8),
TextButton.icon(
onPressed: () {
// Navigate to Settings where Relay Management is accessible
// The bottom navigation bar will handle switching to Settings tab
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Go to Settings tab to manage relays'),
),
);
},
icon: const Icon(Icons.settings),
label: const Text('Manage Relays'),
),
],
),
),
);
}
Widget _buildEventsSection() {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Recent Events',
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 18,
color: Colors.grey, fontWeight: FontWeight.bold,
), ),
), ),
const SizedBox(height: 12),
..._events.take(5).map((event) => Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Text(
event,
style: const TextStyle(fontSize: 12),
),
)),
], ],
), ),
), ),
@ -59,3 +413,4 @@ class NostrEventsScreen extends StatelessWidget {
} }
} }

Loading…
Cancel
Save

Powered by TurnKey Linux.