import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_platform_widgets/flutter_platform_widgets.dart'; import 'package:mobile_nebula/components/SimplePage.dart'; import 'package:mobile_nebula/components/config/ConfigCheckboxItem.dart'; import 'package:mobile_nebula/components/config/ConfigItem.dart'; import 'package:mobile_nebula/components/config/ConfigPageItem.dart'; import 'package:mobile_nebula/components/config/ConfigSection.dart'; import 'package:mobile_nebula/models/Certificate.dart'; import 'package:mobile_nebula/models/HostInfo.dart'; import 'package:mobile_nebula/models/Site.dart'; import 'package:mobile_nebula/screens/siteConfig/CertificateDetailsScreen.dart'; import 'package:mobile_nebula/services/utils.dart'; import 'package:pull_to_refresh/pull_to_refresh.dart'; class HostInfoScreen extends StatefulWidget { const HostInfoScreen({ Key? key, required this.hostInfo, required this.isLighthouse, required this.pending, this.onChanged, required this.site, required this.supportsQRScanning, }) : super(key: key); final bool isLighthouse; final bool pending; final HostInfo hostInfo; final Function? onChanged; final Site site; final bool supportsQRScanning; @override _HostInfoScreenState createState() => _HostInfoScreenState(); } //TODO: have a config option to refresh hostmaps on a cadence (applies to 3 screens so far) class _HostInfoScreenState extends State { late HostInfo hostInfo; RefreshController refreshController = RefreshController(initialRefresh: false); @override void initState() { _setHostInfo(widget.hostInfo); super.initState(); } @override Widget build(BuildContext context) { final title = widget.pending ? 'Pending' : 'Active'; return SimplePage( title: Text('$title Host Info'), refreshController: refreshController, onRefresh: () async { await _getHostInfo(); refreshController.refreshCompleted(); }, leadingAction: Utils.leadingBackWidget(context, onPressed: () { Navigator.pop(context); }), child: Column( children: [_buildMain(), _buildDetails(), _buildRemotes(), !widget.pending ? _buildClose() : Container()])); } Widget _buildMain() { return ConfigSection(children: [ ConfigItem(label: Text('VPN IP'), labelWidth: 150, content: SelectableText(hostInfo.vpnIp)), hostInfo.cert != null ? ConfigPageItem( label: Text('Certificate'), labelWidth: 150, content: Text(hostInfo.cert!.details.name), onPressed: () => Utils.openPage( context, (context) => CertificateDetailsScreen( certInfo: CertificateInfo(cert: hostInfo.cert!), supportsQRScanning: widget.supportsQRScanning, ))) : Container(), ]); } Widget _buildDetails() { return ConfigSection(children: [ ConfigItem( label: Text('Lighthouse'), labelWidth: 150, content: SelectableText(widget.isLighthouse ? 'Yes' : 'No')), ConfigItem(label: Text('Local Index'), labelWidth: 150, content: SelectableText('${hostInfo.localIndex}')), ConfigItem(label: Text('Remote Index'), labelWidth: 150, content: SelectableText('${hostInfo.remoteIndex}')), ConfigItem( label: Text('Message Counter'), labelWidth: 150, content: SelectableText('${hostInfo.messageCounter}')), ]); } Widget _buildRemotes() { if (hostInfo.remoteAddresses.length == 0) { return ConfigSection( label: 'REMOTES', children: [ConfigItem(content: Text('No remote addresses yet'), labelWidth: 0)]); } return widget.pending ? _buildStaticRemotes() : _buildEditRemotes(); } Widget _buildEditRemotes() { List items = []; final currentRemote = hostInfo.currentRemote.toString(); final double ipWidth = Utils.textSize("000.000.000.000:000000", CupertinoTheme.of(context).textTheme.textStyle).width; hostInfo.remoteAddresses.forEach((remoteObj) { String remote = remoteObj.toString(); items.add(ConfigCheckboxItem( key: Key(remote), label: Text(remote), //TODO: need to do something to adjust the font size in the event we have an ipv6 address labelWidth: ipWidth, checked: currentRemote == remote, onChanged: () async { if (remote == currentRemote) { return; } try { final h = await widget.site.setRemoteForTunnel(hostInfo.vpnIp, remote); if (h != null) { _setHostInfo(h); } } catch (err) { Utils.popError(context, 'Error while changing the remote', err.toString()); } }, )); }); return ConfigSection(label: items.length > 0 ? 'Tap to change the active address' : null, children: items); } Widget _buildStaticRemotes() { List items = []; final currentRemote = hostInfo.currentRemote.toString(); final double ipWidth = Utils.textSize("000.000.000.000:000000", CupertinoTheme.of(context).textTheme.textStyle).width; hostInfo.remoteAddresses.forEach((remoteObj) { String remote = remoteObj.toString(); items.add(ConfigCheckboxItem( key: Key(remote), label: Text(remote), //TODO: need to do something to adjust the font size in the event we have an ipv6 address labelWidth: ipWidth, checked: currentRemote == remote, )); }); return ConfigSection(label: items.length > 0 ? 'REMOTES' : null, children: items); } Widget _buildClose() { return Padding( padding: EdgeInsets.only(top: 50, bottom: 10, left: 10, right: 10), child: SizedBox( width: double.infinity, child: PlatformElevatedButton( child: Text('Close Tunnel'), color: CupertinoColors.systemRed.resolveFrom(context), onPressed: () => Utils.confirmDelete(context, 'Close Tunnel?', () async { try { await widget.site.closeTunnel(hostInfo.vpnIp); if (widget.onChanged != null) { widget.onChanged!(); } Navigator.pop(context); } catch (err) { Utils.popError(context, 'Error while trying to close the tunnel', err.toString()); } }, deleteLabel: 'Close')))); } _getHostInfo() async { try { final h = await widget.site.getHostInfo(hostInfo.vpnIp, widget.pending); if (h == null) { return Utils.popError(context, '', 'The tunnel for this host no longer exists'); } _setHostInfo(h); } catch (err) { Utils.popError(context, 'Failed to refresh host info', err.toString()); } } _setHostInfo(HostInfo h) { h.remoteAddresses.sort((a, b) { final diff = a.ip.compareTo(b.ip); return diff == 0 ? a.port - b.port : diff; }); setState(() { hostInfo = h; }); } }