mirror of
https://github.com/DefinedNet/mobile_nebula.git
synced 2025-01-18 19:27:05 +00:00
991837676a
This pulls out a component that we can use for "dangerous" operations like deleting, and styles it in one place. It also starts to move us slowly towards Material 3, with the rounded corners on these buttons in Android. Android: |Before Light|Before Dark|After Light|After Dark| |---|---|---|---| |<img width="425" alt="Android Studio 2025-01-15 14 16 36" src="https://github.com/user-attachments/assets/4823e551-6a40-48dd-9bc1-3004699b90ea" />|<img width="417" alt="Android Studio 2025-01-15 14 16 47" src="https://github.com/user-attachments/assets/df5461fd-586e-47bb-99b9-0212e63f0454" />|<img width="413" alt="Android Studio 2025-01-15 14 15 59" src="https://github.com/user-attachments/assets/d88a6225-b71a-4886-8387-e35811a3a6ec" />|<img width="418" alt="Android Studio 2025-01-15 14 16 15" src="https://github.com/user-attachments/assets/d4f23b1c-7003-4a00-b865-4a123d8fe3e9" />| iOS: |Before Light|Before Dark|After Light|After Dark| |---|---|---|---| |<img width="437" alt="Simulator 2025-01-15 15 56 26" src="https://github.com/user-attachments/assets/87c4eed3-6d07-4858-8ad8-d8c011538154" />|<img width="445" alt="Simulator 2025-01-15 15 56 36" src="https://github.com/user-attachments/assets/9dc5b174-7bc7-48ec-a3c0-61633168c31a" />|<img width="439" alt="Simulator 2025-01-15 16 05 23" src="https://github.com/user-attachments/assets/31dc9ab6-8a3c-49c7-892d-627f16e2a8cd" />|<img width="444" alt="Simulator 2025-01-15 16 05 37" src="https://github.com/user-attachments/assets/979280d6-e1f4-4d57-a86a-10bb4def729a" />|
203 lines
7.1 KiB
Dart
203 lines
7.1 KiB
Dart
import 'package:flutter/cupertino.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_platform_widgets/flutter_platform_widgets.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();
|
|
},
|
|
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: <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;
|
|
});
|
|
}
|
|
}
|