import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_platform_widgets/flutter_platform_widgets.dart'; import 'package:mobile_nebula/components/FormPage.dart'; import 'package:mobile_nebula/components/IPAndPortFormField.dart'; import 'package:mobile_nebula/components/IPFormField.dart'; import 'package:mobile_nebula/components/config/ConfigButtonItem.dart'; import 'package:mobile_nebula/components/config/ConfigItem.dart'; import 'package:mobile_nebula/components/config/ConfigSection.dart'; import 'package:mobile_nebula/models/Hostmap.dart'; import 'package:mobile_nebula/models/IPAndPort.dart'; import 'package:mobile_nebula/services/utils.dart'; class _IPAndPort { final FocusNode focusNode; IPAndPort destination; _IPAndPort({required this.focusNode, required this.destination}); } class StaticHostmapScreen extends StatefulWidget { StaticHostmapScreen({ Key? key, this.nebulaIp = '', destinations, this.lighthouse = false, this.onDelete, required this.onSave, }) : this.destinations = destinations ?? [], super(key: key); final List destinations; final String nebulaIp; final bool lighthouse; final ValueChanged? onSave; final Function? onDelete; @override _StaticHostmapScreenState createState() => _StaticHostmapScreenState(); } class _StaticHostmapScreenState extends State { late Map _destinations; late String _nebulaIp; late bool _lighthouse; bool changed = false; @override void initState() { _nebulaIp = widget.nebulaIp; _lighthouse = widget.lighthouse; _destinations = {}; widget.destinations.forEach((dest) { _destinations[UniqueKey()] = _IPAndPort(focusNode: FocusNode(), destination: dest); }); if (_destinations.length == 0) { _addDestination(); } // _addDestination() above sets us to changed, set it back to false since we are at the default state changed = false; super.initState(); } @override Widget build(BuildContext context) { return FormPage( title: widget.onDelete == null ? widget.onSave == null ? 'View Static Host' : 'New Static Host' : 'Edit Static Host', changed: changed, onSave: _onSave, child: Column(children: [ ConfigSection(label: 'Maps a nebula ip address to multiple real world addresses', children: [ ConfigItem( label: Text('Nebula IP'), labelWidth: 200, content: widget.onSave == null ? Text(_nebulaIp, textAlign: TextAlign.end) : IPFormField( help: "Required", initialValue: _nebulaIp, ipOnly: true, textAlign: TextAlign.end, crossAxisAlignment: CrossAxisAlignment.end, textInputAction: TextInputAction.next, onSaved: (v) { if (v != null) { _nebulaIp = v; } })), ConfigItem( label: Text('Lighthouse'), labelWidth: 200, content: Container( alignment: Alignment.centerRight, child: Switch.adaptive( value: _lighthouse, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, onChanged: widget.onSave == null ? null : (v) { setState(() { changed = true; _lighthouse = v; }); })), ), ]), ConfigSection( label: 'List of public ips or dns names where for this host', children: _buildHosts(), ), widget.onDelete != null ? Padding( padding: EdgeInsets.only(top: 50, bottom: 10, left: 10, right: 10), child: SizedBox( width: double.infinity, child: PlatformElevatedButton( child: Text('Delete'), color: CupertinoColors.systemRed.resolveFrom(context), onPressed: () => Utils.confirmDelete(context, 'Delete host map?', () { Navigator.of(context).pop(); widget.onDelete!(); }), ))) : Container() ])); } _onSave() { Navigator.pop(context); if (widget.onSave != null) { var map = Hostmap( nebulaIp: _nebulaIp, destinations: [], lighthouse: _lighthouse); _destinations.forEach((_, dest) { map.destinations.add(dest.destination); }); widget.onSave!(map); } } List _buildHosts() { List items = []; _destinations.forEach((key, dest) { items.add(ConfigItem( key: key, label: Align( alignment: Alignment.centerLeft, child: widget.onSave == null ? Container() : PlatformIconButton( padding: EdgeInsets.zero, icon: Icon(Icons.remove_circle, color: CupertinoColors.systemRed.resolveFrom(context)), onPressed: () => setState(() { _removeDestination(key); _dismissKeyboard(); }))), labelWidth: 70, content: Row(children: [ Expanded( child: widget.onSave == null ? Text(dest.destination.toString(), textAlign: TextAlign.end) : IPAndPortFormField( ipHelp: 'public ip or name', ipTextAlign: TextAlign.end, enableIPV6: true, noBorder: true, initialValue: dest.destination, onSaved: (v) { if (v != null) { dest.destination = v; } }, )), ]), )); }); if (widget.onSave != null) { items.add(ConfigButtonItem( content: Text('Add another'), onPressed: () => setState(() { _addDestination(); _dismissKeyboard(); }))); } return items; } _addDestination() { changed = true; _destinations[UniqueKey()] = _IPAndPort(focusNode: FocusNode(), destination: IPAndPort()); // We can't onChanged here because it causes rendering issues on first build due to ensuring there is a single destination } _removeDestination(Key key) { changed = true; _destinations.remove(key); } _dismissKeyboard() { FocusScopeNode currentFocus = FocusScope.of(context); if (!currentFocus.hasPrimaryFocus) { currentFocus.unfocus(); } } @override void dispose() { _destinations.forEach((key, dest) { dest.focusNode.dispose(); }); super.dispose(); } }