mobile_nebula/lib/screens/HostInfoScreen.dart
Ian VanSchooten ad45cc1d78
Fix screen titles on iOS (#230)
TODO:
- [x] Address Android, which this probably breaks.

Previously the back button was taking up all the room in the title bar, this fixes it so that we can see titles again.  It also truncates site names so they stay to one line.

|Before|After|
|---|---|
|![image](https://github.com/user-attachments/assets/3e07a50d-fb40-40da-87f8-4d623019b26d)|![Simulator 2025-01-23 16 32 34](https://github.com/user-attachments/assets/ea668973-e67d-4fc5-8731-578e5f3fdd27)|


|Before|After|
|---|---|
|![image](https://github.com/user-attachments/assets/d95e1a9d-f431-42aa-a9f2-357b20c37abb)|![Simulator 2025-01-23 16 11 15](https://github.com/user-attachments/assets/ff3f664b-1983-4514-a492-cf585153e294)|

|Before|After|
|---|---|
|![image](https://github.com/user-attachments/assets/0ea3aa0d-340a-44db-8a0a-e0c8032c2450)|![image](https://github.com/user-attachments/assets/fb7e26c5-5c67-4dd7-808c-d471ca1e913e)|

|Before|After|
|---|---|
|![image](https://github.com/user-attachments/assets/bffec7e3-561d-4a43-ab8a-3bd1cc95003c)|![Simulator 2025-01-23 16 13 23](https://github.com/user-attachments/assets/288c1f7f-4d79-4b59-b693-0cbcdd2024db)|


A few other "After" screenshots:

|Logs|DN enrollment|
|---|---|
|![Simulator 2025-01-23 16 30 48](https://github.com/user-attachments/assets/4698939e-c4ad-4929-bd0b-1b72fc21c439)|![image](https://github.com/user-attachments/assets/4c738c41-af3c-4465-9907-76fce34ecdd9)|
2025-01-24 07:51:37 -05:00

199 lines
6.9 KiB
Dart

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:mobile_nebula/components/DangerButton.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<HostInfoScreen> {
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();
},
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: <Widget>[
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<Widget> 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<Widget> 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: DangerButton(
child: Text('Close Tunnel'),
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;
});
}
}