import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'dart:io'; import 'package:path_provider/path_provider.dart'; import 'package:permission_handler/permission_handler.dart'; // Import the generated functions directly import 'bridge_generated/api.dart'; import 'bridge_generated/frb_generated.dart'; // Initialize the Rust library void main() async { WidgetsFlutterBinding.ensureInitialized(); await RustLib.init(); runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return MaterialApp( title: 'Iroh Sync', theme: ThemeData( primarySwatch: Colors.indigo, visualDensity: VisualDensity.adaptivePlatformDensity, useMaterial3: true, ), home: const SyncHomePage(), ); } } class SyncHomePage extends StatefulWidget { const SyncHomePage({Key? key}) : super(key: key); @override _SyncHomePageState createState() => _SyncHomePageState(); } class _SyncHomePageState extends State { final _ticketController = TextEditingController(); String _syncStatus = "Initializing..."; String _nodeId = ""; String _generatedTicket = ""; bool _isSyncing = false; String _syncDirectory = ""; @override void initState() { super.initState(); _initPermissionsAndDirectory(); } Future _initPermissionsAndDirectory() async { var status = await Permission.manageExternalStorage.request(); if (!status.isGranted) { setState(() { _syncStatus = "Storage permission denied. Please grant permission in App Settings."; }); return; } try { final directory = await getApplicationDocumentsDirectory(); final syncPath = Directory('${directory.path}/iroh_sync_data'); if (!await syncPath.exists()) { await syncPath.create(recursive: true); } setState(() { _syncDirectory = syncPath.path; _syncStatus = "Ready. Press Start Sync."; }); } catch (e) { setState(() { _syncStatus = "Error initializing directory: $e"; }); } } void _startSync() async { if (_syncDirectory.isEmpty) { return; } setState(() { _isSyncing = true; _syncStatus = "Starting..."; }); try { final nodeId = await startSync( syncDir: _syncDirectory, ticket: _ticketController.text.trim().isNotEmpty ? _ticketController.text.trim() : null, ); setState(() { _nodeId = nodeId; _syncStatus = "Sync running..."; }); _pollStatus(); } catch (e) { setState(() { _isSyncing = false; _syncStatus = "Error starting sync: $e"; }); } } void _stopSync() async { try { await stopSync(); setState(() { _isSyncing = false; _syncStatus = "Sync stopped."; _nodeId = ""; _generatedTicket = ""; }); } catch (e) { setState(() { _syncStatus = "Error stopping sync: $e"; }); } } void _pollStatus() async { while (_isSyncing) { await Future.delayed(const Duration(seconds: 3)); if (!mounted || !_isSyncing) break; try { final status = await getStatus(); if (mounted) { setState(() { _syncStatus = status; }); } } catch (e) { if (mounted) { setState(() { _syncStatus = "Error getting status: $e"; }); } } } } void _generateTicket() async { if (!_isSyncing) { return; } try { final ticket = await generateTicket(); setState(() { _generatedTicket = ticket; }); } catch (e) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("Error generating ticket: $e")), ); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Iroh Sync Client'), ), body: GestureDetector( onTap: () => FocusScope.of(context).unfocus(), child: SingleChildScrollView( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Card( child: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Sync Control', style: Theme.of(context).textTheme.titleLarge), const SizedBox(height: 16), Text('Sync Directory:', style: Theme.of(context).textTheme.titleSmall), SelectableText(_syncDirectory, style: const TextStyle(color: Colors.grey)), const SizedBox(height: 16), TextField( controller: _ticketController, decoration: const InputDecoration( labelText: 'Connection Ticket (optional)', border: OutlineInputBorder(), ), enabled: !_isSyncing, ), const SizedBox(height: 16), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ FilledButton.icon( icon: const Icon(Icons.play_arrow), onPressed: _isSyncing ? null : _startSync, // --- FIXED --- label: const Text('Start'), style: FilledButton.styleFrom(backgroundColor: Colors.green), ), FilledButton.icon( icon: const Icon(Icons.stop), onPressed: !_isSyncing ? null : _stopSync, // --- FIXED --- label: const Text('Stop'), style: FilledButton.styleFrom(backgroundColor: Colors.red), ), ], ), ], ), ), ), const SizedBox(height: 16), Card( child: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Status', style: Theme.of(context).textTheme.titleLarge), const SizedBox(height: 16), Text('Node ID:', style: Theme.of(context).textTheme.titleSmall), SelectableText(_nodeId.isEmpty ? 'N/A' : _nodeId), const SizedBox(height: 8), Text('Sync Status:', style: Theme.of(context).textTheme.titleSmall), Text(_syncStatus), ], ), ), ), const SizedBox(height: 16), Card( child: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Sharing', style: Theme.of(context).textTheme.titleLarge), const SizedBox(height: 16), SizedBox( width: double.infinity, child: FilledButton.icon( icon: const Icon(Icons.qr_code_2), onPressed: _isSyncing ? _generateTicket : null, // --- FIXED --- label: const Text('Generate New Ticket'), ), ), if (_generatedTicket.isNotEmpty) ...[ const SizedBox(height: 16), Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: Colors.black12, borderRadius: BorderRadius.circular(8), ), child: Row( children: [ Expanded(child: SelectableText(_generatedTicket)), IconButton( icon: const Icon(Icons.copy), onPressed: () { Clipboard.setData(ClipboardData(text: _generatedTicket)); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Ticket copied to clipboard!')), ); }, ), ], ), ), ] ], ), ), ), ], ), ), ), ); } }