// Haveno App extends the features of Haveno, supporting mobile devices and more. // Copyright (C) 2024 Kewbit (https://kewbit.org) // Source Code: https://git.haveno.com/haveno/haveno-app.git // // Author: Kewbit // Website: https://kewbit.org // Contact Email: me@kewbit.org // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . import 'package:flutter/material.dart'; import 'package:haveno/enums.dart'; import 'package:haveno_app/providers/haveno_client_providers/xmr_connections_provider.dart'; import 'package:provider/provider.dart'; class NodeManagerScreen extends StatefulWidget { const NodeManagerScreen({super.key}); @override _NodeManagerScreenState createState() => _NodeManagerScreenState(); } class _NodeManagerScreenState extends State { bool _isAutoSwitchEnabled = false; // This tracks the state of the toggle switch final Map _isDeleting = {}; // Tracks the deletion status for each node @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { final provider = Provider.of(context, listen: false); // Fetch all node connections provider.getXmrConnectionSettings(); // Fetch the current active node provider.getActiveConnection(); // Fetch if auto switch is enabled and update the state provider.getAutoSwitchBestConnection().then((autoSwitchEnabled) { print("Is Auto Switch Enabled On Daemon: $autoSwitchEnabled"); setState(() { _isAutoSwitchEnabled = autoSwitchEnabled; }); }); }); } // Toggle switch handler Future _handleAutoSwitchToggle(bool value, XmrConnectionsProvider provider) async { setState(() { _isAutoSwitchEnabled = value; // Optimistically update the UI }); print("You turned $value xmr connection autosswitch"); // Call the provider's autoSwitchBestConnection method final success = await provider.setAutoSwitchBestConnection(value); if (!success) { // If the operation failed, revert the switch and show an error snackbar setState(() { _isAutoSwitchEnabled = !value; // Revert to previous state }); ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Failed to enable auto-switch to best node.'), backgroundColor: Colors.red, ), ); } else { // Show success snackbar ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: const Text('Auto-switch to best node enabled successfully.'), backgroundColor: Colors.green, ), ); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Node Manager'), ), body: Consumer( builder: (context, provider, child) { final nodes = provider.xmrNodeConnections; final activeNode = provider.xmrActiveConnection; if (nodes.isEmpty) { return const Center(child: CircularProgressIndicator()); } return Column( children: [ // Toggle switch widget Padding( padding: const EdgeInsets.all(16.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text('Automatically connect to the best node'), Switch( value: _isAutoSwitchEnabled, onChanged: (value) => _handleAutoSwitchToggle(value, provider), ), ], ), ), Expanded( child: ListView.builder( itemCount: nodes.length, itemBuilder: (context, index) { final node = nodes[index]; final isActiveNode = activeNode != null && activeNode.url == node.url; final isOnline = node.onlineStatus == UrlConnection_OnlineStatus.ONLINE; final dotColor = isOnline ? Colors.green : Colors.red; final isDeleting = _isDeleting[node.url] ?? false; // Check if the node is being deleted return GestureDetector( onTap: () async { await provider.setConnection(node); // Set the new active node }, child: Card( margin: const EdgeInsets.fromLTRB(8, 8, 8, 2), // Consistent margins with the PaymentAccountsScreen color: Theme.of(context).cardTheme.color, elevation: isActiveNode ? 4.0 : 2.0, // Slightly more elevated when active shadowColor: isActiveNode ? const Color.fromARGB(255, 255, 103, 2).withOpacity(0.5) : Colors.black12, // Glow effect when active shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), side: isActiveNode ? BorderSide( color: const Color.fromARGB(255, 255, 103, 2).withOpacity(0.5), // Orange border for the active node width: 2, ) : BorderSide.none, ), child: Padding( padding: const EdgeInsets.fromLTRB(16, 8, 8, 8), // Consistent padding child: Row( crossAxisAlignment: CrossAxisAlignment.center, // Vertically center the row content children: [ Icon(Icons.circle, color: dotColor, size: 16), // Status icon const SizedBox(width: 16), // Spacing between the icon and the text Expanded( child: Text( node.url, overflow: TextOverflow.ellipsis, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), ), ), isDeleting ? const SizedBox( width: 24, height: 24, child: CircularProgressIndicator( strokeWidth: 2.0, ), ) : IconButton( icon: Icon(Icons.delete, color: Theme.of(context).colorScheme.secondary.withOpacity(0.23)), onPressed: () async { setState(() { _isDeleting[node.url] = true; }); // Call the removeConnection function in the provider final success = await provider.removeConnection(node.url); // After the response, remove the loading indicator setState(() { _isDeleting.remove(node.url); }); if (success) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Node removed: ${node.url}'), ), ); } else { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Failed to remove node: ${node.url}'), ), ); } }, ), ], ), ), ), ); }, ), ), ], ); }, ), floatingActionButton: FloatingActionButton( onPressed: () { // Add your logic for adding a new node here. }, child: const Icon(Icons.add), ), ); } }