From ed348ab126160e64ba09899c946383ca9e54768c Mon Sep 17 00:00:00 2001 From: Caleb Jasik Date: Thu, 13 Feb 2025 15:37:44 -0600 Subject: [PATCH] Flutter formatting changes (#252) * `flutter fmt lib/` * Re-enable formatting in CI --- .github/workflows/flutterfmt.yml | 5 +- lib/components/CIDRField.dart | 58 ++-- lib/components/CIDRFormField.dart | 86 +++--- lib/components/DangerButton.dart | 22 +- lib/components/FormPage.dart | 120 ++++---- lib/components/IPAndPortField.dart | 52 ++-- lib/components/IPAndPortFormField.dart | 97 ++++--- lib/components/IPField.dart | 74 +++-- lib/components/IPFormField.dart | 89 +++--- lib/components/PlatformTextFormField.dart | 149 +++++----- lib/components/SimplePage.dart | 97 ++++--- lib/components/SiteItem.dart | 50 ++-- lib/components/SiteTitle.dart | 24 +- lib/components/SpecialButton.dart | 59 ++-- lib/components/SpecialTextField.dart | 122 +++++---- lib/components/buttons/PrimaryButton.dart | 17 +- lib/components/config/ConfigButtonItem.dart | 15 +- lib/components/config/ConfigCheckboxItem.dart | 36 ++- lib/components/config/ConfigHeader.dart | 7 +- lib/components/config/ConfigItem.dart | 35 +-- lib/components/config/ConfigPageItem.dart | 49 ++-- lib/components/config/ConfigSection.dart | 32 ++- lib/components/config/ConfigTextItem.dart | 9 +- lib/main.dart | 83 +++--- lib/models/CIDR.dart | 5 +- lib/models/Certificate.dart | 65 ++--- lib/models/HostInfo.dart | 5 +- lib/models/IPAndPort.dart | 5 +- lib/models/Site.dart | 51 ++-- lib/models/StaticHosts.dart | 10 +- lib/models/UnsafeRoute.dart | 10 +- lib/screens/AboutScreen.dart | 95 ++++--- lib/screens/EnrollmentScreen.dart | 141 +++++----- lib/screens/HostInfoScreen.dart | 165 ++++++----- lib/screens/LicensesScreen.dart | 30 +- lib/screens/MainScreen.dart | 174 ++++++------ lib/screens/SettingsScreen.dart | 105 ++++--- lib/screens/SiteDetailScreen.dart | 257 ++++++++++-------- lib/screens/SiteLogsScreen.dart | 148 +++++----- lib/screens/SiteTunnelsScreen.dart | 69 ++--- .../siteConfig/AddCertificateScreen.dart | 169 ++++++------ lib/screens/siteConfig/AdvancedScreen.dart | 157 ++++++----- lib/screens/siteConfig/CAListScreen.dart | 179 ++++++------ .../siteConfig/CertificateDetailsScreen.dart | 135 +++++---- lib/screens/siteConfig/CipherScreen.dart | 35 ++- .../siteConfig/LogVerbosityScreen.dart | 33 ++- .../siteConfig/RenderedConfigScreen.dart | 29 +- lib/screens/siteConfig/ScanQRScreen.dart | 103 +++---- lib/screens/siteConfig/SiteConfigScreen.dart | 243 +++++++++-------- .../siteConfig/StaticHostmapScreen.dart | 200 ++++++++------ lib/screens/siteConfig/StaticHostsScreen.dart | 144 +++++----- lib/screens/siteConfig/UnsafeRouteScreen.dart | 123 +++++---- .../siteConfig/UnsafeRoutesScreen.dart | 65 ++--- lib/services/share.dart | 15 +- lib/services/storage.dart | 13 +- lib/services/theme.dart | 17 +- lib/services/utils.dart | 140 +++++----- 57 files changed, 2397 insertions(+), 2125 deletions(-) diff --git a/.github/workflows/flutterfmt.yml b/.github/workflows/flutterfmt.yml index 7a7590a..997f798 100644 --- a/.github/workflows/flutterfmt.yml +++ b/.github/workflows/flutterfmt.yml @@ -23,6 +23,5 @@ jobs: with: show-progress: false - # Disabled for a single PR, this will be re-enabled in the PR that has lots of formatting changes. - # - name: Check formating - # run: dart format -l120 lib/ --set-exit-if-changed --suppress-analytics --output none + - name: Check formating + run: dart format -l120 lib/ --set-exit-if-changed --suppress-analytics --output none diff --git a/lib/components/CIDRField.dart b/lib/components/CIDRField.dart index 1f3d472..d85356d 100644 --- a/lib/components/CIDRField.dart +++ b/lib/components/CIDRField.dart @@ -53,9 +53,10 @@ class _CIDRFieldState extends State { var textStyle = CupertinoTheme.of(context).textTheme.textStyle; return Container( - child: Row(children: [ - Expanded( - child: Padding( + child: Row( + children: [ + Expanded( + child: Padding( padding: EdgeInsets.fromLTRB(6, 6, 2, 6), child: IPField( help: widget.ipHelp, @@ -74,30 +75,35 @@ class _CIDRFieldState extends State { widget.onChanged!(cidr); }, controller: widget.ipController, - ))), - Text("/"), - Container( - width: Utils.textSize("bits", textStyle).width + 12, - padding: EdgeInsets.fromLTRB(2, 6, 6, 6), - child: SpecialTextField( - keyboardType: TextInputType.number, - focusNode: bitsFocus, - nextFocusNode: widget.nextFocusNode, - controller: widget.bitsController, - onChanged: (val) { - if (widget.onChanged == null) { - return; - } + ), + ), + ), + Text("/"), + Container( + width: Utils.textSize("bits", textStyle).width + 12, + padding: EdgeInsets.fromLTRB(2, 6, 6, 6), + child: SpecialTextField( + keyboardType: TextInputType.number, + focusNode: bitsFocus, + nextFocusNode: widget.nextFocusNode, + controller: widget.bitsController, + onChanged: (val) { + if (widget.onChanged == null) { + return; + } - cidr.bits = int.tryParse(val) ?? 0; - widget.onChanged!(cidr); - }, - maxLength: 2, - inputFormatters: [FilteringTextInputFormatter.digitsOnly], - textInputAction: widget.textInputAction ?? TextInputAction.done, - placeholder: 'bits', - )) - ])); + cidr.bits = int.tryParse(val) ?? 0; + widget.onChanged!(cidr); + }, + maxLength: 2, + inputFormatters: [FilteringTextInputFormatter.digitsOnly], + textInputAction: widget.textInputAction ?? TextInputAction.done, + placeholder: 'bits', + ), + ), + ], + ), + ); } @override diff --git a/lib/components/CIDRFormField.dart b/lib/components/CIDRFormField.dart index a950643..915f28d 100644 --- a/lib/components/CIDRFormField.dart +++ b/lib/components/CIDRFormField.dart @@ -18,51 +18,57 @@ class CIDRFormField extends FormField { this.ipController, this.bitsController, }) : super( - key: key, - initialValue: initialValue, - onSaved: onSaved, - validator: (cidr) { - if (cidr == null) { - return "Please fill out this field"; - } + key: key, + initialValue: initialValue, + onSaved: onSaved, + validator: (cidr) { + if (cidr == null) { + return "Please fill out this field"; + } - if (!ipValidator(cidr.ip, enableIPV6)) { - return 'Please enter a valid ip address'; - } + if (!ipValidator(cidr.ip, enableIPV6)) { + return 'Please enter a valid ip address'; + } - if (cidr.bits > 32 || cidr.bits < 0) { - return "Please enter a valid number of bits"; - } + if (cidr.bits > 32 || cidr.bits < 0) { + return "Please enter a valid number of bits"; + } - return null; - }, - builder: (FormFieldState field) { - final _CIDRFormField state = field as _CIDRFormField; + return null; + }, + builder: (FormFieldState field) { + final _CIDRFormField state = field as _CIDRFormField; - void onChangedHandler(CIDR value) { - if (onChanged != null) { - onChanged(value); - } - field.didChange(value); - } + void onChangedHandler(CIDR value) { + if (onChanged != null) { + onChanged(value); + } + field.didChange(value); + } - return Column(crossAxisAlignment: CrossAxisAlignment.end, children: [ - CIDRField( - autoFocus: autoFocus, - focusNode: focusNode, - nextFocusNode: nextFocusNode, - onChanged: onChangedHandler, - textInputAction: textInputAction, - ipController: state._effectiveIPController, - bitsController: state._effectiveBitsController, - ), - field.hasError - ? Text(field.errorText ?? "Unknown error", - style: TextStyle(color: CupertinoColors.systemRed.resolveFrom(field.context), fontSize: 13), - textAlign: TextAlign.end) - : Container(height: 0) - ]); - }); + return Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + CIDRField( + autoFocus: autoFocus, + focusNode: focusNode, + nextFocusNode: nextFocusNode, + onChanged: onChangedHandler, + textInputAction: textInputAction, + ipController: state._effectiveIPController, + bitsController: state._effectiveBitsController, + ), + field.hasError + ? Text( + field.errorText ?? "Unknown error", + style: TextStyle(color: CupertinoColors.systemRed.resolveFrom(field.context), fontSize: 13), + textAlign: TextAlign.end, + ) + : Container(height: 0), + ], + ); + }, + ); final TextEditingController? ipController; final TextEditingController? bitsController; diff --git a/lib/components/DangerButton.dart b/lib/components/DangerButton.dart index 52e30aa..bbc029b 100644 --- a/lib/components/DangerButton.dart +++ b/lib/components/DangerButton.dart @@ -13,18 +13,24 @@ class DangerButton extends StatelessWidget { Widget build(BuildContext context) { if (Platform.isAndroid) { return FilledButton( - onPressed: onPressed, - child: child, - style: FilledButton.styleFrom( - backgroundColor: Theme.of(context).colorScheme.error, - foregroundColor: Theme.of(context).colorScheme.onError)); + onPressed: onPressed, + child: child, + style: FilledButton.styleFrom( + backgroundColor: Theme.of(context).colorScheme.error, + foregroundColor: Theme.of(context).colorScheme.onError, + ), + ); } else { // Workaround for https://github.com/flutter/flutter/issues/161590 final themeData = CupertinoTheme.of(context); return CupertinoTheme( - data: themeData.copyWith(primaryColor: CupertinoColors.white), - child: CupertinoButton( - child: child, onPressed: onPressed, color: CupertinoColors.systemRed.resolveFrom(context))); + data: themeData.copyWith(primaryColor: CupertinoColors.white), + child: CupertinoButton( + child: child, + onPressed: onPressed, + color: CupertinoColors.systemRed.resolveFrom(context), + ), + ); } } } diff --git a/lib/components/FormPage.dart b/lib/components/FormPage.dart index 2cdb1d3..b471718 100644 --- a/lib/components/FormPage.dart +++ b/lib/components/FormPage.dart @@ -5,15 +5,15 @@ import 'package:mobile_nebula/services/utils.dart'; /// SimplePage with a form and built in validation and confirmation to discard changes if any are made class FormPage extends StatefulWidget { - const FormPage( - {Key? key, - required this.title, - required this.child, - required this.onSave, - required this.changed, - this.hideSave = false, - this.scrollController}) - : super(key: key); + const FormPage({ + Key? key, + required this.title, + required this.child, + required this.onSave, + required this.changed, + this.hideSave = false, + this.scrollController, + }) : super(key: key); final String title; final Function onSave; @@ -39,42 +39,61 @@ class _FormPageState extends State { changed = widget.changed || changed; return PopScope( - canPop: !changed, - onPopInvokedWithResult: (bool didPop, Object? result) async { - if (didPop) { - return; - } - final NavigatorState navigator = Navigator.of(context); + canPop: !changed, + onPopInvokedWithResult: (bool didPop, Object? result) async { + if (didPop) { + return; + } + final NavigatorState navigator = Navigator.of(context); - Utils.confirmDelete(context, 'Discard changes?', () { + Utils.confirmDelete( + context, + 'Discard changes?', + () { navigator.pop(); - }, deleteLabel: 'Yes', cancelLabel: 'No'); - }, - child: SimplePage( - leadingAction: _buildLeader(context), - trailingActions: _buildTrailer(context), - scrollController: widget.scrollController, - title: Text(widget.title), - child: Form( - key: _formKey, - onChanged: () => setState(() { - changed = true; - }), - child: widget.child), - )); + }, + deleteLabel: 'Yes', + cancelLabel: 'No', + ); + }, + child: SimplePage( + leadingAction: _buildLeader(context), + trailingActions: _buildTrailer(context), + scrollController: widget.scrollController, + title: Text(widget.title), + child: Form( + key: _formKey, + onChanged: + () => setState(() { + changed = true; + }), + child: widget.child, + ), + ), + ); } Widget _buildLeader(BuildContext context) { - return Utils.leadingBackWidget(context, label: changed ? 'Cancel' : 'Back', onPressed: () { - if (changed) { - Utils.confirmDelete(context, 'Discard changes?', () { - changed = false; + return Utils.leadingBackWidget( + context, + label: changed ? 'Cancel' : 'Back', + onPressed: () { + if (changed) { + Utils.confirmDelete( + context, + 'Discard changes?', + () { + changed = false; + Navigator.pop(context); + }, + deleteLabel: 'Yes', + cancelLabel: 'No', + ); + } else { Navigator.pop(context); - }, deleteLabel: 'Yes', cancelLabel: 'No'); - } else { - Navigator.pop(context); - } - }); + } + }, + ); } List _buildTrailer(BuildContext context) { @@ -83,21 +102,18 @@ class _FormPageState extends State { } return [ - Utils.trailingSaveWidget( - context, - () { - if (_formKey.currentState == null) { - return; - } + Utils.trailingSaveWidget(context, () { + if (_formKey.currentState == null) { + return; + } - if (!_formKey.currentState!.validate()) { - return; - } + if (!_formKey.currentState!.validate()) { + return; + } - _formKey.currentState!.save(); - widget.onSave(); - }, - ) + _formKey.currentState!.save(); + widget.onSave(); + }), ]; } } diff --git a/lib/components/IPAndPortField.dart b/lib/components/IPAndPortField.dart index 6ed4190..3d71955 100644 --- a/lib/components/IPAndPortField.dart +++ b/lib/components/IPAndPortField.dart @@ -59,9 +59,10 @@ class _IPAndPortFieldState extends State { var textStyle = CupertinoTheme.of(context).textTheme.textStyle; return Container( - child: Row(children: [ - Expanded( - child: Padding( + child: Row( + children: [ + Expanded( + child: Padding( padding: EdgeInsets.fromLTRB(6, 6, 2, 6), child: IPField( help: widget.ipHelp, @@ -76,26 +77,31 @@ class _IPAndPortFieldState extends State { }, textAlign: widget.ipTextAlign, controller: widget.ipController, - ))), - Text(":"), - Container( - width: Utils.textSize("00000", textStyle).width + 12, - padding: EdgeInsets.fromLTRB(2, 6, 6, 6), - child: SpecialTextField( - keyboardType: TextInputType.number, - focusNode: _portFocus, - nextFocusNode: widget.nextFocusNode, - controller: widget.portController, - onChanged: (val) { - _ipAndPort.port = int.tryParse(val); - widget.onChanged(_ipAndPort); - }, - maxLength: 5, - inputFormatters: [FilteringTextInputFormatter.digitsOnly], - textInputAction: TextInputAction.done, - placeholder: 'port', - )) - ])); + ), + ), + ), + Text(":"), + Container( + width: Utils.textSize("00000", textStyle).width + 12, + padding: EdgeInsets.fromLTRB(2, 6, 6, 6), + child: SpecialTextField( + keyboardType: TextInputType.number, + focusNode: _portFocus, + nextFocusNode: widget.nextFocusNode, + controller: widget.portController, + onChanged: (val) { + _ipAndPort.port = int.tryParse(val); + widget.onChanged(_ipAndPort); + }, + maxLength: 5, + inputFormatters: [FilteringTextInputFormatter.digitsOnly], + textInputAction: TextInputAction.done, + placeholder: 'port', + ), + ), + ], + ), + ); } @override diff --git a/lib/components/IPAndPortFormField.dart b/lib/components/IPAndPortFormField.dart index c9e8c4c..416fb3d 100644 --- a/lib/components/IPAndPortFormField.dart +++ b/lib/components/IPAndPortFormField.dart @@ -24,54 +24,59 @@ class IPAndPortFormField extends FormField { this.ipController, this.portController, }) : super( - key: key, - initialValue: initialValue, - onSaved: onSaved, - validator: (ipAndPort) { - if (ipAndPort == null) { - return "Please fill out this field"; - } + key: key, + initialValue: initialValue, + onSaved: onSaved, + validator: (ipAndPort) { + if (ipAndPort == null) { + return "Please fill out this field"; + } - if (!ipValidator(ipAndPort.ip, enableIPV6) && (!ipOnly && !dnsValidator(ipAndPort.ip))) { - return ipOnly ? 'Please enter a valid ip address' : 'Please enter a valid ip address or dns name'; - } + if (!ipValidator(ipAndPort.ip, enableIPV6) && (!ipOnly && !dnsValidator(ipAndPort.ip))) { + return ipOnly ? 'Please enter a valid ip address' : 'Please enter a valid ip address or dns name'; + } - if (ipAndPort.port == null || ipAndPort.port! > 65535 || ipAndPort.port! < 0) { - return "Please enter a valid port"; - } + if (ipAndPort.port == null || ipAndPort.port! > 65535 || ipAndPort.port! < 0) { + return "Please enter a valid port"; + } - return null; - }, - builder: (FormFieldState field) { - final _IPAndPortFormField state = field as _IPAndPortFormField; + return null; + }, + builder: (FormFieldState field) { + final _IPAndPortFormField state = field as _IPAndPortFormField; - void onChangedHandler(IPAndPort value) { - if (onChanged != null) { - onChanged(value); - } - field.didChange(value); - } + void onChangedHandler(IPAndPort value) { + if (onChanged != null) { + onChanged(value); + } + field.didChange(value); + } - return Column(children: [ - IPAndPortField( - ipOnly: ipOnly, - ipHelp: ipHelp, - autoFocus: autoFocus, - focusNode: focusNode, - nextFocusNode: nextFocusNode, - onChanged: onChangedHandler, - textInputAction: textInputAction, - ipController: state._effectiveIPController, - portController: state._effectivePortController, - noBorder: noBorder, - ipTextAlign: ipTextAlign, - ), - field.hasError - ? Text(field.errorText!, - style: TextStyle(color: CupertinoColors.systemRed.resolveFrom(field.context), fontSize: 13)) - : Container(height: 0) - ]); - }); + return Column( + children: [ + IPAndPortField( + ipOnly: ipOnly, + ipHelp: ipHelp, + autoFocus: autoFocus, + focusNode: focusNode, + nextFocusNode: nextFocusNode, + onChanged: onChangedHandler, + textInputAction: textInputAction, + ipController: state._effectiveIPController, + portController: state._effectivePortController, + noBorder: noBorder, + ipTextAlign: ipTextAlign, + ), + field.hasError + ? Text( + field.errorText!, + style: TextStyle(color: CupertinoColors.systemRed.resolveFrom(field.context), fontSize: 13), + ) + : Container(height: 0), + ], + ); + }, + ); final TextEditingController? ipController; final TextEditingController? portController; @@ -109,8 +114,10 @@ class _IPAndPortFormField extends FormFieldState { @override void didUpdateWidget(IPAndPortFormField oldWidget) { super.didUpdateWidget(oldWidget); - var update = - IPAndPort(ip: widget.ipController?.text, port: int.tryParse(widget.portController?.text ?? "") ?? null); + var update = IPAndPort( + ip: widget.ipController?.text, + port: int.tryParse(widget.portController?.text ?? "") ?? null, + ); bool shouldUpdate = false; if (widget.ipController != oldWidget.ipController) { diff --git a/lib/components/IPField.dart b/lib/components/IPField.dart index d08b530..96c38c6 100644 --- a/lib/components/IPField.dart +++ b/lib/components/IPField.dart @@ -16,19 +16,19 @@ class IPField extends StatelessWidget { final controller; final textAlign; - const IPField( - {Key? key, - this.ipOnly = false, - this.help = "ip address", - this.autoFocus = false, - this.focusNode, - this.nextFocusNode, - this.onChanged, - this.textPadding = const EdgeInsets.all(6.0), - this.textInputAction, - this.controller, - this.textAlign = TextAlign.center}) - : super(key: key); + const IPField({ + Key? key, + this.ipOnly = false, + this.help = "ip address", + this.autoFocus = false, + this.focusNode, + this.nextFocusNode, + this.onChanged, + this.textPadding = const EdgeInsets.all(6.0), + this.textInputAction, + this.controller, + this.textAlign = TextAlign.center, + }) : super(key: key); @override Widget build(BuildContext context) { @@ -36,21 +36,22 @@ class IPField extends StatelessWidget { final double? ipWidth = ipOnly ? Utils.textSize("000000000000000", textStyle).width + 12 : null; return SizedBox( - width: ipWidth, - child: SpecialTextField( - keyboardType: ipOnly ? TextInputType.numberWithOptions(decimal: true, signed: true) : null, - textAlign: textAlign, - autofocus: autoFocus, - focusNode: focusNode, - nextFocusNode: nextFocusNode, - controller: controller, - onChanged: onChanged, - maxLength: ipOnly ? 15 : null, - maxLengthEnforcement: ipOnly ? MaxLengthEnforcement.enforced : MaxLengthEnforcement.none, - inputFormatters: ipOnly ? [IPTextInputFormatter()] : [FilteringTextInputFormatter.allow(RegExp(r'[^\s]+'))], - textInputAction: this.textInputAction, - placeholder: help, - )); + width: ipWidth, + child: SpecialTextField( + keyboardType: ipOnly ? TextInputType.numberWithOptions(decimal: true, signed: true) : null, + textAlign: textAlign, + autofocus: autoFocus, + focusNode: focusNode, + nextFocusNode: nextFocusNode, + controller: controller, + onChanged: onChanged, + maxLength: ipOnly ? 15 : null, + maxLengthEnforcement: ipOnly ? MaxLengthEnforcement.enforced : MaxLengthEnforcement.none, + inputFormatters: ipOnly ? [IPTextInputFormatter()] : [FilteringTextInputFormatter.allow(RegExp(r'[^\s]+'))], + textInputAction: this.textInputAction, + placeholder: help, + ), + ); } } @@ -59,16 +60,13 @@ class IPTextInputFormatter extends TextInputFormatter { @override TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue) { - return _selectionAwareTextManipulation( - newValue, - (String substring) { - return whitelistedPattern - .allMatches(substring) - .map((Match match) => match.group(0)!) - .join() - .replaceAll(RegExp(r','), '.'); - }, - ); + return _selectionAwareTextManipulation(newValue, (String substring) { + return whitelistedPattern + .allMatches(substring) + .map((Match match) => match.group(0)!) + .join() + .replaceAll(RegExp(r','), '.'); + }); } } diff --git a/lib/components/IPFormField.dart b/lib/components/IPFormField.dart index 4811db9..bde80d5 100644 --- a/lib/components/IPFormField.dart +++ b/lib/components/IPFormField.dart @@ -25,52 +25,57 @@ class IPFormField extends FormField { crossAxisAlignment = CrossAxisAlignment.center, textAlign = TextAlign.center, }) : super( - key: key, - initialValue: initialValue, - onSaved: onSaved, - validator: (ip) { - if (ip == null || ip == "") { - return "Please fill out this field"; - } + key: key, + initialValue: initialValue, + onSaved: onSaved, + validator: (ip) { + if (ip == null || ip == "") { + return "Please fill out this field"; + } - if (!ipValidator(ip, enableIPV6) || (!ipOnly && !dnsValidator(ip))) { - print(ip); - return ipOnly ? 'Please enter a valid ip address' : 'Please enter a valid ip address or dns name'; - } + if (!ipValidator(ip, enableIPV6) || (!ipOnly && !dnsValidator(ip))) { + print(ip); + return ipOnly ? 'Please enter a valid ip address' : 'Please enter a valid ip address or dns name'; + } - return null; - }, - builder: (FormFieldState field) { - final _IPFormField state = field as _IPFormField; + return null; + }, + builder: (FormFieldState field) { + final _IPFormField state = field as _IPFormField; - void onChangedHandler(String value) { - if (onChanged != null) { - onChanged(value); - } - field.didChange(value); - } + void onChangedHandler(String value) { + if (onChanged != null) { + onChanged(value); + } + field.didChange(value); + } - return Column(crossAxisAlignment: crossAxisAlignment, children: [ - IPField( - ipOnly: ipOnly, - help: help, - autoFocus: autoFocus, - focusNode: focusNode, - nextFocusNode: nextFocusNode, - onChanged: onChangedHandler, - textPadding: textPadding, - textInputAction: textInputAction, - controller: state._effectiveController, - textAlign: textAlign), - field.hasError - ? Text( - field.errorText!, - style: TextStyle(color: CupertinoColors.systemRed.resolveFrom(field.context), fontSize: 13), - textAlign: textAlign, - ) - : Container(height: 0) - ]); - }); + return Column( + crossAxisAlignment: crossAxisAlignment, + children: [ + IPField( + ipOnly: ipOnly, + help: help, + autoFocus: autoFocus, + focusNode: focusNode, + nextFocusNode: nextFocusNode, + onChanged: onChangedHandler, + textPadding: textPadding, + textInputAction: textInputAction, + controller: state._effectiveController, + textAlign: textAlign, + ), + field.hasError + ? Text( + field.errorText!, + style: TextStyle(color: CupertinoColors.systemRed.resolveFrom(field.context), fontSize: 13), + textAlign: textAlign, + ) + : Container(height: 0), + ], + ); + }, + ); final TextEditingController? controller; diff --git a/lib/components/PlatformTextFormField.dart b/lib/components/PlatformTextFormField.dart index 6ff9107..624d78a 100644 --- a/lib/components/PlatformTextFormField.dart +++ b/lib/components/PlatformTextFormField.dart @@ -5,81 +5,86 @@ import 'package:mobile_nebula/components/SpecialTextField.dart'; class PlatformTextFormField extends FormField { //TODO: auto-validate, enabled? - PlatformTextFormField( - {Key? key, - widgetKey, - this.controller, - focusNode, - nextFocusNode, - TextInputType? keyboardType, - textInputAction, - List? inputFormatters, - textAlign, - autofocus, - maxLines = 1, - maxLength, - maxLengthEnforcement, - onChanged, - keyboardAppearance, - minLines, - expands, - suffix, - textAlignVertical, - String? initialValue, - String? placeholder, - FormFieldValidator? validator, - ValueChanged? onSaved}) - : super( - key: key, - initialValue: controller != null ? controller.text : (initialValue ?? ''), - onSaved: onSaved, - validator: (str) { - if (validator != null) { - return validator(str); - } + PlatformTextFormField({ + Key? key, + widgetKey, + this.controller, + focusNode, + nextFocusNode, + TextInputType? keyboardType, + textInputAction, + List? inputFormatters, + textAlign, + autofocus, + maxLines = 1, + maxLength, + maxLengthEnforcement, + onChanged, + keyboardAppearance, + minLines, + expands, + suffix, + textAlignVertical, + String? initialValue, + String? placeholder, + FormFieldValidator? validator, + ValueChanged? onSaved, + }) : super( + key: key, + initialValue: controller != null ? controller.text : (initialValue ?? ''), + onSaved: onSaved, + validator: (str) { + if (validator != null) { + return validator(str); + } - return null; - }, - builder: (FormFieldState field) { - final _PlatformTextFormFieldState state = field as _PlatformTextFormFieldState; + return null; + }, + builder: (FormFieldState field) { + final _PlatformTextFormFieldState state = field as _PlatformTextFormFieldState; - void onChangedHandler(String value) { - if (onChanged != null) { - onChanged(value); - } - field.didChange(value); - } + void onChangedHandler(String value) { + if (onChanged != null) { + onChanged(value); + } + field.didChange(value); + } - return Column(crossAxisAlignment: CrossAxisAlignment.end, children: [ - SpecialTextField( - key: widgetKey, - controller: state._effectiveController, - focusNode: focusNode, - nextFocusNode: nextFocusNode, - keyboardType: keyboardType, - textInputAction: textInputAction, - textAlign: textAlign, - autofocus: autofocus, - maxLines: maxLines, - maxLength: maxLength, - maxLengthEnforcement: maxLengthEnforcement, - onChanged: onChangedHandler, - keyboardAppearance: keyboardAppearance, - minLines: minLines, - expands: expands, - textAlignVertical: textAlignVertical, - placeholder: placeholder, - inputFormatters: inputFormatters, - suffix: suffix), - field.hasError - ? Text( - field.errorText!, - style: TextStyle(color: CupertinoColors.systemRed.resolveFrom(field.context), fontSize: 13), - textAlign: textAlign, - ) - : Container(height: 0) - ]); - }); + return Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + SpecialTextField( + key: widgetKey, + controller: state._effectiveController, + focusNode: focusNode, + nextFocusNode: nextFocusNode, + keyboardType: keyboardType, + textInputAction: textInputAction, + textAlign: textAlign, + autofocus: autofocus, + maxLines: maxLines, + maxLength: maxLength, + maxLengthEnforcement: maxLengthEnforcement, + onChanged: onChangedHandler, + keyboardAppearance: keyboardAppearance, + minLines: minLines, + expands: expands, + textAlignVertical: textAlignVertical, + placeholder: placeholder, + inputFormatters: inputFormatters, + suffix: suffix, + ), + field.hasError + ? Text( + field.errorText!, + style: TextStyle(color: CupertinoColors.systemRed.resolveFrom(field.context), fontSize: 13), + textAlign: textAlign, + ) + : Container(height: 0), + ], + ); + }, + ); final TextEditingController? controller; diff --git a/lib/components/SimplePage.dart b/lib/components/SimplePage.dart index df4b6e4..78d584d 100644 --- a/lib/components/SimplePage.dart +++ b/lib/components/SimplePage.dart @@ -2,29 +2,24 @@ import 'package:flutter/material.dart'; import 'package:flutter_platform_widgets/flutter_platform_widgets.dart'; import 'package:pull_to_refresh/pull_to_refresh.dart'; -enum SimpleScrollable { - none, - vertical, - horizontal, - both, -} +enum SimpleScrollable { none, vertical, horizontal, both } class SimplePage extends StatelessWidget { - const SimplePage( - {Key? key, - required this.title, - required this.child, - this.leadingAction, - this.trailingActions = const [], - this.scrollable = SimpleScrollable.vertical, - this.scrollbar = true, - this.scrollController, - this.bottomBar, - this.onRefresh, - this.onLoading, - this.alignment, - this.refreshController}) - : super(key: key); + const SimplePage({ + Key? key, + required this.title, + required this.child, + this.leadingAction, + this.trailingActions = const [], + this.scrollable = SimpleScrollable.vertical, + this.scrollbar = true, + this.scrollController, + this.bottomBar, + this.onRefresh, + this.onLoading, + this.alignment, + this.refreshController, + }) : super(key: key); final Widget title; final Widget child; @@ -52,9 +47,10 @@ class SimplePage extends StatelessWidget { if (scrollable == SimpleScrollable.vertical || scrollable == SimpleScrollable.both) { realChild = SingleChildScrollView( - scrollDirection: Axis.vertical, - child: realChild, - controller: refreshController == null ? scrollController : null); + scrollDirection: Axis.vertical, + child: realChild, + controller: refreshController == null ? scrollController : null, + ); addScrollbar = true; } @@ -65,19 +61,20 @@ class SimplePage extends StatelessWidget { if (refreshController != null) { realChild = RefreshConfiguration( - headerTriggerDistance: 100, - footerTriggerDistance: -100, - maxUnderScrollExtent: 100, - child: SmartRefresher( - scrollController: scrollController, - onRefresh: onRefresh, - onLoading: onLoading, - controller: refreshController!, - child: realChild, - enablePullUp: onLoading != null, - enablePullDown: onRefresh != null, - footer: ClassicFooter(loadStyle: LoadStyle.ShowWhenLoading), - )); + headerTriggerDistance: 100, + footerTriggerDistance: -100, + maxUnderScrollExtent: 100, + child: SmartRefresher( + scrollController: scrollController, + onRefresh: onRefresh, + onLoading: onLoading, + controller: refreshController!, + child: realChild, + enablePullUp: onLoading != null, + enablePullDown: onRefresh != null, + footer: ClassicFooter(loadStyle: LoadStyle.ShowWhenLoading), + ), + ); addScrollbar = true; } @@ -90,24 +87,24 @@ class SimplePage extends StatelessWidget { } if (bottomBar != null) { - realChild = Column(children: [ - Expanded(child: realChild), - bottomBar!, - ]); + realChild = Column(children: [Expanded(child: realChild), bottomBar!]); } return PlatformScaffold( - backgroundColor: Theme.of(context).scaffoldBackgroundColor, - appBar: PlatformAppBar( - title: title, - leading: leadingAction, - trailingActions: trailingActions, - cupertino: (_, __) => CupertinoNavigationBarData( + backgroundColor: Theme.of(context).scaffoldBackgroundColor, + appBar: PlatformAppBar( + title: title, + leading: leadingAction, + trailingActions: trailingActions, + cupertino: + (_, __) => CupertinoNavigationBarData( transitionBetweenRoutes: false, // TODO: set title on route, show here instead of just "Back" previousPageTitle: 'Back', - padding: EdgeInsetsDirectional.only(end: 8.0)), - ), - body: SafeArea(child: realChild)); + padding: EdgeInsetsDirectional.only(end: 8.0), + ), + ), + body: SafeArea(child: realChild), + ); } } diff --git a/lib/components/SiteItem.dart b/lib/components/SiteItem.dart index 4896d20..d1d8d4f 100644 --- a/lib/components/SiteItem.dart +++ b/lib/components/SiteItem.dart @@ -13,17 +13,19 @@ class SiteItem extends StatelessWidget { @override Widget build(BuildContext context) { - final borderColor = site.errors.length > 0 - ? CupertinoColors.systemRed.resolveFrom(context) - : site.connected + final borderColor = + site.errors.length > 0 + ? CupertinoColors.systemRed.resolveFrom(context) + : site.connected ? CupertinoColors.systemGreen.resolveFrom(context) : CupertinoColors.systemGrey2.resolveFrom(context); final border = BorderSide(color: borderColor, width: 10); return Container( - margin: EdgeInsets.symmetric(vertical: 6), - decoration: BoxDecoration(border: Border(left: border)), - child: _buildContent(context)); + margin: EdgeInsets.symmetric(vertical: 6), + decoration: BoxDecoration(border: Border(left: border)), + child: _buildContent(context), + ); } Widget _buildContent(BuildContext context) { @@ -32,21 +34,25 @@ class SiteItem extends StatelessWidget { Theme.of(context).brightness == Brightness.dark ? 'images/dn-logo-dark.svg' : 'images/dn-logo-light.svg'; return SpecialButton( - decoration: - BoxDecoration(border: Border(top: border, bottom: border), color: Utils.configItemBackground(context)), - onPressed: onPressed, - child: Padding( - padding: EdgeInsets.fromLTRB(10, 10, 5, 10), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - site.managed - ? Padding(padding: EdgeInsets.only(right: 10), child: SvgPicture.asset(dnIcon, width: 12)) - : Container(), - Expanded(child: Text(site.name, style: TextStyle(fontWeight: FontWeight.bold))), - Padding(padding: EdgeInsets.only(right: 10)), - Icon(CupertinoIcons.forward, color: CupertinoColors.placeholderText.resolveFrom(context), size: 18) - ], - ))); + decoration: BoxDecoration( + border: Border(top: border, bottom: border), + color: Utils.configItemBackground(context), + ), + onPressed: onPressed, + child: Padding( + padding: EdgeInsets.fromLTRB(10, 10, 5, 10), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + site.managed + ? Padding(padding: EdgeInsets.only(right: 10), child: SvgPicture.asset(dnIcon, width: 12)) + : Container(), + Expanded(child: Text(site.name, style: TextStyle(fontWeight: FontWeight.bold))), + Padding(padding: EdgeInsets.only(right: 10)), + Icon(CupertinoIcons.forward, color: CupertinoColors.placeholderText.resolveFrom(context), size: 18), + ], + ), + ), + ); } } diff --git a/lib/components/SiteTitle.dart b/lib/components/SiteTitle.dart index 58a50ea..f85f476 100644 --- a/lib/components/SiteTitle.dart +++ b/lib/components/SiteTitle.dart @@ -14,17 +14,17 @@ class SiteTitle extends StatelessWidget { Theme.of(context).brightness == Brightness.dark ? 'images/dn-logo-dark.svg' : 'images/dn-logo-light.svg'; return IntrinsicWidth( - child: Padding( - padding: EdgeInsets.symmetric(horizontal: 16), - child: Row(children: [ - site.managed - ? Padding(padding: EdgeInsets.only(right: 10), child: SvgPicture.asset(dnIcon, width: 12)) - : Container(), - Expanded( - child: Text( - site.name, - overflow: TextOverflow.ellipsis, - )) - ]))); + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 16), + child: Row( + children: [ + site.managed + ? Padding(padding: EdgeInsets.only(right: 10), child: SvgPicture.asset(dnIcon, width: 12)) + : Container(), + Expanded(child: Text(site.name, overflow: TextOverflow.ellipsis)), + ], + ), + ), + ); } } diff --git a/lib/components/SpecialButton.dart b/lib/components/SpecialButton.dart index fd97aff..0779531 100644 --- a/lib/components/SpecialButton.dart +++ b/lib/components/SpecialButton.dart @@ -6,7 +6,7 @@ import 'package:flutter/material.dart'; // This is a button that pushes the bare minimum onto you, it doesn't even respect button themes - unless you tell it to class SpecialButton extends StatefulWidget { const SpecialButton({Key? key, this.child, this.color, this.onPressed, this.useButtonTheme = false, this.decoration}) - : super(key: key); + : super(key: key); final Widget? child; final Color? color; @@ -32,14 +32,13 @@ class _SpecialButtonState extends State with SingleTickerProvider } return Material( - textStyle: textStyle, - child: Ink( - decoration: widget.decoration, - color: widget.color, - child: InkWell( - child: widget.child, - onTap: widget.onPressed, - ))); + textStyle: textStyle, + child: Ink( + decoration: widget.decoration, + color: widget.color, + child: InkWell(child: widget.child, onTap: widget.onPressed), + ), + ); } Widget _buildGeneric() { @@ -49,21 +48,22 @@ class _SpecialButtonState extends State with SingleTickerProvider } return Container( - decoration: widget.decoration, - child: GestureDetector( - behavior: HitTestBehavior.opaque, - onTapDown: _handleTapDown, - onTapUp: _handleTapUp, - onTapCancel: _handleTapCancel, - onTap: widget.onPressed, - child: Semantics( - button: true, - child: FadeTransition( - opacity: _opacityAnimation!, - child: DefaultTextStyle(style: textStyle, child: Container(child: widget.child, color: widget.color)), - ), + decoration: widget.decoration, + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTapDown: _handleTapDown, + onTapUp: _handleTapUp, + onTapCancel: _handleTapCancel, + onTap: widget.onPressed, + child: Semantics( + button: true, + child: FadeTransition( + opacity: _opacityAnimation!, + child: DefaultTextStyle(style: textStyle, child: Container(child: widget.child, color: widget.color)), ), - )); + ), + ), + ); } // Eyeballed values. Feel free to tweak. @@ -77,11 +77,7 @@ class _SpecialButtonState extends State with SingleTickerProvider @override void initState() { super.initState(); - _animationController = AnimationController( - duration: const Duration(milliseconds: 200), - value: 0.0, - vsync: this, - ); + _animationController = AnimationController(duration: const Duration(milliseconds: 200), value: 0.0, vsync: this); _opacityAnimation = _animationController!.drive(CurveTween(curve: Curves.decelerate)).drive(_opacityTween); _setTween(); } @@ -131,9 +127,10 @@ class _SpecialButtonState extends State with SingleTickerProvider } final bool wasHeldDown = _buttonHeldDown; - final TickerFuture ticker = _buttonHeldDown - ? _animationController!.animateTo(1.0, duration: kFadeOutDuration) - : _animationController!.animateTo(0.0, duration: kFadeInDuration); + final TickerFuture ticker = + _buttonHeldDown + ? _animationController!.animateTo(1.0, duration: kFadeOutDuration) + : _animationController!.animateTo(0.0, duration: kFadeInDuration); ticker.then((void value) { if (mounted && wasHeldDown != _buttonHeldDown) { diff --git a/lib/components/SpecialTextField.dart b/lib/components/SpecialTextField.dart index aa4f645..1912aac 100644 --- a/lib/components/SpecialTextField.dart +++ b/lib/components/SpecialTextField.dart @@ -4,31 +4,31 @@ import 'package:flutter_platform_widgets/flutter_platform_widgets.dart'; /// A normal TextField or CupertinoTextField that looks the same on all platforms class SpecialTextField extends StatefulWidget { - const SpecialTextField( - {Key? key, - this.placeholder, - this.suffix, - this.controller, - this.focusNode, - this.nextFocusNode, - this.autocorrect, - this.minLines, - this.maxLines, - this.maxLength, - this.maxLengthEnforcement, - this.style, - this.keyboardType, - this.textInputAction, - this.textCapitalization, - this.textAlign, - this.autofocus, - this.onChanged, - this.enabled, - this.expands, - this.keyboardAppearance, - this.textAlignVertical, - this.inputFormatters}) - : super(key: key); + const SpecialTextField({ + Key? key, + this.placeholder, + this.suffix, + this.controller, + this.focusNode, + this.nextFocusNode, + this.autocorrect, + this.minLines, + this.maxLines, + this.maxLength, + this.maxLengthEnforcement, + this.style, + this.keyboardType, + this.textInputAction, + this.textCapitalization, + this.textAlign, + this.autofocus, + this.onChanged, + this.enabled, + this.expands, + this.keyboardAppearance, + this.textAlignVertical, + this.inputFormatters, + }) : super(key: key); final String? placeholder; final TextEditingController? controller; @@ -76,42 +76,48 @@ class _SpecialTextFieldState extends State { @override Widget build(BuildContext context) { return PlatformTextField( - autocorrect: widget.autocorrect, - minLines: widget.minLines, - maxLines: widget.maxLines, - maxLength: widget.maxLength, - maxLengthEnforcement: widget.maxLengthEnforcement, - keyboardType: widget.keyboardType, - keyboardAppearance: widget.keyboardAppearance, - textInputAction: widget.textInputAction, - textCapitalization: widget.textCapitalization, - textAlign: widget.textAlign, - textAlignVertical: widget.textAlignVertical, - autofocus: widget.autofocus, - focusNode: widget.focusNode, - onChanged: widget.onChanged, - enabled: widget.enabled ?? true, - onSubmitted: (_) { - if (widget.nextFocusNode != null) { - FocusScope.of(context).requestFocus(widget.nextFocusNode); - } - }, - expands: widget.expands, - inputFormatters: formatters, - material: (_, __) => MaterialTextFieldData( + autocorrect: widget.autocorrect, + minLines: widget.minLines, + maxLines: widget.maxLines, + maxLength: widget.maxLength, + maxLengthEnforcement: widget.maxLengthEnforcement, + keyboardType: widget.keyboardType, + keyboardAppearance: widget.keyboardAppearance, + textInputAction: widget.textInputAction, + textCapitalization: widget.textCapitalization, + textAlign: widget.textAlign, + textAlignVertical: widget.textAlignVertical, + autofocus: widget.autofocus, + focusNode: widget.focusNode, + onChanged: widget.onChanged, + enabled: widget.enabled ?? true, + onSubmitted: (_) { + if (widget.nextFocusNode != null) { + FocusScope.of(context).requestFocus(widget.nextFocusNode); + } + }, + expands: widget.expands, + inputFormatters: formatters, + material: + (_, __) => MaterialTextFieldData( decoration: InputDecoration( - border: InputBorder.none, - contentPadding: EdgeInsets.zero, - isDense: true, - hintText: widget.placeholder, - counterText: '', - suffix: widget.suffix)), - cupertino: (_, __) => CupertinoTextFieldData( + border: InputBorder.none, + contentPadding: EdgeInsets.zero, + isDense: true, + hintText: widget.placeholder, + counterText: '', + suffix: widget.suffix, + ), + ), + cupertino: + (_, __) => CupertinoTextFieldData( decoration: BoxDecoration(), padding: EdgeInsets.zero, placeholder: widget.placeholder, - suffix: widget.suffix), - style: widget.style, - controller: widget.controller); + suffix: widget.suffix, + ), + style: widget.style, + controller: widget.controller, + ); } } diff --git a/lib/components/buttons/PrimaryButton.dart b/lib/components/buttons/PrimaryButton.dart index 349b4c0..449d523 100644 --- a/lib/components/buttons/PrimaryButton.dart +++ b/lib/components/buttons/PrimaryButton.dart @@ -13,16 +13,21 @@ class PrimaryButton extends StatelessWidget { Widget build(BuildContext context) { if (Platform.isAndroid) { return FilledButton( - onPressed: onPressed, - child: child, - style: FilledButton.styleFrom(backgroundColor: Theme.of(context).colorScheme.primary)); + onPressed: onPressed, + child: child, + style: FilledButton.styleFrom(backgroundColor: Theme.of(context).colorScheme.primary), + ); } else { // Workaround for https://github.com/flutter/flutter/issues/161590 final themeData = CupertinoTheme.of(context); return CupertinoTheme( - data: themeData.copyWith(primaryColor: CupertinoColors.white), - child: CupertinoButton( - child: child, onPressed: onPressed, color: CupertinoColors.secondaryLabel.resolveFrom(context))); + data: themeData.copyWith(primaryColor: CupertinoColors.white), + child: CupertinoButton( + child: child, + onPressed: onPressed, + color: CupertinoColors.secondaryLabel.resolveFrom(context), + ), + ); } } } diff --git a/lib/components/config/ConfigButtonItem.dart b/lib/components/config/ConfigButtonItem.dart index 955f9d5..de484f2 100644 --- a/lib/components/config/ConfigButtonItem.dart +++ b/lib/components/config/ConfigButtonItem.dart @@ -13,12 +13,13 @@ class ConfigButtonItem extends StatelessWidget { @override Widget build(BuildContext context) { return SpecialButton( - color: Utils.configItemBackground(context), - onPressed: onPressed, - useButtonTheme: true, - child: Container( - constraints: BoxConstraints(minHeight: Utils.minInteractiveSize, minWidth: double.infinity), - child: Center(child: content), - )); + color: Utils.configItemBackground(context), + onPressed: onPressed, + useButtonTheme: true, + child: Container( + constraints: BoxConstraints(minHeight: Utils.minInteractiveSize, minWidth: double.infinity), + child: Center(child: content), + ), + ); } } diff --git a/lib/components/config/ConfigCheckboxItem.dart b/lib/components/config/ConfigCheckboxItem.dart index 8947b3d..7d37c8b 100644 --- a/lib/components/config/ConfigCheckboxItem.dart +++ b/lib/components/config/ConfigCheckboxItem.dart @@ -3,9 +3,14 @@ import 'package:mobile_nebula/components/SpecialButton.dart'; import 'package:mobile_nebula/services/utils.dart'; class ConfigCheckboxItem extends StatelessWidget { - const ConfigCheckboxItem( - {Key? key, this.label, this.content, this.labelWidth = 100, this.onChanged, this.checked = false}) - : super(key: key); + const ConfigCheckboxItem({ + Key? key, + this.label, + this.content, + this.labelWidth = 100, + this.onChanged, + this.checked = false, + }) : super(key: key); final Widget? label; final Widget? content; @@ -16,18 +21,19 @@ class ConfigCheckboxItem extends StatelessWidget { @override Widget build(BuildContext context) { Widget item = Container( - padding: EdgeInsets.symmetric(horizontal: 15), - constraints: BoxConstraints(minHeight: Utils.minInteractiveSize, minWidth: double.infinity), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - label != null ? Container(width: labelWidth, child: label) : Container(), - Expanded(child: Container(child: content, padding: EdgeInsets.only(right: 10))), - checked - ? Icon(CupertinoIcons.check_mark, color: CupertinoColors.systemBlue.resolveFrom(context)) - : Container() - ], - )); + padding: EdgeInsets.symmetric(horizontal: 15), + constraints: BoxConstraints(minHeight: Utils.minInteractiveSize, minWidth: double.infinity), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + label != null ? Container(width: labelWidth, child: label) : Container(), + Expanded(child: Container(child: content, padding: EdgeInsets.only(right: 10))), + checked + ? Icon(CupertinoIcons.check_mark, color: CupertinoColors.systemBlue.resolveFrom(context)) + : Container(), + ], + ), + ); if (onChanged != null) { return SpecialButton( diff --git a/lib/components/config/ConfigHeader.dart b/lib/components/config/ConfigHeader.dart index bcb9c09..032e147 100644 --- a/lib/components/config/ConfigHeader.dart +++ b/lib/components/config/ConfigHeader.dart @@ -20,10 +20,9 @@ class ConfigHeader extends StatelessWidget { padding: const EdgeInsets.only(left: 10.0, top: 30.0, bottom: 5.0, right: 10.0), child: Text( label, - style: basicTextStyle(context).copyWith( - color: color ?? CupertinoColors.secondaryLabel.resolveFrom(context), - fontSize: _headerFontSize, - ), + style: basicTextStyle( + context, + ).copyWith(color: color ?? CupertinoColors.secondaryLabel.resolveFrom(context), fontSize: _headerFontSize), ), ); } diff --git a/lib/components/config/ConfigItem.dart b/lib/components/config/ConfigItem.dart index dcb3247..81b0667 100644 --- a/lib/components/config/ConfigItem.dart +++ b/lib/components/config/ConfigItem.dart @@ -5,13 +5,13 @@ import 'package:flutter/material.dart'; import 'package:mobile_nebula/services/utils.dart'; class ConfigItem extends StatelessWidget { - const ConfigItem( - {Key? key, - this.label, - required this.content, - this.labelWidth = 100, - this.crossAxisAlignment = CrossAxisAlignment.center}) - : super(key: key); + const ConfigItem({ + Key? key, + this.label, + required this.content, + this.labelWidth = 100, + this.crossAxisAlignment = CrossAxisAlignment.center, + }) : super(key: key); final Widget? label; final Widget content; @@ -28,15 +28,16 @@ class ConfigItem extends StatelessWidget { } return Container( - color: Utils.configItemBackground(context), - padding: EdgeInsets.symmetric(vertical: 6, horizontal: 15), - constraints: BoxConstraints(minHeight: Utils.minInteractiveSize), - child: Row( - crossAxisAlignment: crossAxisAlignment, - children: [ - Container(width: labelWidth, child: DefaultTextStyle(style: textStyle, child: Container(child: label))), - Expanded(child: DefaultTextStyle(style: textStyle, child: Container(child: content))), - ], - )); + color: Utils.configItemBackground(context), + padding: EdgeInsets.symmetric(vertical: 6, horizontal: 15), + constraints: BoxConstraints(minHeight: Utils.minInteractiveSize), + child: Row( + crossAxisAlignment: crossAxisAlignment, + children: [ + Container(width: labelWidth, child: DefaultTextStyle(style: textStyle, child: Container(child: label))), + Expanded(child: DefaultTextStyle(style: textStyle, child: Container(child: content))), + ], + ), + ); } } diff --git a/lib/components/config/ConfigPageItem.dart b/lib/components/config/ConfigPageItem.dart index 80354a1..1b93839 100644 --- a/lib/components/config/ConfigPageItem.dart +++ b/lib/components/config/ConfigPageItem.dart @@ -6,15 +6,15 @@ import 'package:mobile_nebula/components/SpecialButton.dart'; import 'package:mobile_nebula/services/utils.dart'; class ConfigPageItem extends StatelessWidget { - const ConfigPageItem( - {Key? key, - this.label, - this.content, - this.labelWidth = 100, - this.onPressed, - this.disabled = false, - this.crossAxisAlignment = CrossAxisAlignment.center}) - : super(key: key); + const ConfigPageItem({ + Key? key, + this.label, + this.content, + this.labelWidth = 100, + this.onPressed, + this.disabled = false, + this.crossAxisAlignment = CrossAxisAlignment.center, + }) : super(key: key); final Widget? label; final Widget? content; @@ -30,8 +30,10 @@ class ConfigPageItem extends StatelessWidget { if (Platform.isAndroid) { final origTheme = Theme.of(context); theme = origTheme.copyWith( - textTheme: origTheme.textTheme - .copyWith(labelLarge: origTheme.textTheme.labelLarge!.copyWith(fontWeight: FontWeight.normal))); + textTheme: origTheme.textTheme.copyWith( + labelLarge: origTheme.textTheme.labelLarge!.copyWith(fontWeight: FontWeight.normal), + ), + ); return Theme(data: theme, child: _buildContent(context)); } else { final origTheme = CupertinoTheme.of(context); @@ -45,18 +47,19 @@ class ConfigPageItem extends StatelessWidget { onPressed: this.disabled ? null : onPressed, color: Utils.configItemBackground(context), child: Container( - padding: EdgeInsets.symmetric(vertical: 6, horizontal: 15), - constraints: BoxConstraints(minHeight: Utils.minInteractiveSize, minWidth: double.infinity), - child: Row( - crossAxisAlignment: crossAxisAlignment, - children: [ - label != null ? Container(width: labelWidth, child: label) : Container(), - Expanded(child: Container(child: content, padding: EdgeInsets.only(right: 10))), - this.disabled - ? Container() - : Icon(CupertinoIcons.forward, color: CupertinoColors.placeholderText.resolveFrom(context), size: 18) - ], - )), + padding: EdgeInsets.symmetric(vertical: 6, horizontal: 15), + constraints: BoxConstraints(minHeight: Utils.minInteractiveSize, minWidth: double.infinity), + child: Row( + crossAxisAlignment: crossAxisAlignment, + children: [ + label != null ? Container(width: labelWidth, child: label) : Container(), + Expanded(child: Container(child: content, padding: EdgeInsets.only(right: 10))), + this.disabled + ? Container() + : Icon(CupertinoIcons.forward, color: CupertinoColors.placeholderText.resolveFrom(context), size: 18), + ], + ), + ), ); } } diff --git a/lib/components/config/ConfigSection.dart b/lib/components/config/ConfigSection.dart index 544b960..58cf3da 100644 --- a/lib/components/config/ConfigSection.dart +++ b/lib/components/config/ConfigSection.dart @@ -5,7 +5,7 @@ import 'ConfigHeader.dart'; class ConfigSection extends StatelessWidget { const ConfigSection({Key? key, this.label, required this.children, this.borderColor, this.labelColor}) - : super(key: key); + : super(key: key); final List children; final String? label; @@ -27,19 +27,27 @@ class ConfigSection extends StatelessWidget { if (children[i + 1].runtimeType.toString() == 'ConfigButtonItem') { pad = 0; } - _children.add(Padding( - child: Divider(height: 1, color: Utils.configSectionBorder(context)), padding: EdgeInsets.only(left: pad))); + _children.add( + Padding( + child: Divider(height: 1, color: Utils.configSectionBorder(context)), + padding: EdgeInsets.only(left: pad), + ), + ); } } - return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - label != null ? ConfigHeader(label: label!, color: labelColor) : Container(height: 20), - Container( - decoration: - BoxDecoration(border: Border(top: border, bottom: border), color: Utils.configItemBackground(context)), - child: Column( - children: _children, - )) - ]); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + label != null ? ConfigHeader(label: label!, color: labelColor) : Container(height: 20), + Container( + decoration: BoxDecoration( + border: Border(top: border, bottom: border), + color: Utils.configItemBackground(context), + ), + child: Column(children: _children), + ), + ], + ); } } diff --git a/lib/components/config/ConfigTextItem.dart b/lib/components/config/ConfigTextItem.dart index 75bd6d6..577f559 100644 --- a/lib/components/config/ConfigTextItem.dart +++ b/lib/components/config/ConfigTextItem.dart @@ -1,9 +1,12 @@ import 'package:flutter/cupertino.dart'; class ConfigTextItem extends StatelessWidget { - const ConfigTextItem( - {Key? key, this.placeholder, this.controller, this.style = const TextStyle(fontFamily: 'RobotoMono')}) - : super(key: key); + const ConfigTextItem({ + Key? key, + this.placeholder, + this.controller, + this.style = const TextStyle(fontFamily: 'RobotoMono'), + }) : super(key: key); final String? placeholder; final TextEditingController? controller; diff --git a/lib/main.dart b/lib/main.dart index 758e276..61fd999 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -19,16 +19,13 @@ Future main() async { var settings = Settings(); if (settings.trackErrors) { - await SentryFlutter.init( - (options) { - options.dsn = 'https://96106df405ade3f013187dfc8e4200e7@o920269.ingest.us.sentry.io/4508132321001472'; - // Capture all traces. May need to adjust if overwhelming - options.tracesSampleRate = 1.0; - // For each trace, capture all profiles - options.profilesSampleRate = 1.0; - }, - appRunner: () => runApp(Main()), - ); + await SentryFlutter.init((options) { + options.dsn = 'https://96106df405ade3f013187dfc8e4200e7@o920269.ingest.us.sentry.io/4508132321001472'; + // Capture all traces. May need to adjust if overwhelming + options.tracesSampleRate = 1.0; + // For each trace, capture all profiles + options.profilesSampleRate = 1.0; + }, appRunner: () => runApp(Main())); } else { runApp(Main()); } @@ -91,41 +88,41 @@ class _AppState extends State { return PlatformProvider( settings: PlatformSettingsData(iosUsesMaterialWidgets: true), - builder: (context) => PlatformApp( - debugShowCheckedModeBanner: false, - localizationsDelegates: >[ - DefaultMaterialLocalizations.delegate, - DefaultWidgetsLocalizations.delegate, - DefaultCupertinoLocalizations.delegate, - ], - title: 'Nebula', - material: (_, __) { - return new MaterialAppData( - themeMode: brightness == Brightness.light ? ThemeMode.light : ThemeMode.dark, - theme: brightness == Brightness.light ? theme.light() : theme.dark(), - ); - }, - cupertino: (_, __) => CupertinoAppData( - theme: CupertinoThemeData(brightness: brightness), - ), - onGenerateRoute: (settings) { - if (settings.name == '/') { - return platformPageRoute(context: context, builder: (context) => MainScreen(this.dnEnrolled)); - } + builder: + (context) => PlatformApp( + debugShowCheckedModeBanner: false, + localizationsDelegates: >[ + DefaultMaterialLocalizations.delegate, + DefaultWidgetsLocalizations.delegate, + DefaultCupertinoLocalizations.delegate, + ], + title: 'Nebula', + material: (_, __) { + return new MaterialAppData( + themeMode: brightness == Brightness.light ? ThemeMode.light : ThemeMode.dark, + theme: brightness == Brightness.light ? theme.light() : theme.dark(), + ); + }, + cupertino: (_, __) => CupertinoAppData(theme: CupertinoThemeData(brightness: brightness)), + onGenerateRoute: (settings) { + if (settings.name == '/') { + return platformPageRoute(context: context, builder: (context) => MainScreen(this.dnEnrolled)); + } - final uri = Uri.parse(settings.name!); - if (uri.path == EnrollmentScreen.routeName) { - // TODO: maybe implement this as a dialog instead of a page, you can stack multiple enrollment screens which is annoying in dev - return platformPageRoute( - context: context, - builder: (context) => - EnrollmentScreen(code: EnrollmentScreen.parseCode(settings.name!), stream: this.dnEnrolled), - ); - } + final uri = Uri.parse(settings.name!); + if (uri.path == EnrollmentScreen.routeName) { + // TODO: maybe implement this as a dialog instead of a page, you can stack multiple enrollment screens which is annoying in dev + return platformPageRoute( + context: context, + builder: + (context) => + EnrollmentScreen(code: EnrollmentScreen.parseCode(settings.name!), stream: this.dnEnrolled), + ); + } - return null; - }, - ), + return null; + }, + ), ); } } diff --git a/lib/models/CIDR.dart b/lib/models/CIDR.dart index c5746a8..a6207ee 100644 --- a/lib/models/CIDR.dart +++ b/lib/models/CIDR.dart @@ -19,9 +19,6 @@ class CIDR { throw 'Invalid CIDR string'; } - return CIDR( - ip: parts[0], - bits: int.parse(parts[1]), - ); + return CIDR(ip: parts[0], bits: int.parse(parts[1])); } } diff --git a/lib/models/Certificate.dart b/lib/models/Certificate.dart index b48d984..7d9a2a0 100644 --- a/lib/models/Certificate.dart +++ b/lib/models/Certificate.dart @@ -4,13 +4,13 @@ class CertificateInfo { CertificateValidity? validity; CertificateInfo.debug({this.rawCert = ""}) - : this.cert = Certificate.debug(), - this.validity = CertificateValidity.debug(); + : this.cert = Certificate.debug(), + this.validity = CertificateValidity.debug(); CertificateInfo.fromJson(Map json) - : cert = Certificate.fromJson(json['Cert']), - rawCert = json['RawCert'], - validity = CertificateValidity.fromJson(json['Validity']); + : cert = Certificate.fromJson(json['Cert']), + rawCert = json['RawCert'], + validity = CertificateValidity.fromJson(json['Validity']); CertificateInfo({required this.cert, this.rawCert, this.validity}); @@ -24,15 +24,12 @@ class Certificate { String fingerprint; String signature; - Certificate.debug() - : this.details = CertificateDetails.debug(), - this.fingerprint = "DEBUG", - this.signature = "DEBUG"; + Certificate.debug() : this.details = CertificateDetails.debug(), this.fingerprint = "DEBUG", this.signature = "DEBUG"; Certificate.fromJson(Map json) - : details = CertificateDetails.fromJson(json['details']), - fingerprint = json['fingerprint'], - signature = json['signature']; + : details = CertificateDetails.fromJson(json['details']), + fingerprint = json['fingerprint'], + signature = json['signature']; } class CertificateDetails { @@ -47,37 +44,33 @@ class CertificateDetails { String issuer; CertificateDetails.debug() - : this.name = "DEBUG", - notBefore = DateTime.now(), - notAfter = DateTime.now(), - publicKey = "", - groups = [], - ips = [], - subnets = [], - isCa = false, - issuer = "DEBUG"; + : this.name = "DEBUG", + notBefore = DateTime.now(), + notAfter = DateTime.now(), + publicKey = "", + groups = [], + ips = [], + subnets = [], + isCa = false, + issuer = "DEBUG"; CertificateDetails.fromJson(Map json) - : name = json['name'], - notBefore = DateTime.parse(json['notBefore']), - notAfter = DateTime.parse(json['notAfter']), - publicKey = json['publicKey'], - groups = List.from(json['groups']), - ips = List.from(json['ips']), - subnets = List.from(json['subnets']), - isCa = json['isCa'], - issuer = json['issuer']; + : name = json['name'], + notBefore = DateTime.parse(json['notBefore']), + notAfter = DateTime.parse(json['notAfter']), + publicKey = json['publicKey'], + groups = List.from(json['groups']), + ips = List.from(json['ips']), + subnets = List.from(json['subnets']), + isCa = json['isCa'], + issuer = json['issuer']; } class CertificateValidity { bool valid; String reason; - CertificateValidity.debug() - : this.valid = true, - this.reason = ""; + CertificateValidity.debug() : this.valid = true, this.reason = ""; - CertificateValidity.fromJson(Map json) - : valid = json['Valid'], - reason = json['Reason']; + CertificateValidity.fromJson(Map json) : valid = json['Valid'], reason = json['Reason']; } diff --git a/lib/models/HostInfo.dart b/lib/models/HostInfo.dart index 0982419..d9c14c0 100644 --- a/lib/models/HostInfo.dart +++ b/lib/models/HostInfo.dart @@ -52,10 +52,7 @@ class UDPAddress { String ip; int port; - UDPAddress({ - required this.ip, - required this.port, - }); + UDPAddress({required this.ip, required this.port}); @override String toString() { diff --git a/lib/models/IPAndPort.dart b/lib/models/IPAndPort.dart index 82aaf76..a7c8aa8 100644 --- a/lib/models/IPAndPort.dart +++ b/lib/models/IPAndPort.dart @@ -21,9 +21,6 @@ class IPAndPort { //TODO: Uri.parse is as close as I could get to parsing both ipv4 and v6 addresses with a port without bringing a whole mess of code into here final uri = Uri.parse("ugh://$val"); - return IPAndPort( - ip: uri.host, - port: uri.port, - ); + return IPAndPort(ip: uri.host, port: uri.port); } } diff --git a/lib/models/Site.dart b/lib/models/Site.dart index df98dfc..861b100 100644 --- a/lib/models/Site.dart +++ b/lib/models/Site.dart @@ -94,19 +94,22 @@ class Site { this.lastManagedUpdate = lastManagedUpdate; _updates = EventChannel('net.defined.nebula/${this.id}'); - _updates.receiveBroadcastStream().listen((d) { - try { - _updateFromJson(d); - _change.add(null); - } catch (err) { - //TODO: handle the error - print(err); - } - }, onError: (err) { - _updateFromJson(err.details); - var error = err as PlatformException; - _change.addError(error.message ?? 'An unexpected error occurred'); - }); + _updates.receiveBroadcastStream().listen( + (d) { + try { + _updateFromJson(d); + _change.add(null); + } catch (err) { + //TODO: handle the error + print(err); + } + }, + onError: (err) { + _updateFromJson(err.details); + var error = err as PlatformException; + _change.addError(error.message ?? 'An unexpected error occurred'); + }, + ); } factory Site.fromJson(Map json) { @@ -220,9 +223,11 @@ class Site { 'id': id, 'staticHostmap': staticHostmap, 'unsafeRoutes': unsafeRoutes, - 'ca': ca.map((cert) { - return cert.rawCert; - }).join('\n'), + 'ca': ca + .map((cert) { + return cert.rawCert; + }) + .join('\n'), 'cert': certInfo?.rawCert, 'key': key, 'lhDuration': lhDuration, @@ -341,8 +346,11 @@ class Site { Future getHostInfo(String vpnIp, bool pending) async { try { - var ret = await platform - .invokeMethod("active.getHostInfo", {"id": id, "vpnIp": vpnIp, "pending": pending}); + var ret = await platform.invokeMethod("active.getHostInfo", { + "id": id, + "vpnIp": vpnIp, + "pending": pending, + }); final h = jsonDecode(ret); if (h == null) { return null; @@ -358,8 +366,11 @@ class Site { Future setRemoteForTunnel(String vpnIp, String addr) async { try { - var ret = await platform - .invokeMethod("active.setRemoteForTunnel", {"id": id, "vpnIp": vpnIp, "addr": addr}); + var ret = await platform.invokeMethod("active.setRemoteForTunnel", { + "id": id, + "vpnIp": vpnIp, + "addr": addr, + }); final h = jsonDecode(ret); if (h == null) { return null; diff --git a/lib/models/StaticHosts.dart b/lib/models/StaticHosts.dart index 1402f5c..8c853fa 100644 --- a/lib/models/StaticHosts.dart +++ b/lib/models/StaticHosts.dart @@ -14,16 +14,10 @@ class StaticHost { result.add(IPAndPort.fromString(item)); }); - return StaticHost( - lighthouse: json['lighthouse'], - destinations: result, - ); + return StaticHost(lighthouse: json['lighthouse'], destinations: result); } Map toJson() { - return { - 'lighthouse': lighthouse, - 'destinations': destinations, - }; + return {'lighthouse': lighthouse, 'destinations': destinations}; } } diff --git a/lib/models/UnsafeRoute.dart b/lib/models/UnsafeRoute.dart index 0486290..935b518 100644 --- a/lib/models/UnsafeRoute.dart +++ b/lib/models/UnsafeRoute.dart @@ -5,16 +5,10 @@ class UnsafeRoute { UnsafeRoute({this.route, this.via}); factory UnsafeRoute.fromJson(Map json) { - return UnsafeRoute( - route: json['route'], - via: json['via'], - ); + return UnsafeRoute(route: json['route'], via: json['via']); } Map toJson() { - return { - 'route': route, - 'via': via, - }; + return {'route': route, 'via': via}; } } diff --git a/lib/screens/AboutScreen.dart b/lib/screens/AboutScreen.dart index 44b96cc..1f4655c 100644 --- a/lib/screens/AboutScreen.dart +++ b/lib/screens/AboutScreen.dart @@ -37,52 +37,67 @@ class _AboutScreenState extends State { // packageInfo is null until ready is true if (!ready) { return Center( - child: PlatformCircularProgressIndicator(cupertino: (_, __) { - return CupertinoProgressIndicatorData(radius: 50); - }), + child: PlatformCircularProgressIndicator( + cupertino: (_, __) { + return CupertinoProgressIndicatorData(radius: 50); + }, + ), ); } return SimplePage( title: Text('About'), - child: Column(children: [ - ConfigSection(children: [ - ConfigItem( - label: Text('App version'), - labelWidth: 150, - content: _buildText('${packageInfo!.version}-${packageInfo!.buildNumber} (sha: $gitSha)')), - ConfigItem( - label: Text('Nebula version'), labelWidth: 150, content: _buildText('$nebulaVersion ($goVersion)')), - ConfigItem( - label: Text('Flutter version'), - labelWidth: 150, - content: _buildText(flutterVersion['frameworkVersion'] ?? 'Unknown')), - ConfigItem( - label: Text('Dart version'), - labelWidth: 150, - content: _buildText(flutterVersion['dartSdkVersion'] ?? 'Unknown')), - ]), - ConfigSection(children: [ - //TODO: wire up these other pages -// ConfigPageItem(label: Text('Changelog'), labelWidth: 300, onPressed: () => Utils.launchUrl('https://defined.net/mobile/changelog', context)), - ConfigPageItem( - label: Text('Privacy policy'), - labelWidth: 300, - onPressed: () => Utils.launchUrl('https://www.defined.net/privacy/', context)), - ConfigPageItem( - label: Text('Licenses'), - labelWidth: 300, - onPressed: () => Utils.openPage(context, (context) { - return LicensesScreen(); - })), - ]), - Padding( + child: Column( + children: [ + ConfigSection( + children: [ + ConfigItem( + label: Text('App version'), + labelWidth: 150, + content: _buildText('${packageInfo!.version}-${packageInfo!.buildNumber} (sha: $gitSha)'), + ), + ConfigItem( + label: Text('Nebula version'), + labelWidth: 150, + content: _buildText('$nebulaVersion ($goVersion)'), + ), + ConfigItem( + label: Text('Flutter version'), + labelWidth: 150, + content: _buildText(flutterVersion['frameworkVersion'] ?? 'Unknown'), + ), + ConfigItem( + label: Text('Dart version'), + labelWidth: 150, + content: _buildText(flutterVersion['dartSdkVersion'] ?? 'Unknown'), + ), + ], + ), + ConfigSection( + children: [ + //TODO: wire up these other pages + // ConfigPageItem(label: Text('Changelog'), labelWidth: 300, onPressed: () => Utils.launchUrl('https://defined.net/mobile/changelog', context)), + ConfigPageItem( + label: Text('Privacy policy'), + labelWidth: 300, + onPressed: () => Utils.launchUrl('https://www.defined.net/privacy/', context), + ), + ConfigPageItem( + label: Text('Licenses'), + labelWidth: 300, + onPressed: + () => Utils.openPage(context, (context) { + return LicensesScreen(); + }), + ), + ], + ), + Padding( padding: EdgeInsets.only(top: 20), - child: Text( - 'Copyright © 2024 Defined Networking, Inc', - textAlign: TextAlign.center, - )), - ]), + child: Text('Copyright © 2024 Defined Networking, Inc', textAlign: TextAlign.center), + ), + ], + ), ); } diff --git a/lib/screens/EnrollmentScreen.dart b/lib/screens/EnrollmentScreen.dart index 2cee762..a7e517d 100644 --- a/lib/screens/EnrollmentScreen.dart +++ b/lib/screens/EnrollmentScreen.dart @@ -94,65 +94,78 @@ class _EnrollmentScreenState extends State { } else { // No code, show the error child = Padding( - child: Center( - child: Text( + child: Center( + child: Text( 'No valid enrollment code was found.\n\nContact your administrator to obtain a new enrollment code.', textAlign: TextAlign.center, - )), - padding: EdgeInsets.only(top: 20)); + ), + ), + padding: EdgeInsets.only(top: 20), + ); } } else if (this.error != null) { // Error while enrolling, display it child = Center( - child: Column( - children: [ - Padding( + child: Column( + children: [ + Padding( child: SelectableText( - 'There was an issue while attempting to enroll this device. Contact your administrator to obtain a new enrollment code.'), - padding: EdgeInsets.symmetric(horizontal: 16, vertical: 20)), - Padding( - child: SelectableText.rich(TextSpan(children: [ - TextSpan(text: 'If the problem persists, please let us know at '), + 'There was an issue while attempting to enroll this device. Contact your administrator to obtain a new enrollment code.', + ), + padding: EdgeInsets.symmetric(horizontal: 16, vertical: 20), + ), + Padding( + child: SelectableText.rich( TextSpan( - text: 'support@defined.net', - style: bodyTextStyle.apply(color: colorScheme.primary), - recognizer: TapGestureRecognizer() - ..onTap = () async { - if (await canLaunchUrl(contactUri)) { - print(await launchUrl(contactUri)); - } - }, + children: [ + TextSpan(text: 'If the problem persists, please let us know at '), + TextSpan( + text: 'support@defined.net', + style: bodyTextStyle.apply(color: colorScheme.primary), + recognizer: + TapGestureRecognizer() + ..onTap = () async { + if (await canLaunchUrl(contactUri)) { + print(await launchUrl(contactUri)); + } + }, + ), + TextSpan(text: ' and provide the following error:'), + ], ), - TextSpan(text: ' and provide the following error:'), - ])), - padding: EdgeInsets.symmetric(horizontal: 16, vertical: 10)), - Container( - child: Padding(child: SelectableText(this.error!), padding: EdgeInsets.all(16)), - color: Theme.of(context).colorScheme.errorContainer, - ), - ], - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - )); + ), + padding: EdgeInsets.symmetric(horizontal: 16, vertical: 10), + ), + Container( + child: Padding(child: SelectableText(this.error!), padding: EdgeInsets.all(16)), + color: Theme.of(context).colorScheme.errorContainer, + ), + ], + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + ), + ); } else if (this.enrolled) { // Enrollment complete! child = Padding( - child: Center( - child: Text( - 'Enrollment complete! 🎉', - textAlign: TextAlign.center, - )), - padding: EdgeInsets.only(top: 20)); + child: Center(child: Text('Enrollment complete! 🎉', textAlign: TextAlign.center)), + padding: EdgeInsets.only(top: 20), + ); } else { // Have a code and actively enrolling alignment = Alignment.center; child = Center( - child: Column(children: [ - Padding(child: Text('Contacting DN for enrollment'), padding: EdgeInsets.only(bottom: 25)), - PlatformCircularProgressIndicator(cupertino: (_, __) { - return CupertinoProgressIndicatorData(radius: 50); - }) - ])); + child: Column( + children: [ + Padding(child: Text('Contacting DN for enrollment'), padding: EdgeInsets.only(bottom: 25)), + PlatformCircularProgressIndicator( + cupertino: (_, __) { + return CupertinoProgressIndicatorData(radius: 50); + }, + ), + ], + ), + ); } return SimplePage(title: Text('Enroll with Managed Nebula'), child: child, alignment: alignment); @@ -182,32 +195,26 @@ class _EnrollmentScreenState extends State { } final input = Padding( - padding: EdgeInsets.symmetric(horizontal: 16), - child: PlatformTextFormField( - controller: enrollInput, - validator: validator, - hintText: 'from admin.defined.net', - cupertino: (_, __) => CupertinoTextFormFieldData( - prefix: Text("Code or link"), - ), - material: (_, __) => MaterialTextFormFieldData( - decoration: const InputDecoration(labelText: 'Code or link'), - ), - )); - - final form = Form( - key: _formKey, - child: Platform.isAndroid ? input : ConfigSection(children: [input]), + padding: EdgeInsets.symmetric(horizontal: 16), + child: PlatformTextFormField( + controller: enrollInput, + validator: validator, + hintText: 'from admin.defined.net', + cupertino: (_, __) => CupertinoTextFormFieldData(prefix: Text("Code or link")), + material: (_, __) => MaterialTextFormFieldData(decoration: const InputDecoration(labelText: 'Code or link')), + ), ); - return Column(children: [ - Padding( - padding: EdgeInsets.symmetric(vertical: 32), - child: form, - ), - Padding( + final form = Form(key: _formKey, child: Platform.isAndroid ? input : ConfigSection(children: [input])); + + return Column( + children: [ + Padding(padding: EdgeInsets.symmetric(vertical: 32), child: form), + Padding( padding: EdgeInsets.symmetric(horizontal: 16), - child: Row(children: [Expanded(child: PrimaryButton(child: Text('Submit'), onPressed: onSubmit))])) - ]); + child: Row(children: [Expanded(child: PrimaryButton(child: Text('Submit'), onPressed: onSubmit))]), + ), + ], + ); } } diff --git a/lib/screens/HostInfoScreen.dart b/lib/screens/HostInfoScreen.dart index f70f881..3a899e9 100644 --- a/lib/screens/HostInfoScreen.dart +++ b/lib/screens/HostInfoScreen.dart @@ -53,49 +53,66 @@ class _HostInfoScreenState extends State { 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()])); + 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( + 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(), - ]); + 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}')), - ]); + 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)]); + label: 'REMOTES', + children: [ConfigItem(content: Text('No remote addresses yet'), labelWidth: 0)], + ); } return widget.pending ? _buildStaticRemotes() : _buildEditRemotes(); @@ -109,26 +126,28 @@ class _HostInfoScreenState extends State { 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); + 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; } - } catch (err) { - Utils.popError(context, 'Error while changing the remote', err.toString()); - } - }, - )); + + 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); @@ -142,12 +161,14 @@ class _HostInfoScreenState extends State { 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, - )); + 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); @@ -155,22 +176,26 @@ class _HostInfoScreenState extends State { 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')))); + 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 { diff --git a/lib/screens/LicensesScreen.dart b/lib/screens/LicensesScreen.dart index 0ddcd6b..78a311a 100644 --- a/lib/screens/LicensesScreen.dart +++ b/lib/screens/LicensesScreen.dart @@ -24,20 +24,13 @@ class LicensesScreen extends StatelessWidget { return Padding( padding: const EdgeInsets.all(8), child: PlatformListTile( - onTap: () { - Utils.openPage( - context, - (_) => LicenceDetailPage( - title: capitalize(dep.name), - licence: dep.license!, - ), - ); - }, - title: Text( - capitalize(dep.name), - ), - subtitle: Text(dep.description), - trailing: Icon(context.platformIcons.forward, size: 18)), + onTap: () { + Utils.openPage(context, (_) => LicenceDetailPage(title: capitalize(dep.name), licence: dep.license!)); + }, + title: Text(capitalize(dep.name)), + subtitle: Text(dep.description), + trailing: Icon(context.platformIcons.forward, size: 18), + ), ); }, ), @@ -62,14 +55,7 @@ class LicenceDetailPage extends StatelessWidget { decoration: BoxDecoration(borderRadius: BorderRadius.circular(8)), child: SingleChildScrollView( physics: const BouncingScrollPhysics(), - child: Column( - children: [ - Text( - licence, - style: const TextStyle(fontSize: 15), - ), - ], - ), + child: Column(children: [Text(licence, style: const TextStyle(fontSize: 15))]), ), ), ), diff --git a/lib/screens/MainScreen.dart b/lib/screens/MainScreen.dart index 6684e03..a61fe5f 100644 --- a/lib/screens/MainScreen.dart +++ b/lib/screens/MainScreen.dart @@ -115,11 +115,7 @@ class _MainScreenState extends State { if (kDebugMode) { debugSite = Row( - children: [ - _debugSave(badDebugSave), - _debugSave(goodDebugSave), - _debugClearKeys(), - ], + children: [_debugSave(badDebugSave), _debugSave(goodDebugSave), _debugClearKeys()], mainAxisAlignment: MainAxisAlignment.center, ); } @@ -141,13 +137,15 @@ class _MainScreenState extends State { leadingAction: PlatformIconButton( padding: EdgeInsets.zero, icon: Icon(Icons.add, size: 28.0), - onPressed: () => Utils.openPage(context, (context) { - return SiteConfigScreen( - onSave: (_) { - _loadSites(); - }, - supportsQRScanning: supportsQRScanning); - }), + onPressed: + () => Utils.openPage(context, (context) { + return SiteConfigScreen( + onSave: (_) { + _loadSites(); + }, + supportsQRScanning: supportsQRScanning, + ); + }), ), refreshController: refreshController, onRefresh: () { @@ -169,13 +167,15 @@ class _MainScreenState extends State { Widget _buildBody() { if (error != null) { return Center( - child: Padding( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: error!, - ), - padding: EdgeInsets.symmetric(vertical: 0, horizontal: 10))); + child: Padding( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: error!, + ), + padding: EdgeInsets.symmetric(vertical: 0, horizontal: 10), + ), + ); } return _buildSites(); @@ -183,19 +183,22 @@ class _MainScreenState extends State { Widget _buildNoSites() { return Padding( - padding: const EdgeInsets.all(8.0), - child: Center( - child: Column( - children: [ - Padding( - padding: const EdgeInsets.fromLTRB(0, 8.0, 0, 8.0), - child: Text('Welcome to Nebula!', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20)), - ), - Text('You don\'t have any site configurations installed yet. Hit the plus button above to get started.', - textAlign: TextAlign.center), - ], - ), - )); + padding: const EdgeInsets.all(8.0), + child: Center( + child: Column( + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(0, 8.0, 0, 8.0), + child: Text('Welcome to Nebula!', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20)), + ), + Text( + 'You don\'t have any site configurations installed yet. Hit the plus button above to get started.', + textAlign: TextAlign.center, + ), + ], + ), + ), + ); } Widget _buildSites() { @@ -205,7 +208,8 @@ class _MainScreenState extends State { List items = []; sites!.forEach((site) { - items.add(SiteItem( + items.add( + SiteItem( key: Key(site.id), site: site, onPressed: () { @@ -216,42 +220,45 @@ class _MainScreenState extends State { supportsQRScanning: supportsQRScanning, ); }); - })); + }, + ), + ); }); Widget child = ReorderableListView( - shrinkWrap: true, - scrollController: scrollController, - padding: EdgeInsets.symmetric(vertical: 5), - children: items, - onReorder: (oldI, newI) async { - if (oldI < newI) { - // removing the item at oldIndex will shorten the list by 1. - newI -= 1; - } + shrinkWrap: true, + scrollController: scrollController, + padding: EdgeInsets.symmetric(vertical: 5), + children: items, + onReorder: (oldI, newI) async { + if (oldI < newI) { + // removing the item at oldIndex will shorten the list by 1. + newI -= 1; + } - setState(() { - final Site moved = sites!.removeAt(oldI); - sites!.insert(newI, moved); - }); - - for (var i = 0; i < sites!.length; i++) { - if (sites![i].sortKey == i) { - continue; - } - - sites![i].sortKey = i; - try { - await sites![i].save(); - } catch (err) { - //TODO: display error at the end - print('ERR ${sites![i].name} - $err'); - } - } - - _loadSites(); + setState(() { + final Site moved = sites!.removeAt(oldI); + sites!.insert(newI, moved); }); + for (var i = 0; i < sites!.length; i++) { + if (sites![i].sortKey == i) { + continue; + } + + sites![i].sortKey = i; + try { + await sites![i].save(); + } catch (err) { + //TODO: display error at the end + print('ERR ${sites![i].name} - $err'); + } + } + + _loadSites(); + }, + ); + if (Platform.isIOS) { child = CupertinoTheme(child: child, data: CupertinoTheme.of(context)); } @@ -267,16 +274,18 @@ class _MainScreenState extends State { var uuid = Uuid(); var s = Site( - name: siteConfig['name']!, - id: uuid.v4(), - staticHostmap: { - "10.1.0.1": StaticHost( - lighthouse: true, - destinations: [IPAndPort(ip: '10.1.1.53', port: 4242), IPAndPort(ip: '1::1', port: 4242)]) - }, - ca: [CertificateInfo.debug(rawCert: siteConfig['ca'])], - certInfo: CertificateInfo.debug(rawCert: siteConfig['cert']), - unsafeRoutes: [UnsafeRoute(route: '10.3.3.3/32', via: '10.1.0.1')]); + name: siteConfig['name']!, + id: uuid.v4(), + staticHostmap: { + "10.1.0.1": StaticHost( + lighthouse: true, + destinations: [IPAndPort(ip: '10.1.1.53', port: 4242), IPAndPort(ip: '1::1', port: 4242)], + ), + }, + ca: [CertificateInfo.debug(rawCert: siteConfig['ca'])], + certInfo: CertificateInfo.debug(rawCert: siteConfig['cert']), + unsafeRoutes: [UnsafeRoute(route: '10.3.3.3/32', via: '10.1.0.1')], + ); s.key = siteConfig['key']; @@ -309,14 +318,17 @@ class _MainScreenState extends State { var site = Site.fromJson(rawSite); //TODO: we need to cancel change listeners when we rebuild - site.onChange().listen((_) { - setState(() {}); - }, onError: (err) { - setState(() {}); - if (ModalRoute.of(context)!.isCurrent) { - Utils.popError(context, "${site.name} Error", err); - } - }); + site.onChange().listen( + (_) { + setState(() {}); + }, + onError: (err) { + setState(() {}); + if (ModalRoute.of(context)!.isCurrent) { + Utils.popError(context, "${site.name} Error", err); + } + }, + ); sites!.add(site); } catch (err) { diff --git a/lib/screens/SettingsScreen.dart b/lib/screens/SettingsScreen.dart index db01e69..c9b465f 100644 --- a/lib/screens/SettingsScreen.dart +++ b/lib/screens/SettingsScreen.dart @@ -38,10 +38,11 @@ class _SettingsScreenState extends State { Widget build(BuildContext context) { List colorSection = []; - colorSection.add(ConfigItem( - label: Text('Use system colors'), - labelWidth: 200, - content: Align( + colorSection.add( + ConfigItem( + label: Text('Use system colors'), + labelWidth: 200, + content: Align( alignment: Alignment.centerRight, child: Switch.adaptive( materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, @@ -49,13 +50,16 @@ class _SettingsScreenState extends State { settings.useSystemColors = value; }, value: settings.useSystemColors, - )), - )); + ), + ), + ), + ); if (!settings.useSystemColors) { - colorSection.add(ConfigItem( - label: Text('Dark mode'), - content: Align( + colorSection.add( + ConfigItem( + label: Text('Dark mode'), + content: Align( alignment: Alignment.centerRight, child: Switch.adaptive( materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, @@ -63,16 +67,19 @@ class _SettingsScreenState extends State { settings.darkMode = value; }, value: settings.darkMode, - )), - )); + ), + ), + ), + ); } List items = []; items.add(ConfigSection(children: colorSection)); - items.add(ConfigItem( - label: Text('Wrap log output'), - labelWidth: 200, - content: Align( + items.add( + ConfigItem( + label: Text('Wrap log output'), + labelWidth: 200, + content: Align( alignment: Alignment.centerRight, child: Switch.adaptive( materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, @@ -82,14 +89,18 @@ class _SettingsScreenState extends State { settings.logWrap = value; }); }, - )), - )); + ), + ), + ), + ); - items.add(ConfigSection(children: [ - ConfigItem( - label: Text('Report errors automatically'), - labelWidth: 250, - content: Align( + items.add( + ConfigSection( + children: [ + ConfigItem( + label: Text('Report errors automatically'), + labelWidth: 250, + content: Align( alignment: Alignment.centerRight, child: Switch.adaptive( materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, @@ -99,27 +110,35 @@ class _SettingsScreenState extends State { settings.trackErrors = value; }); }, - ))), - ])); - - items.add(ConfigSection(children: [ - ConfigPageItem( - label: Text('Enroll with Managed Nebula'), - labelWidth: 250, - onPressed: () => - Utils.openPage(context, (context) => EnrollmentScreen(stream: widget.stream, allowCodeEntry: true))) - ])); - - items.add(ConfigSection(children: [ - ConfigPageItem( - label: Text('About'), - onPressed: () => Utils.openPage(context, (context) => AboutScreen()), - ) - ])); - - return SimplePage( - title: Text('Settings'), - child: Column(children: items), + ), + ), + ), + ], + ), ); + + items.add( + ConfigSection( + children: [ + ConfigPageItem( + label: Text('Enroll with Managed Nebula'), + labelWidth: 250, + onPressed: + () => + Utils.openPage(context, (context) => EnrollmentScreen(stream: widget.stream, allowCodeEntry: true)), + ), + ], + ), + ); + + items.add( + ConfigSection( + children: [ + ConfigPageItem(label: Text('About'), onPressed: () => Utils.openPage(context, (context) => AboutScreen())), + ], + ), + ); + + return SimplePage(title: Text('Settings'), child: Column(children: items)); } } diff --git a/lib/screens/SiteDetailScreen.dart b/lib/screens/SiteDetailScreen.dart index 58e0ad3..d99505d 100644 --- a/lib/screens/SiteDetailScreen.dart +++ b/lib/screens/SiteDetailScreen.dart @@ -23,12 +23,8 @@ import '../components/SiteTitle.dart'; //TODO: ios is now the problem with connecting screwing our ability to query the hostmap (its a race) class SiteDetailScreen extends StatefulWidget { - const SiteDetailScreen({ - Key? key, - required this.site, - this.onChanged, - required this.supportsQRScanning, - }) : super(key: key); + const SiteDetailScreen({Key? key, required this.site, this.onChanged, required this.supportsQRScanning}) + : super(key: key); final Site site; final Function? onChanged; @@ -54,21 +50,24 @@ class _SiteDetailScreenState extends State { _listHostmap(); } - onChange = site.onChange().listen((_) { - // TODO: Gross hack... we get site.connected = true to trigger the toggle before the VPN service has started. - // If we fetch the hostmap now we'll never get a response. Wait until Nebula is running. - if (site.status == 'Connected') { - _listHostmap(); - } else { - activeHosts = null; - pendingHosts = null; - } + onChange = site.onChange().listen( + (_) { + // TODO: Gross hack... we get site.connected = true to trigger the toggle before the VPN service has started. + // If we fetch the hostmap now we'll never get a response. Wait until Nebula is running. + if (site.status == 'Connected') { + _listHostmap(); + } else { + activeHosts = null; + pendingHosts = null; + } - setState(() {}); - }, onError: (err) { - setState(() {}); - Utils.popError(context, "Error", err); - }); + setState(() {}); + }, + onError: (err) { + setState(() {}); + Utils.popError(context, "Error", err); + }, + ); super.initState(); } @@ -84,27 +83,33 @@ class _SiteDetailScreenState extends State { final title = SiteTitle(site: widget.site); return SimplePage( - title: title, - leadingAction: Utils.leadingBackWidget(context, onPressed: () { + title: title, + leadingAction: Utils.leadingBackWidget( + context, + onPressed: () { if (changed && widget.onChanged != null) { widget.onChanged!(); } Navigator.pop(context); - }), - refreshController: refreshController, - onRefresh: () async { - if (site.connected && site.status == "Connected") { - await _listHostmap(); - } - refreshController.refreshCompleted(); }, - child: Column(children: [ + ), + refreshController: refreshController, + onRefresh: () async { + if (site.connected && site.status == "Connected") { + await _listHostmap(); + } + refreshController.refreshCompleted(); + }, + child: Column( + children: [ _buildErrors(), _buildConfig(), site.connected ? _buildHosts() : Container(), _buildSiteDetails(), _buildDelete(), - ])); + ], + ), + ); } Widget _buildErrors() { @@ -114,8 +119,12 @@ class _SiteDetailScreenState extends State { List items = []; site.errors.forEach((error) { - items.add(ConfigItem( - labelWidth: 0, content: Padding(padding: EdgeInsets.symmetric(vertical: 10), child: SelectableText(error)))); + items.add( + ConfigItem( + labelWidth: 0, + content: Padding(padding: EdgeInsets.symmetric(vertical: 10), child: SelectableText(error)), + ), + ); }); return ConfigSection( @@ -140,29 +149,38 @@ class _SiteDetailScreenState extends State { } } - return ConfigSection(children: [ - ConfigItem( + return ConfigSection( + children: [ + ConfigItem( label: Text('Status'), - content: Row(mainAxisAlignment: MainAxisAlignment.end, children: [ - Padding( + content: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Padding( padding: EdgeInsets.only(right: 5), - child: Text(widget.site.status, - style: TextStyle(color: CupertinoColors.secondaryLabel.resolveFrom(context)))), - Switch.adaptive( - value: widget.site.connected, - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - onChanged: widget.site.errors.length > 0 && !widget.site.connected ? null : handleChange, - ) - ])), - ConfigPageItem( - label: Text('Logs'), - onPressed: () { - Utils.openPage(context, (context) { - return SiteLogsScreen(site: widget.site); - }); - }, - ), - ]); + child: Text( + widget.site.status, + style: TextStyle(color: CupertinoColors.secondaryLabel.resolveFrom(context)), + ), + ), + Switch.adaptive( + value: widget.site.connected, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + onChanged: widget.site.errors.length > 0 && !widget.site.connected ? null : handleChange, + ), + ], + ), + ), + ConfigPageItem( + label: Text('Logs'), + onPressed: () { + Utils.openPage(context, (context) { + return SiteLogsScreen(site: widget.site); + }); + }, + ), + ], + ); } Widget _buildHosts() { @@ -184,83 +202,92 @@ class _SiteDetailScreenState extends State { label: "TUNNELS", children: [ ConfigPageItem( - onPressed: () { - if (activeHosts == null) return; + onPressed: () { + if (activeHosts == null) return; - Utils.openPage( - context, - (context) => SiteTunnelsScreen( - pending: false, - tunnels: activeHosts!, - site: site, - onChanged: (hosts) { - setState(() { - activeHosts = hosts; - }); - }, - supportsQRScanning: widget.supportsQRScanning, - )); - }, - label: Text("Active"), - content: Container(alignment: Alignment.centerRight, child: active)), + Utils.openPage( + context, + (context) => SiteTunnelsScreen( + pending: false, + tunnels: activeHosts!, + site: site, + onChanged: (hosts) { + setState(() { + activeHosts = hosts; + }); + }, + supportsQRScanning: widget.supportsQRScanning, + ), + ); + }, + label: Text("Active"), + content: Container(alignment: Alignment.centerRight, child: active), + ), ConfigPageItem( - onPressed: () { - if (pendingHosts == null) return; + onPressed: () { + if (pendingHosts == null) return; - Utils.openPage( - context, - (context) => SiteTunnelsScreen( - pending: true, - tunnels: pendingHosts!, - site: site, - onChanged: (hosts) { - setState(() { - pendingHosts = hosts; - }); - }, - supportsQRScanning: widget.supportsQRScanning, - )); - }, - label: Text("Pending"), - content: Container(alignment: Alignment.centerRight, child: pending)) + Utils.openPage( + context, + (context) => SiteTunnelsScreen( + pending: true, + tunnels: pendingHosts!, + site: site, + onChanged: (hosts) { + setState(() { + pendingHosts = hosts; + }); + }, + supportsQRScanning: widget.supportsQRScanning, + ), + ); + }, + label: Text("Pending"), + content: Container(alignment: Alignment.centerRight, child: pending), + ), ], ); } Widget _buildSiteDetails() { - return ConfigSection(children: [ - ConfigPageItem( - crossAxisAlignment: CrossAxisAlignment.center, - content: Text('Configuration'), - onPressed: () { - Utils.openPage(context, (context) { - return SiteConfigScreen( - site: widget.site, - onSave: (site) async { - changed = true; - setState(() {}); - }, - supportsQRScanning: widget.supportsQRScanning, - ); - }); - }, - ), - ]); + return ConfigSection( + children: [ + ConfigPageItem( + crossAxisAlignment: CrossAxisAlignment.center, + content: Text('Configuration'), + onPressed: () { + Utils.openPage(context, (context) { + return SiteConfigScreen( + site: widget.site, + onSave: (site) async { + changed = true; + setState(() {}); + }, + supportsQRScanning: widget.supportsQRScanning, + ); + }); + }, + ), + ], + ); } Widget _buildDelete() { return Padding( - padding: EdgeInsets.only(top: 50, bottom: 10, left: 10, right: 10), - child: SizedBox( - width: double.infinity, - child: DangerButton( - child: Text('Delete'), - onPressed: () => Utils.confirmDelete(context, 'Delete Site?', () async { + padding: EdgeInsets.only(top: 50, bottom: 10, left: 10, right: 10), + child: SizedBox( + width: double.infinity, + child: DangerButton( + child: Text('Delete'), + onPressed: + () => Utils.confirmDelete(context, 'Delete Site?', () async { if (await _deleteSite()) { Navigator.of(context).pop(); } }), - ))); + ), + ), + ); } _listHostmap() async { diff --git a/lib/screens/SiteLogsScreen.dart b/lib/screens/SiteLogsScreen.dart index ed93465..a3269ed 100644 --- a/lib/screens/SiteLogsScreen.dart +++ b/lib/screens/SiteLogsScreen.dart @@ -60,20 +60,21 @@ class _SiteLogsScreenState extends State { }, refreshController: refreshController, child: Container( - padding: EdgeInsets.all(5), - constraints: logBoxConstraints(context), - child: ListenableBuilder( - listenable: logsNotifier, - builder: (context, child) => SelectableText( - switch (logsNotifier.logsResult) { - Ok(:var value) => value.trim(), - Error(:var error) => error is LogsNotFoundException + padding: EdgeInsets.all(5), + constraints: logBoxConstraints(context), + child: ListenableBuilder( + listenable: logsNotifier, + builder: + (context, child) => SelectableText(switch (logsNotifier.logsResult) { + Ok(:var value) => value.trim(), + Error(:var error) => + error is LogsNotFoundException ? error.error() : Utils.popError(context, "Error while reading logs.", error.toString()), - null => "", - }, - style: TextStyle(fontFamily: 'RobotoMono', fontSize: 14)), - )), + null => "", + }, style: TextStyle(fontFamily: 'RobotoMono', fontSize: 14)), + ), + ), bottomBar: _buildBottomBar(), ); } @@ -81,79 +82,80 @@ class _SiteLogsScreenState extends State { Widget _buildTextWrapToggle() { return Platform.isIOS ? Tooltip( - message: "Turn ${settings.logWrap ? "off" : "on"} text wrapping", - child: CupertinoButton.tinted( - // Use the default tint when enabled, match the background when not. - color: settings.logWrap ? null : CupertinoColors.systemBackground, - sizeStyle: CupertinoButtonSize.small, - borderRadius: const BorderRadius.all(Radius.circular(8)), - child: const Icon(Icons.wrap_text), - onPressed: () => { + message: "Turn ${settings.logWrap ? "off" : "on"} text wrapping", + child: CupertinoButton.tinted( + // Use the default tint when enabled, match the background when not. + color: settings.logWrap ? null : CupertinoColors.systemBackground, + sizeStyle: CupertinoButtonSize.small, + borderRadius: const BorderRadius.all(Radius.circular(8)), + child: const Icon(Icons.wrap_text), + onPressed: + () => { + setState(() { + settings.logWrap = !settings.logWrap; + }), + }, + ), + ) + : IconButton.filledTonal( + isSelected: settings.logWrap, + tooltip: "Turn ${settings.logWrap ? "off" : "on"} text wrapping", + // The variants of wrap_text seem to be the same, but this seems most correct. + selectedIcon: const Icon(Icons.wrap_text_outlined), + icon: const Icon(Icons.wrap_text), + onPressed: + () => { setState(() { settings.logWrap = !settings.logWrap; - }) + }), }, - ), - ) - : IconButton.filledTonal( - isSelected: settings.logWrap, - tooltip: "Turn ${settings.logWrap ? "off" : "on"} text wrapping", - // The variants of wrap_text seem to be the same, but this seems most correct. - selectedIcon: const Icon(Icons.wrap_text_outlined), - icon: const Icon(Icons.wrap_text), - onPressed: () => { - setState(() { - settings.logWrap = !settings.logWrap; - }) - }, - ); + ); } Widget _buildBottomBar() { - var borderSide = BorderSide( - color: CupertinoColors.separator, - style: BorderStyle.solid, - width: 0.0, - ); + var borderSide = BorderSide(color: CupertinoColors.separator, style: BorderStyle.solid, width: 0.0); var padding = Platform.isAndroid ? EdgeInsets.fromLTRB(0, 20, 0, 30) : EdgeInsets.all(10); return PlatformWidgetBuilder( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - spacing: 8, - children: [ - Tooltip( - message: "Share logs", - child: PlatformIconButton( - icon: Icon(context.platformIcons.share), - onPressed: () { - Share.shareFile(context, - title: '${widget.site.name} logs', - filePath: widget.site.logFile, - filename: '${widget.site.name}.log'); - }, - ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + spacing: 8, + children: [ + Tooltip( + message: "Share logs", + child: PlatformIconButton( + icon: Icon(context.platformIcons.share), + onPressed: () { + Share.shareFile( + context, + title: '${widget.site.name} logs', + filePath: widget.site.logFile, + filename: '${widget.site.name}.log', + ); + }, ), - Tooltip( - message: 'Go to latest', - child: PlatformIconButton( - icon: Icon(context.platformIcons.downArrow), - onPressed: () async { - controller.animateTo(controller.position.maxScrollExtent, - duration: const Duration(milliseconds: 500), curve: Curves.linearToEaseOut); - }, - ), + ), + Tooltip( + message: 'Go to latest', + child: PlatformIconButton( + icon: Icon(context.platformIcons.downArrow), + onPressed: () async { + controller.animateTo( + controller.position.maxScrollExtent, + duration: const Duration(milliseconds: 500), + curve: Curves.linearToEaseOut, + ); + }, ), - ], - ), - cupertino: (context, child, platform) => Container( - decoration: BoxDecoration( - border: Border(top: borderSide), - ), - padding: padding, - child: child), - material: (context, child, platform) => BottomAppBar(child: child)); + ), + ], + ), + cupertino: + (context, child, platform) => + Container(decoration: BoxDecoration(border: Border(top: borderSide)), padding: padding, child: child), + material: (context, child, platform) => BottomAppBar(child: child), + ); } logBoxConstraints(BuildContext context) { diff --git a/lib/screens/SiteTunnelsScreen.dart b/lib/screens/SiteTunnelsScreen.dart index 7b3e6c8..51ba6df 100644 --- a/lib/screens/SiteTunnelsScreen.dart +++ b/lib/screens/SiteTunnelsScreen.dart @@ -53,32 +53,36 @@ class _SiteTunnelsScreenState extends State { Widget build(BuildContext context) { final double ipWidth = Utils.textSize("000.000.000.000", CupertinoTheme.of(context).textTheme.textStyle).width + 32; - final List children = tunnels.map((hostInfo) { - final isLh = site.staticHostmap[hostInfo.vpnIp]?.lighthouse ?? false; - final icon = switch (isLh) { - true => Icon(Icons.lightbulb_outline, color: CupertinoColors.placeholderText.resolveFrom(context)), - false => Icon(Icons.computer, color: CupertinoColors.placeholderText.resolveFrom(context)) - }; + final List children = + tunnels.map((hostInfo) { + final isLh = site.staticHostmap[hostInfo.vpnIp]?.lighthouse ?? false; + final icon = switch (isLh) { + true => Icon(Icons.lightbulb_outline, color: CupertinoColors.placeholderText.resolveFrom(context)), + false => Icon(Icons.computer, color: CupertinoColors.placeholderText.resolveFrom(context)), + }; - return (ConfigPageItem( - onPressed: () => Utils.openPage( - context, - (context) => HostInfoScreen( - isLighthouse: isLh, - hostInfo: hostInfo, - pending: widget.pending, - site: widget.site, - onChanged: () { - _listHostmap(); - }, - supportsQRScanning: widget.supportsQRScanning, - ), - ), - label: Row(children: [Padding(child: icon, padding: EdgeInsets.only(right: 10)), Text(hostInfo.vpnIp)]), - labelWidth: ipWidth, - content: Container(alignment: Alignment.centerRight, child: Text(hostInfo.cert?.details.name ?? "")), - )); - }).toList(); + return (ConfigPageItem( + onPressed: + () => Utils.openPage( + context, + (context) => HostInfoScreen( + isLighthouse: isLh, + hostInfo: hostInfo, + pending: widget.pending, + site: widget.site, + onChanged: () { + _listHostmap(); + }, + supportsQRScanning: widget.supportsQRScanning, + ), + ), + label: Row( + children: [Padding(child: icon, padding: EdgeInsets.only(right: 10)), Text(hostInfo.vpnIp)], + ), + labelWidth: ipWidth, + content: Container(alignment: Alignment.centerRight, child: Text(hostInfo.cert?.details.name ?? "")), + )); + }).toList(); final Widget child = switch (children.length) { 0 => Center(child: Padding(child: Text('No tunnels to show'), padding: EdgeInsets.only(top: 30))), @@ -88,13 +92,14 @@ class _SiteTunnelsScreenState extends State { final title = widget.pending ? 'Pending' : 'Active'; return SimplePage( - title: Text('$title Tunnels'), - refreshController: refreshController, - onRefresh: () async { - await _listHostmap(); - refreshController.refreshCompleted(); - }, - child: child); + title: Text('$title Tunnels'), + refreshController: refreshController, + onRefresh: () async { + await _listHostmap(); + refreshController.refreshCompleted(); + }, + child: child, + ); } _sortTunnels() { diff --git a/lib/screens/siteConfig/AddCertificateScreen.dart b/lib/screens/siteConfig/AddCertificateScreen.dart index 0fc09e8..c803fc4 100644 --- a/lib/screens/siteConfig/AddCertificateScreen.dart +++ b/lib/screens/siteConfig/AddCertificateScreen.dart @@ -87,32 +87,34 @@ class _AddCertificateScreenState extends State { List _buildShare() { return [ ConfigSection( - label: 'Share your public key with a nebula CA so they can sign and return a certificate', - children: [ - ConfigItem( - labelWidth: 0, - content: SelectableText(pubKey, style: TextStyle(fontFamily: 'RobotoMono', fontSize: 14)), - ), - Builder( - builder: (BuildContext context) { - return ConfigButtonItem( - content: Text('Share Public Key'), - onPressed: () async { - await Share.share(context, - title: 'Please sign and return a certificate', text: pubKey, filename: 'device.pub'); - }, - ); - }, - ), - ]) + label: 'Share your public key with a nebula CA so they can sign and return a certificate', + children: [ + ConfigItem( + labelWidth: 0, + content: SelectableText(pubKey, style: TextStyle(fontFamily: 'RobotoMono', fontSize: 14)), + ), + Builder( + builder: (BuildContext context) { + return ConfigButtonItem( + content: Text('Share Public Key'), + onPressed: () async { + await Share.share( + context, + title: 'Please sign and return a certificate', + text: pubKey, + filename: 'device.pub', + ); + }, + ); + }, + ), + ], + ), ]; } List _buildLoadCert() { - Map children = { - 'paste': Text('Copy/Paste'), - 'file': Text('File'), - }; + Map children = {'paste': Text('Copy/Paste'), 'file': Text('File')}; // not all devices have a camera for QR codes if (widget.supportsQRScanning) { @@ -121,18 +123,19 @@ class _AddCertificateScreenState extends State { List items = [ Padding( - padding: EdgeInsets.fromLTRB(10, 25, 10, 0), - child: CupertinoSlidingSegmentedControl( - groupValue: inputType, - onValueChanged: (v) { - if (v != null) { - setState(() { - inputType = v; - }); - } - }, - children: children, - )) + padding: EdgeInsets.fromLTRB(10, 25, 10, 0), + child: CupertinoSlidingSegmentedControl( + groupValue: inputType, + onValueChanged: (v) { + if (v != null) { + setState(() { + inputType = v; + }); + } + }, + children: children, + ), + ), ]; if (inputType == 'paste') { @@ -149,23 +152,25 @@ class _AddCertificateScreenState extends State { Widget _buildKey() { if (!showKey) { return Padding( - padding: EdgeInsets.only(top: 20, bottom: 10, left: 10, right: 10), - child: SizedBox( - width: double.infinity, - child: PrimaryButton( - child: Text('Show/Import Private Key'), - onPressed: () => Utils.confirmDelete(context, 'Show/Import Private Key?', () { - setState(() { - showKey = true; - }); - }, deleteLabel: 'Yes')))); + padding: EdgeInsets.only(top: 20, bottom: 10, left: 10, right: 10), + child: SizedBox( + width: double.infinity, + child: PrimaryButton( + child: Text('Show/Import Private Key'), + onPressed: + () => Utils.confirmDelete(context, 'Show/Import Private Key?', () { + setState(() { + showKey = true; + }); + }, deleteLabel: 'Yes'), + ), + ), + ); } return ConfigSection( label: 'Import a private key generated on another device', - children: [ - ConfigTextItem(controller: keyController, style: TextStyle(fontFamily: 'RobotoMono', fontSize: 14)), - ], + children: [ConfigTextItem(controller: keyController, style: TextStyle(fontFamily: 'RobotoMono', fontSize: 14))], ); } @@ -173,17 +178,15 @@ class _AddCertificateScreenState extends State { return [ ConfigSection( children: [ - ConfigTextItem( - placeholder: 'Certificate PEM Contents', - controller: pasteController, - ), + ConfigTextItem(placeholder: 'Certificate PEM Contents', controller: pasteController), ConfigButtonItem( - content: Center(child: Text('Load Certificate')), - onPressed: () { - _addCertEntry(pasteController.text); - }), + content: Center(child: Text('Load Certificate')), + onPressed: () { + _addCertEntry(pasteController.text); + }, + ), ], - ) + ), ]; } @@ -192,21 +195,22 @@ class _AddCertificateScreenState extends State { ConfigSection( children: [ ConfigButtonItem( - content: Center(child: Text('Choose a file')), - onPressed: () async { - try { - final content = await Utils.pickFile(context); - if (content == null) { - return; - } - - _addCertEntry(content); - } catch (err) { - return Utils.popError(context, 'Failed to load certificate file', err.toString()); + content: Center(child: Text('Choose a file')), + onPressed: () async { + try { + final content = await Utils.pickFile(context); + if (content == null) { + return; } - }) + + _addCertEntry(content); + } catch (err) { + return Utils.popError(context, 'Failed to load certificate file', err.toString()); + } + }, + ), ], - ) + ), ]; } @@ -219,10 +223,7 @@ class _AddCertificateScreenState extends State { onPressed: () async { var result = await Navigator.push( context, - platformPageRoute( - context: context, - builder: (context) => new ScanQRScreen(), - ), + platformPageRoute(context: context, builder: (context) => new ScanQRScreen()), ); if (result != null) { _addCertEntry(result); @@ -230,7 +231,7 @@ class _AddCertificateScreenState extends State { }, ), ], - ) + ), ]; } @@ -247,18 +248,26 @@ class _AddCertificateScreenState extends State { if (certs.length > 0) { var tryCertInfo = CertificateInfo.fromJson(certs.first); if (tryCertInfo.cert.details.isCa) { - return Utils.popError(context, 'Error loading certificate content', - 'A certificate authority is not appropriate for a client certificate.'); + return Utils.popError( + context, + 'Error loading certificate content', + 'A certificate authority is not appropriate for a client certificate.', + ); } else if (!tryCertInfo.validity!.valid) { return Utils.popError(context, 'Certificate was invalid', tryCertInfo.validity!.reason); } - var certMatch = await platform - .invokeMethod("nebula.verifyCertAndKey", {"cert": rawCert, "key": keyController.text}); + var certMatch = await platform.invokeMethod("nebula.verifyCertAndKey", { + "cert": rawCert, + "key": keyController.text, + }); if (!certMatch) { // The method above will throw if there is a mismatch, this is just here in case we introduce a bug in the future - return Utils.popError(context, 'Error loading certificate content', - 'The provided certificates public key is not compatible with the private key.'); + return Utils.popError( + context, + 'Error loading certificate content', + 'The provided certificates public key is not compatible with the private key.', + ); } if (widget.onReplace != null) { diff --git a/lib/screens/siteConfig/AdvancedScreen.dart b/lib/screens/siteConfig/AdvancedScreen.dart index 247b981..fa886c6 100644 --- a/lib/screens/siteConfig/AdvancedScreen.dart +++ b/lib/screens/siteConfig/AdvancedScreen.dart @@ -40,11 +40,7 @@ class Advanced { } class AdvancedScreen extends StatefulWidget { - const AdvancedScreen({ - Key? key, - required this.site, - required this.onSave, - }) : super(key: key); + const AdvancedScreen({Key? key, required this.site, required this.onSave}) : super(key: key); final Site site; final ValueChanged onSave; @@ -73,22 +69,24 @@ class _AdvancedScreenState extends State { @override Widget build(BuildContext context) { return FormPage( - title: 'Advanced Settings', - changed: changed, - onSave: () { - Navigator.pop(context); - widget.onSave(settings); - }, - child: Column(children: [ + title: 'Advanced Settings', + changed: changed, + onSave: () { + Navigator.pop(context); + widget.onSave(settings); + }, + child: Column( + children: [ ConfigSection( children: [ ConfigItem( - label: Text("Lighthouse interval"), - labelWidth: 200, - //TODO: Auto select on focus? - content: widget.site.managed - ? Text(settings.lhDuration.toString() + " seconds", textAlign: TextAlign.right) - : PlatformTextFormField( + label: Text("Lighthouse interval"), + labelWidth: 200, + //TODO: Auto select on focus? + content: + widget.site.managed + ? Text(settings.lhDuration.toString() + " seconds", textAlign: TextAlign.right) + : PlatformTextFormField( initialValue: settings.lhDuration.toString(), keyboardType: TextInputType.number, suffix: Text("seconds"), @@ -102,14 +100,16 @@ class _AdvancedScreenState extends State { } }); }, - )), + ), + ), ConfigItem( - label: Text("Listen port"), - labelWidth: 150, - //TODO: Auto select on focus? - content: widget.site.managed - ? Text(settings.port.toString(), textAlign: TextAlign.right) - : PlatformTextFormField( + label: Text("Listen port"), + labelWidth: 150, + //TODO: Auto select on focus? + content: + widget.site.managed + ? Text(settings.port.toString(), textAlign: TextAlign.right) + : PlatformTextFormField( initialValue: settings.port.toString(), keyboardType: TextInputType.number, textAlign: TextAlign.right, @@ -122,13 +122,15 @@ class _AdvancedScreenState extends State { } }); }, - )), + ), + ), ConfigItem( - label: Text("MTU"), - labelWidth: 150, - content: widget.site.managed - ? Text(settings.mtu.toString(), textAlign: TextAlign.right) - : PlatformTextFormField( + label: Text("MTU"), + labelWidth: 150, + content: + widget.site.managed + ? Text(settings.mtu.toString(), textAlign: TextAlign.right) + : PlatformTextFormField( initialValue: settings.mtu.toString(), keyboardType: TextInputType.number, textAlign: TextAlign.right, @@ -141,41 +143,46 @@ class _AdvancedScreenState extends State { } }); }, - )), + ), + ), ConfigPageItem( - disabled: widget.site.managed, - label: Text('Cipher'), - labelWidth: 150, - content: Text(settings.cipher, textAlign: TextAlign.end), - onPressed: () { - Utils.openPage(context, (context) { - return CipherScreen( - cipher: settings.cipher, - onSave: (cipher) { - setState(() { - settings.cipher = cipher; - changed = true; - }); - }); - }); - }), + disabled: widget.site.managed, + label: Text('Cipher'), + labelWidth: 150, + content: Text(settings.cipher, textAlign: TextAlign.end), + onPressed: () { + Utils.openPage(context, (context) { + return CipherScreen( + cipher: settings.cipher, + onSave: (cipher) { + setState(() { + settings.cipher = cipher; + changed = true; + }); + }, + ); + }); + }, + ), ConfigPageItem( - disabled: widget.site.managed, - label: Text('Log verbosity'), - labelWidth: 150, - content: Text(settings.verbosity, textAlign: TextAlign.end), - onPressed: () { - Utils.openPage(context, (context) { - return LogVerbosityScreen( - verbosity: settings.verbosity, - onSave: (verbosity) { - setState(() { - settings.verbosity = verbosity; - changed = true; - }); - }); - }); - }), + disabled: widget.site.managed, + label: Text('Log verbosity'), + labelWidth: 150, + content: Text(settings.verbosity, textAlign: TextAlign.end), + onPressed: () { + Utils.openPage(context, (context) { + return LogVerbosityScreen( + verbosity: settings.verbosity, + onSave: (verbosity) { + setState(() { + settings.verbosity = verbosity; + changed = true; + }); + }, + ); + }); + }, + ), ConfigPageItem( label: Text('Unsafe routes'), labelWidth: 150, @@ -183,18 +190,20 @@ class _AdvancedScreenState extends State { onPressed: () { Utils.openPage(context, (context) { return UnsafeRoutesScreen( - unsafeRoutes: settings.unsafeRoutes, - onSave: widget.site.managed - ? null - : (routes) { + unsafeRoutes: settings.unsafeRoutes, + onSave: + widget.site.managed + ? null + : (routes) { setState(() { settings.unsafeRoutes = routes; changed = true; }); - }); + }, + ); }); }, - ) + ), ], ), ConfigSection( @@ -211,9 +220,11 @@ class _AdvancedScreenState extends State { Utils.popError(context, 'Failed to render the site config', err.toString()); } }, - ) + ), ], - ) - ])); + ), + ], + ), + ); } } diff --git a/lib/screens/siteConfig/CAListScreen.dart b/lib/screens/siteConfig/CAListScreen.dart index 5cd0bf6..689af17 100644 --- a/lib/screens/siteConfig/CAListScreen.dart +++ b/lib/screens/siteConfig/CAListScreen.dart @@ -18,12 +18,7 @@ import 'package:mobile_nebula/services/utils.dart'; //TODO: In addition you will want to think about re-generation while the site is still active (This means storing multiple keys in secure storage) class CAListScreen extends StatefulWidget { - const CAListScreen({ - Key? key, - required this.cas, - this.onSave, - required this.supportsQRScanning, - }) : super(key: key); + const CAListScreen({Key? key, required this.cas, this.onSave, required this.supportsQRScanning}) : super(key: key); final List cas; final ValueChanged>? onSave; @@ -65,41 +60,47 @@ class _CAListScreenState extends State { } return FormPage( - title: 'Certificate Authorities', - changed: changed, - onSave: () { - if (widget.onSave != null) { - Navigator.pop(context); - widget.onSave!(cas.values.map((ca) { + title: 'Certificate Authorities', + changed: changed, + onSave: () { + if (widget.onSave != null) { + Navigator.pop(context); + widget.onSave!( + cas.values.map((ca) { return ca; - }).toList()); - } - }, - child: Column(children: items)); + }).toList(), + ); + } + }, + child: Column(children: items), + ); } List _buildCAs() { List items = []; cas.forEach((key, ca) { - items.add(ConfigPageItem( - content: Text(ca.cert.details.name), - onPressed: () { - Utils.openPage(context, (context) { - return CertificateDetailsScreen( - certInfo: ca, - onDelete: widget.onSave == null - ? null - : () { - setState(() { - changed = true; - cas.remove(key); - }); - }, - supportsQRScanning: widget.supportsQRScanning, - ); - }); - }, - )); + items.add( + ConfigPageItem( + content: Text(ca.cert.details.name), + onPressed: () { + Utils.openPage(context, (context) { + return CertificateDetailsScreen( + certInfo: ca, + onDelete: + widget.onSave == null + ? null + : () { + setState(() { + changed = true; + cas.remove(key); + }); + }, + supportsQRScanning: widget.supportsQRScanning, + ); + }); + }, + ), + ); }); return items; @@ -137,10 +138,7 @@ class _CAListScreenState extends State { } List _addCA() { - Map children = { - 'paste': Text('Copy/Paste'), - 'file': Text('File'), - }; + Map children = {'paste': Text('Copy/Paste'), 'file': Text('File')}; // not all devices have a camera for QR codes if (widget.supportsQRScanning) { @@ -149,18 +147,19 @@ class _CAListScreenState extends State { List items = [ Padding( - padding: EdgeInsets.fromLTRB(10, 25, 10, 0), - child: CupertinoSlidingSegmentedControl( - groupValue: inputType, - onValueChanged: (v) { - if (v != null) { - setState(() { - inputType = v; - }); - } - }, - children: children, - )) + padding: EdgeInsets.fromLTRB(10, 25, 10, 0), + child: CupertinoSlidingSegmentedControl( + groupValue: inputType, + onValueChanged: (v) { + if (v != null) { + setState(() { + inputType = v; + }); + } + }, + children: children, + ), + ), ]; if (inputType == 'paste') { @@ -178,25 +177,23 @@ class _CAListScreenState extends State { return [ ConfigSection( children: [ - ConfigTextItem( - placeholder: 'CA PEM contents', - controller: pasteController, - ), + ConfigTextItem(placeholder: 'CA PEM contents', controller: pasteController), ConfigButtonItem( - content: Text('Load CA'), - onPressed: () { - _addCAEntry(pasteController.text, (err) { - print(err); - if (err != null) { - return Utils.popError(context, 'Failed to parse CA content', err); - } + content: Text('Load CA'), + onPressed: () { + _addCAEntry(pasteController.text, (err) { + print(err); + if (err != null) { + return Utils.popError(context, 'Failed to parse CA content', err); + } - pasteController.text = ''; - setState(() {}); - }); - }), + pasteController.text = ''; + setState(() {}); + }); + }, + ), ], - ) + ), ]; } @@ -205,27 +202,28 @@ class _CAListScreenState extends State { ConfigSection( children: [ ConfigButtonItem( - content: Text('Choose a file'), - onPressed: () async { - try { - final content = await Utils.pickFile(context); - if (content == null) { - return; - } - - _addCAEntry(content, (err) { - if (err != null) { - Utils.popError(context, 'Error loading CA file', err); - } else { - setState(() {}); - } - }); - } catch (err) { - return Utils.popError(context, 'Failed to load CA file', err.toString()); + content: Text('Choose a file'), + onPressed: () async { + try { + final content = await Utils.pickFile(context); + if (content == null) { + return; } - }) + + _addCAEntry(content, (err) { + if (err != null) { + Utils.popError(context, 'Error loading CA file', err); + } else { + setState(() {}); + } + }); + } catch (err) { + return Utils.popError(context, 'Failed to load CA file', err.toString()); + } + }, + ), ], - ) + ), ]; } @@ -238,10 +236,7 @@ class _CAListScreenState extends State { onPressed: () async { var result = await Navigator.push( context, - platformPageRoute( - context: context, - builder: (context) => new ScanQRScreen(), - ), + platformPageRoute(context: context, builder: (context) => new ScanQRScreen()), ); if (result != null) { _addCAEntry(result, (err) { @@ -253,9 +248,9 @@ class _CAListScreenState extends State { }); } }, - ) + ), ], - ) + ), ]; } } diff --git a/lib/screens/siteConfig/CertificateDetailsScreen.dart b/lib/screens/siteConfig/CertificateDetailsScreen.dart index da5fd97..e601fe2 100644 --- a/lib/screens/siteConfig/CertificateDetailsScreen.dart +++ b/lib/screens/siteConfig/CertificateDetailsScreen.dart @@ -76,39 +76,44 @@ class _CertificateDetailsScreenState extends State { } }, hideSave: widget.onSave == null && widget.onReplace == null, - child: Column(children: [ - _buildID(), - _buildFilters(), - _buildValid(), - _buildAdvanced(), - _buildReplace(), - _buildDelete(), - ]), + child: Column( + children: [_buildID(), _buildFilters(), _buildValid(), _buildAdvanced(), _buildReplace(), _buildDelete()], + ), ); } Widget _buildID() { - return ConfigSection(children: [ - ConfigItem(label: Text('Name'), content: SelectableText(certInfo.cert.details.name)), - ConfigItem( - label: Text('Type'), content: Text(certInfo.cert.details.isCa ? 'CA certificate' : 'Client certificate')), - ]); + return ConfigSection( + children: [ + ConfigItem(label: Text('Name'), content: SelectableText(certInfo.cert.details.name)), + ConfigItem( + label: Text('Type'), + content: Text(certInfo.cert.details.isCa ? 'CA certificate' : 'Client certificate'), + ), + ], + ); } Widget _buildValid() { var valid = Text('yes'); if (certInfo.validity != null && !certInfo.validity!.valid) { - valid = Text(certInfo.validity!.valid ? 'yes' : certInfo.validity!.reason, - style: TextStyle(color: CupertinoColors.systemRed.resolveFrom(context))); + valid = Text( + certInfo.validity!.valid ? 'yes' : certInfo.validity!.reason, + style: TextStyle(color: CupertinoColors.systemRed.resolveFrom(context)), + ); } return ConfigSection( label: 'VALIDITY', children: [ ConfigItem(label: Text('Valid?'), content: valid), ConfigItem( - label: Text('Created'), content: SelectableText(certInfo.cert.details.notBefore.toLocal().toString())), + label: Text('Created'), + content: SelectableText(certInfo.cert.details.notBefore.toLocal().toString()), + ), ConfigItem( - label: Text('Expires'), content: SelectableText(certInfo.cert.details.notAfter.toLocal().toString())), + label: Text('Expires'), + content: SelectableText(certInfo.cert.details.notAfter.toLocal().toString()), + ), ], ); } @@ -136,20 +141,24 @@ class _CertificateDetailsScreenState extends State { return ConfigSection( children: [ ConfigItem( - label: Text('Fingerprint'), - content: - SelectableText(certInfo.cert.fingerprint, style: TextStyle(fontFamily: 'RobotoMono', fontSize: 14)), - crossAxisAlignment: CrossAxisAlignment.start), + label: Text('Fingerprint'), + content: SelectableText(certInfo.cert.fingerprint, style: TextStyle(fontFamily: 'RobotoMono', fontSize: 14)), + crossAxisAlignment: CrossAxisAlignment.start, + ), ConfigItem( - label: Text('Public Key'), - content: SelectableText(certInfo.cert.details.publicKey, - style: TextStyle(fontFamily: 'RobotoMono', fontSize: 14)), - crossAxisAlignment: CrossAxisAlignment.start), + label: Text('Public Key'), + content: SelectableText( + certInfo.cert.details.publicKey, + style: TextStyle(fontFamily: 'RobotoMono', fontSize: 14), + ), + crossAxisAlignment: CrossAxisAlignment.start, + ), certInfo.rawCert != null ? ConfigItem( - label: Text('PEM Format'), - content: SelectableText(certInfo.rawCert!, style: TextStyle(fontFamily: 'RobotoMono', fontSize: 14)), - crossAxisAlignment: CrossAxisAlignment.start) + label: Text('PEM Format'), + content: SelectableText(certInfo.rawCert!, style: TextStyle(fontFamily: 'RobotoMono', fontSize: 14)), + crossAxisAlignment: CrossAxisAlignment.start, + ) : Container(), ], ); @@ -161,30 +170,32 @@ class _CertificateDetailsScreenState extends State { } return Padding( - padding: EdgeInsets.only(top: 50, bottom: 10, left: 10, right: 10), - child: SizedBox( - width: double.infinity, - child: DangerButton( - child: Text('Replace certificate'), - onPressed: () { - Utils.openPage(context, (context) { - return AddCertificateScreen( - onReplace: (result) { - setState(() { - changed = true; - certResult = result; - certInfo = result.certInfo; - }); - // Slam the page back to the top - controller.animateTo(0, - duration: const Duration(milliseconds: 10), curve: Curves.linearToEaseOut); - }, - pubKey: widget.pubKey!, - privKey: widget.privKey!, - supportsQRScanning: widget.supportsQRScanning, - ); + padding: EdgeInsets.only(top: 50, bottom: 10, left: 10, right: 10), + child: SizedBox( + width: double.infinity, + child: DangerButton( + child: Text('Replace certificate'), + onPressed: () { + Utils.openPage(context, (context) { + return AddCertificateScreen( + onReplace: (result) { + setState(() { + changed = true; + certResult = result; + certInfo = result.certInfo; }); - }))); + // Slam the page back to the top + controller.animateTo(0, duration: const Duration(milliseconds: 10), curve: Curves.linearToEaseOut); + }, + pubKey: widget.pubKey!, + privKey: widget.privKey!, + supportsQRScanning: widget.supportsQRScanning, + ); + }); + }, + ), + ), + ); } Widget _buildDelete() { @@ -195,14 +206,18 @@ class _CertificateDetailsScreenState extends State { var title = certInfo.cert.details.isCa ? 'Delete CA?' : 'Delete cert?'; return Padding( - padding: EdgeInsets.only(top: 50, bottom: 10, left: 10, right: 10), - child: SizedBox( - width: double.infinity, - child: DangerButton( - child: Text('Delete'), - onPressed: () => Utils.confirmDelete(context, title, () async { - Navigator.pop(context); - widget.onDelete!(); - })))); + padding: EdgeInsets.only(top: 50, bottom: 10, left: 10, right: 10), + child: SizedBox( + width: double.infinity, + child: DangerButton( + child: Text('Delete'), + onPressed: + () => Utils.confirmDelete(context, title, () async { + Navigator.pop(context); + widget.onDelete!(); + }), + ), + ), + ); } } diff --git a/lib/screens/siteConfig/CipherScreen.dart b/lib/screens/siteConfig/CipherScreen.dart index db8bfcd..fdcc399 100644 --- a/lib/screens/siteConfig/CipherScreen.dart +++ b/lib/screens/siteConfig/CipherScreen.dart @@ -6,11 +6,7 @@ import 'package:mobile_nebula/components/config/ConfigCheckboxItem.dart'; import 'package:mobile_nebula/components/config/ConfigSection.dart'; class CipherScreen extends StatefulWidget { - const CipherScreen({ - Key? key, - required this.cipher, - required this.onSave, - }) : super(key: key); + const CipherScreen({Key? key, required this.cipher, required this.onSave}) : super(key: key); final String cipher; final ValueChanged onSave; @@ -32,15 +28,16 @@ class _CipherScreenState extends State { @override Widget build(BuildContext context) { return FormPage( - title: 'Cipher Selection', - changed: changed, - onSave: () { - Navigator.pop(context); - widget.onSave(cipher); - }, - child: Column( - children: [ - ConfigSection(children: [ + title: 'Cipher Selection', + changed: changed, + onSave: () { + Navigator.pop(context); + widget.onSave(cipher); + }, + child: Column( + children: [ + ConfigSection( + children: [ ConfigCheckboxItem( label: Text("aes"), labelWidth: 150, @@ -62,9 +59,11 @@ class _CipherScreenState extends State { cipher = "chachapoly"; }); }, - ) - ]) - ], - )); + ), + ], + ), + ], + ), + ); } } diff --git a/lib/screens/siteConfig/LogVerbosityScreen.dart b/lib/screens/siteConfig/LogVerbosityScreen.dart index a855183..bf1ca07 100644 --- a/lib/screens/siteConfig/LogVerbosityScreen.dart +++ b/lib/screens/siteConfig/LogVerbosityScreen.dart @@ -6,11 +6,7 @@ import 'package:mobile_nebula/components/config/ConfigCheckboxItem.dart'; import 'package:mobile_nebula/components/config/ConfigSection.dart'; class LogVerbosityScreen extends StatefulWidget { - const LogVerbosityScreen({ - Key? key, - required this.verbosity, - required this.onSave, - }) : super(key: key); + const LogVerbosityScreen({Key? key, required this.verbosity, required this.onSave}) : super(key: key); final String verbosity; final ValueChanged onSave; @@ -32,24 +28,27 @@ class _LogVerbosityScreenState extends State { @override Widget build(BuildContext context) { return FormPage( - title: 'Log Verbosity', - changed: changed, - onSave: () { - Navigator.pop(context); - widget.onSave(verbosity); - }, - child: Column( - children: [ - ConfigSection(children: [ + title: 'Log Verbosity', + changed: changed, + onSave: () { + Navigator.pop(context); + widget.onSave(verbosity); + }, + child: Column( + children: [ + ConfigSection( + children: [ _buildEntry('debug'), _buildEntry('info'), _buildEntry('warning'), _buildEntry('error'), _buildEntry('fatal'), _buildEntry('panic'), - ]) - ], - )); + ], + ), + ], + ), + ); } Widget _buildEntry(String title) { diff --git a/lib/screens/siteConfig/RenderedConfigScreen.dart b/lib/screens/siteConfig/RenderedConfigScreen.dart index f0246c3..fb6de2d 100644 --- a/lib/screens/siteConfig/RenderedConfigScreen.dart +++ b/lib/screens/siteConfig/RenderedConfigScreen.dart @@ -7,11 +7,7 @@ class RenderedConfigScreen extends StatelessWidget { final String config; final String name; - RenderedConfigScreen({ - Key? key, - required this.config, - required this.name, - }) : super(key: key); + RenderedConfigScreen({Key? key, required this.config, required this.name}) : super(key: key); @override Widget build(BuildContext context) { @@ -19,18 +15,21 @@ class RenderedConfigScreen extends StatelessWidget { title: Text('Rendered Site Config'), scrollable: SimpleScrollable.both, trailingActions: [ - Builder(builder: (BuildContext context) { - return PlatformIconButton( - padding: EdgeInsets.zero, - icon: Icon(context.platformIcons.share, size: 28.0), - onPressed: () => Share.share(context, title: '$name.yaml', text: config, filename: '$name.yaml'), - ); - }), + Builder( + builder: (BuildContext context) { + return PlatformIconButton( + padding: EdgeInsets.zero, + icon: Icon(context.platformIcons.share, size: 28.0), + onPressed: () => Share.share(context, title: '$name.yaml', text: config, filename: '$name.yaml'), + ); + }, + ), ], child: Container( - padding: EdgeInsets.all(5), - constraints: BoxConstraints(minWidth: MediaQuery.of(context).size.width), - child: SelectableText(config, style: TextStyle(fontFamily: 'RobotoMono', fontSize: 14))), + padding: EdgeInsets.all(5), + constraints: BoxConstraints(minWidth: MediaQuery.of(context).size.width), + child: SelectableText(config, style: TextStyle(fontFamily: 'RobotoMono', fontSize: 14)), + ), ); } } diff --git a/lib/screens/siteConfig/ScanQRScreen.dart b/lib/screens/siteConfig/ScanQRScreen.dart index 77342e1..0a04d97 100644 --- a/lib/screens/siteConfig/ScanQRScreen.dart +++ b/lib/screens/siteConfig/ScanQRScreen.dart @@ -14,29 +14,28 @@ class _ScanQRScreenState extends State { @override Widget build(BuildContext context) { - final scanWindow = Rect.fromCenter( - center: MediaQuery.sizeOf(context).center(Offset.zero), - width: 250, - height: 250, - ); + final scanWindow = Rect.fromCenter(center: MediaQuery.sizeOf(context).center(Offset.zero), width: 250, height: 250); return Scaffold( - appBar: AppBar(title: const Text('Scan QR')), - backgroundColor: Colors.black, - body: Stack(fit: StackFit.expand, children: [ + appBar: AppBar(title: const Text('Scan QR')), + backgroundColor: Colors.black, + body: Stack( + fit: StackFit.expand, + children: [ Center( child: MobileScanner( - fit: BoxFit.contain, - controller: cameraController, - scanWindow: scanWindow, - onDetect: (BarcodeCapture barcodes) { - var barcode = barcodes.barcodes.firstOrNull; - if (barcode != null && mounted) { - cameraController.stop().then((_) { - Navigator.pop(context, barcode.rawValue); - }); - } - }), + fit: BoxFit.contain, + controller: cameraController, + scanWindow: scanWindow, + onDetect: (BarcodeCapture barcodes) { + var barcode = barcodes.barcodes.firstOrNull; + if (barcode != null && mounted) { + cameraController.stop().then((_) { + Navigator.pop(context, barcode.rawValue); + }); + } + }, + ), ), ValueListenableBuilder( valueListenable: cameraController, @@ -45,9 +44,7 @@ class _ScanQRScreenState extends State { return const SizedBox(); } - return CustomPaint( - painter: ScannerOverlay(scanWindow: scanWindow), - ); + return CustomPaint(painter: ScannerOverlay(scanWindow: scanWindow)); }, ), Align( @@ -63,15 +60,14 @@ class _ScanQRScreenState extends State { ), ), ), - ])); + ], + ), + ); } } class ScannerOverlay extends CustomPainter { - const ScannerOverlay({ - required this.scanWindow, - this.borderRadius = 12.0, - }); + const ScannerOverlay({required this.scanWindow, this.borderRadius = 12.0}); final Rect scanWindow; final double borderRadius; @@ -81,32 +77,30 @@ class ScannerOverlay extends CustomPainter { // we need to pass the size to the custom paint widget final backgroundPath = Path()..addRect(Rect.fromLTWH(0, 0, size.width, size.height)); - final cutoutPath = Path() - ..addRRect( - RRect.fromRectAndCorners( - scanWindow, - topLeft: Radius.circular(borderRadius), - topRight: Radius.circular(borderRadius), - bottomLeft: Radius.circular(borderRadius), - bottomRight: Radius.circular(borderRadius), - ), - ); + final cutoutPath = + Path()..addRRect( + RRect.fromRectAndCorners( + scanWindow, + topLeft: Radius.circular(borderRadius), + topRight: Radius.circular(borderRadius), + bottomLeft: Radius.circular(borderRadius), + bottomRight: Radius.circular(borderRadius), + ), + ); - final backgroundPaint = Paint() - ..color = Colors.black.withValues(alpha: 0.5) - ..style = PaintingStyle.fill - ..blendMode = BlendMode.srcOver; + final backgroundPaint = + Paint() + ..color = Colors.black.withValues(alpha: 0.5) + ..style = PaintingStyle.fill + ..blendMode = BlendMode.srcOver; - final backgroundWithCutout = Path.combine( - PathOperation.difference, - backgroundPath, - cutoutPath, - ); + final backgroundWithCutout = Path.combine(PathOperation.difference, backgroundPath, cutoutPath); - final borderPaint = Paint() - ..color = Colors.white - ..style = PaintingStyle.stroke - ..strokeWidth = 4.0; + final borderPaint = + Paint() + ..color = Colors.white + ..style = PaintingStyle.stroke + ..strokeWidth = 4.0; final borderRect = RRect.fromRectAndCorners( scanWindow, @@ -214,14 +208,7 @@ class ToggleFlashlightButton extends StatelessWidget { }, ); case TorchState.unavailable: - return const SizedBox.square( - dimension: 48.0, - child: Icon( - Icons.no_flash, - size: 32.0, - color: Colors.grey, - ), - ); + return const SizedBox.square(dimension: 48.0, child: Icon(Icons.no_flash, size: 32.0, color: Colors.grey)); } }, ); diff --git a/lib/screens/siteConfig/SiteConfigScreen.dart b/lib/screens/siteConfig/SiteConfigScreen.dart index 8e8a346..1e274eb 100644 --- a/lib/screens/siteConfig/SiteConfigScreen.dart +++ b/lib/screens/siteConfig/SiteConfigScreen.dart @@ -23,12 +23,8 @@ import 'package:mobile_nebula/services/utils.dart'; //TODO: Enforce a name class SiteConfigScreen extends StatefulWidget { - const SiteConfigScreen({ - Key? key, - this.site, - required this.onSave, - required this.supportsQRScanning, - }) : super(key: key); + const SiteConfigScreen({Key? key, this.site, required this.onSave, required this.supportsQRScanning}) + : super(key: key); final Site? site; @@ -71,36 +67,39 @@ class _SiteConfigScreenState extends State { Widget build(BuildContext context) { if (pubKey == null || privKey == null) { return Center( - child: fpw.PlatformCircularProgressIndicator(cupertino: (_, __) { - return fpw.CupertinoProgressIndicatorData(radius: 50); - }), + child: fpw.PlatformCircularProgressIndicator( + cupertino: (_, __) { + return fpw.CupertinoProgressIndicatorData(radius: 50); + }, + ), ); } return FormPage( - title: newSite ? 'New Site' : 'Edit Site', - changed: changed, - onSave: () async { - site.name = nameController.text; - try { - await site.save(); - } catch (error) { - return Utils.popError(context, 'Failed to save the site configuration', error.toString()); - } + title: newSite ? 'New Site' : 'Edit Site', + changed: changed, + onSave: () async { + site.name = nameController.text; + try { + await site.save(); + } catch (error) { + return Utils.popError(context, 'Failed to save the site configuration', error.toString()); + } - Navigator.pop(context); - widget.onSave(site); - }, - child: Column( - children: [ - _main(), - _keys(), - _hosts(), - _advanced(), - _managed(), - kDebugMode ? _debugConfig() : Container(height: 0), - ], - )); + Navigator.pop(context); + widget.onSave(site); + }, + child: Column( + children: [ + _main(), + _keys(), + _hosts(), + _advanced(), + _managed(), + kDebugMode ? _debugConfig() : Container(height: 0), + ], + ), + ); } Widget _debugConfig() { @@ -116,8 +115,9 @@ class _SiteConfigScreenState extends State { } Widget _main() { - return ConfigSection(children: [ - ConfigItem( + return ConfigSection( + children: [ + ConfigItem( label: Text("Name"), content: PlatformTextFormField( placeholder: 'Required', @@ -128,8 +128,10 @@ class _SiteConfigScreenState extends State { } return null; }, - )) - ]); + ), + ), + ], + ); } Widget _managed() { @@ -140,15 +142,19 @@ class _SiteConfigScreenState extends State { } return site.managed - ? ConfigSection(label: "MANAGED CONFIG", children: [ + ? ConfigSection( + label: "MANAGED CONFIG", + children: [ ConfigItem( label: Text("Last Update"), - content: - Wrap(alignment: WrapAlignment.end, crossAxisAlignment: WrapCrossAlignment.center, children: [ - Text(lastUpdate), - ]), - ) - ]) + content: Wrap( + alignment: WrapAlignment.end, + crossAxisAlignment: WrapCrossAlignment.center, + children: [Text(lastUpdate)], + ), + ), + ], + ) : Container(); } @@ -171,14 +177,19 @@ class _SiteConfigScreenState extends State { children: [ ConfigPageItem( label: Text('Certificate'), - content: Wrap(alignment: WrapAlignment.end, crossAxisAlignment: WrapCrossAlignment.center, children: [ - certError - ? Padding( + content: Wrap( + alignment: WrapAlignment.end, + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + certError + ? Padding( child: Icon(Icons.error, color: CupertinoColors.systemRed.resolveFrom(context), size: 20), - padding: EdgeInsets.only(right: 5)) - : Container(), - certError ? Text('Needs attention') : Text(site.certInfo?.cert.details.name ?? 'Unknown certificate') - ]), + padding: EdgeInsets.only(right: 5), + ) + : Container(), + certError ? Text('Needs attention') : Text(site.certInfo?.cert.details.name ?? 'Unknown certificate'), + ], + ), onPressed: () { Utils.openPage(context, (context) { if (site.certInfo != null) { @@ -186,15 +197,16 @@ class _SiteConfigScreenState extends State { certInfo: site.certInfo!, pubKey: pubKey, privKey: privKey, - onReplace: site.managed - ? null - : (result) { - setState(() { - changed = true; - site.certInfo = result.certInfo; - site.key = result.key; - }); - }, + onReplace: + site.managed + ? null + : (result) { + setState(() { + changed = true; + site.certInfo = result.certInfo; + site.key = result.key; + }); + }, supportsQRScanning: widget.supportsQRScanning, ); } @@ -215,32 +227,38 @@ class _SiteConfigScreenState extends State { }, ), ConfigPageItem( - label: Text("CA"), - content: - Wrap(alignment: WrapAlignment.end, crossAxisAlignment: WrapCrossAlignment.center, children: [ + label: Text("CA"), + content: Wrap( + alignment: WrapAlignment.end, + crossAxisAlignment: WrapCrossAlignment.center, + children: [ caError ? Padding( - child: Icon(Icons.error, color: CupertinoColors.systemRed.resolveFrom(context), size: 20), - padding: EdgeInsets.only(right: 5)) + child: Icon(Icons.error, color: CupertinoColors.systemRed.resolveFrom(context), size: 20), + padding: EdgeInsets.only(right: 5), + ) : Container(), - caError ? Text('Needs attention') : Text(Utils.itemCountFormat(site.ca.length)) - ]), - onPressed: () { - Utils.openPage(context, (context) { - return CAListScreen( - cas: site.ca, - onSave: site.managed - ? null - : (ca) { + caError ? Text('Needs attention') : Text(Utils.itemCountFormat(site.ca.length)), + ], + ), + onPressed: () { + Utils.openPage(context, (context) { + return CAListScreen( + cas: site.ca, + onSave: + site.managed + ? null + : (ca) { setState(() { changed = true; site.ca = ca; }); }, - supportsQRScanning: widget.supportsQRScanning, - ); - }); - }) + supportsQRScanning: widget.supportsQRScanning, + ); + }); + }, + ), ], ); } @@ -251,28 +269,35 @@ class _SiteConfigScreenState extends State { children: [ ConfigPageItem( label: Text('Hosts'), - content: Wrap(alignment: WrapAlignment.end, crossAxisAlignment: WrapCrossAlignment.center, children: [ - site.staticHostmap.length == 0 - ? Padding( + content: Wrap( + alignment: WrapAlignment.end, + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + site.staticHostmap.length == 0 + ? Padding( child: Icon(Icons.error, color: CupertinoColors.systemRed.resolveFrom(context), size: 20), - padding: EdgeInsets.only(right: 5)) - : Container(), - site.staticHostmap.length == 0 - ? Text('Needs attention') - : Text(Utils.itemCountFormat(site.staticHostmap.length)) - ]), + padding: EdgeInsets.only(right: 5), + ) + : Container(), + site.staticHostmap.length == 0 + ? Text('Needs attention') + : Text(Utils.itemCountFormat(site.staticHostmap.length)), + ], + ), onPressed: () { Utils.openPage(context, (context) { return StaticHostsScreen( - hostmap: site.staticHostmap, - onSave: site.managed - ? null - : (map) { + hostmap: site.staticHostmap, + onSave: + site.managed + ? null + : (map) { setState(() { changed = true; site.staticHostmap = map; }); - }); + }, + ); }); }, ), @@ -285,24 +310,26 @@ class _SiteConfigScreenState extends State { label: "ADVANCED", children: [ ConfigPageItem( - label: Text('Advanced'), - onPressed: () { - Utils.openPage(context, (context) { - return AdvancedScreen( - site: site, - onSave: (settings) { - setState(() { - changed = true; - site.cipher = settings.cipher; - site.lhDuration = settings.lhDuration; - site.port = settings.port; - site.logVerbosity = settings.verbosity; - site.unsafeRoutes = settings.unsafeRoutes; - site.mtu = settings.mtu; - }); - }); - }); - }) + label: Text('Advanced'), + onPressed: () { + Utils.openPage(context, (context) { + return AdvancedScreen( + site: site, + onSave: (settings) { + setState(() { + changed = true; + site.cipher = settings.cipher; + site.lhDuration = settings.lhDuration; + site.port = settings.port; + site.logVerbosity = settings.verbosity; + site.unsafeRoutes = settings.unsafeRoutes; + site.mtu = settings.mtu; + }); + }, + ); + }); + }, + ), ], ); } diff --git a/lib/screens/siteConfig/StaticHostmapScreen.dart b/lib/screens/siteConfig/StaticHostmapScreen.dart index 3f92209..1e06009 100644 --- a/lib/screens/siteConfig/StaticHostmapScreen.dart +++ b/lib/screens/siteConfig/StaticHostmapScreen.dart @@ -27,8 +27,8 @@ class StaticHostmapScreen extends StatefulWidget { this.lighthouse = false, this.onDelete, required this.onSave, - }) : this.destinations = destinations ?? [], - super(key: key); + }) : this.destinations = destinations ?? [], + super(key: key); final List destinations; final String nebulaIp; @@ -67,67 +67,81 @@ class _StaticHostmapScreenState extends State { @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( + 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( + 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) { + 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(), + }, + ), + ), + ), + ], ), + 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: DangerButton( - child: Text('Delete'), - onPressed: () => Utils.confirmDelete(context, 'Delete host map?', () { - Navigator.of(context).pop(); - widget.onDelete!(); - })))) - : Container() - ])); + padding: EdgeInsets.only(top: 50, bottom: 10, left: 10, right: 10), + child: SizedBox( + width: double.infinity, + child: DangerButton( + child: Text('Delete'), + onPressed: + () => Utils.confirmDelete(context, 'Delete host map?', () { + Navigator.of(context).pop(); + widget.onDelete!(); + }), + ), + ), + ) + : Container(), + ], + ), + ); } _onSave() { @@ -147,47 +161,61 @@ class _StaticHostmapScreenState extends State { List items = []; _destinations.forEach((key, dest) { - items.add(ConfigItem( - key: key, - label: Align( + 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; - } - }, - )), - ]), - )); + 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( + items.add( + ConfigButtonItem( content: Text('Add another'), - onPressed: () => setState(() { + onPressed: + () => setState(() { _addDestination(); _dismissKeyboard(); - }))); + }), + ), + ); } return items; diff --git a/lib/screens/siteConfig/StaticHostsScreen.dart b/lib/screens/siteConfig/StaticHostsScreen.dart index f9a5ea5..a442d8b 100644 --- a/lib/screens/siteConfig/StaticHostsScreen.dart +++ b/lib/screens/siteConfig/StaticHostsScreen.dart @@ -18,20 +18,11 @@ class _Hostmap { List destinations; bool lighthouse; - _Hostmap({ - required this.focusNode, - required this.nebulaIp, - required this.destinations, - required this.lighthouse, - }); + _Hostmap({required this.focusNode, required this.nebulaIp, required this.destinations, required this.lighthouse}); } class StaticHostsScreen extends StatefulWidget { - const StaticHostsScreen({ - Key? key, - required this.hostmap, - required this.onSave, - }) : super(key: key); + const StaticHostsScreen({Key? key, required this.hostmap, required this.onSave}) : super(key: key); final Map hostmap; final ValueChanged>? onSave; @@ -47,8 +38,12 @@ class _StaticHostsScreenState extends State { @override void initState() { widget.hostmap.forEach((key, map) { - _hostmap[UniqueKey()] = - _Hostmap(focusNode: FocusNode(), nebulaIp: key, destinations: map.destinations, lighthouse: map.lighthouse); + _hostmap[UniqueKey()] = _Hostmap( + focusNode: FocusNode(), + nebulaIp: key, + destinations: map.destinations, + lighthouse: map.lighthouse, + ); }); super.initState(); @@ -57,12 +52,11 @@ class _StaticHostsScreenState extends State { @override Widget build(BuildContext context) { return FormPage( - title: 'Static Hosts', - changed: changed, - onSave: _onSave, - child: ConfigSection( - children: _buildHosts(), - )); + title: 'Static Hosts', + changed: changed, + onSave: _onSave, + child: ConfigSection(children: _buildHosts()), + ); } _onSave() { @@ -81,59 +75,73 @@ class _StaticHostsScreenState extends State { final double ipWidth = Utils.textSize("000.000.000.000", CupertinoTheme.of(context).textTheme.textStyle).width + 32; List items = []; _hostmap.forEach((key, host) { - items.add(ConfigPageItem( - label: Row(children: [ - Padding( - child: Icon(host.lighthouse ? Icons.lightbulb_outline : Icons.computer, - color: CupertinoColors.placeholderText.resolveFrom(context)), - padding: EdgeInsets.only(right: 10)), - Text(host.nebulaIp), - ]), - labelWidth: ipWidth, - content: Text(host.destinations.length.toString() + ' items', textAlign: TextAlign.end), - onPressed: () { - Utils.openPage(context, (context) { - return StaticHostmapScreen( + items.add( + ConfigPageItem( + label: Row( + children: [ + Padding( + child: Icon( + host.lighthouse ? Icons.lightbulb_outline : Icons.computer, + color: CupertinoColors.placeholderText.resolveFrom(context), + ), + padding: EdgeInsets.only(right: 10), + ), + Text(host.nebulaIp), + ], + ), + labelWidth: ipWidth, + content: Text(host.destinations.length.toString() + ' items', textAlign: TextAlign.end), + onPressed: () { + Utils.openPage(context, (context) { + return StaticHostmapScreen( nebulaIp: host.nebulaIp, destinations: host.destinations, lighthouse: host.lighthouse, - onSave: widget.onSave == null - ? null - : (map) { - setState(() { - changed = true; - host.nebulaIp = map.nebulaIp; - host.destinations = map.destinations; - host.lighthouse = map.lighthouse; - }); - }, - onDelete: widget.onSave == null - ? null - : () { - setState(() { - changed = true; - _hostmap.remove(key); - }); - }); - }); - }, - )); + onSave: + widget.onSave == null + ? null + : (map) { + setState(() { + changed = true; + host.nebulaIp = map.nebulaIp; + host.destinations = map.destinations; + host.lighthouse = map.lighthouse; + }); + }, + onDelete: + widget.onSave == null + ? null + : () { + setState(() { + changed = true; + _hostmap.remove(key); + }); + }, + ); + }); + }, + ), + ); }); if (widget.onSave != null) { - items.add(ConfigButtonItem( - content: Text('Add a new entry'), - onPressed: () { - Utils.openPage(context, (context) { - return StaticHostmapScreen(onSave: (map) { - setState(() { - changed = true; - _addHostmap(map); - }); + items.add( + ConfigButtonItem( + content: Text('Add a new entry'), + onPressed: () { + Utils.openPage(context, (context) { + return StaticHostmapScreen( + onSave: (map) { + setState(() { + changed = true; + _addHostmap(map); + }); + }, + ); }); - }); - }, - )); + }, + ), + ); } return items; @@ -141,7 +149,11 @@ class _StaticHostsScreenState extends State { _addHostmap(Hostmap map) { _hostmap[UniqueKey()] = (_Hostmap( - focusNode: FocusNode(), nebulaIp: map.nebulaIp, destinations: map.destinations, lighthouse: map.lighthouse)); + focusNode: FocusNode(), + nebulaIp: map.nebulaIp, + destinations: map.destinations, + lighthouse: map.lighthouse, + )); } @override diff --git a/lib/screens/siteConfig/UnsafeRouteScreen.dart b/lib/screens/siteConfig/UnsafeRouteScreen.dart index 351ddd0..9b16120 100644 --- a/lib/screens/siteConfig/UnsafeRouteScreen.dart +++ b/lib/screens/siteConfig/UnsafeRouteScreen.dart @@ -10,12 +10,7 @@ import 'package:mobile_nebula/models/UnsafeRoute.dart'; import 'package:mobile_nebula/services/utils.dart'; class UnsafeRouteScreen extends StatefulWidget { - const UnsafeRouteScreen({ - Key? key, - required this.route, - required this.onSave, - this.onDelete, - }) : super(key: key); + const UnsafeRouteScreen({Key? key, required this.route, required this.onSave, this.onDelete}) : super(key: key); final UnsafeRoute route; final ValueChanged onSave; @@ -44,67 +39,79 @@ class _UnsafeRouteScreenState extends State { var routeCIDR = route.route == null ? CIDR() : CIDR.fromString(route.route!); return FormPage( - title: widget.onDelete == null ? 'New Unsafe Route' : 'Edit Unsafe Route', - changed: changed, - onSave: _onSave, - child: Column(children: [ - ConfigSection(children: [ - ConfigItem( + title: widget.onDelete == null ? 'New Unsafe Route' : 'Edit Unsafe Route', + changed: changed, + onSave: _onSave, + child: Column( + children: [ + ConfigSection( + children: [ + ConfigItem( label: Text('Route'), content: CIDRFormField( - initialValue: routeCIDR, - textInputAction: TextInputAction.next, - focusNode: routeFocus, - nextFocusNode: viaFocus, - onSaved: (v) { - route.route = v.toString(); - })), - ConfigItem( + initialValue: routeCIDR, + textInputAction: TextInputAction.next, + focusNode: routeFocus, + nextFocusNode: viaFocus, + onSaved: (v) { + route.route = v.toString(); + }, + ), + ), + ConfigItem( label: Text('Via'), content: IPFormField( - initialValue: route.via ?? '', - ipOnly: true, - help: 'nebula ip', - textAlign: TextAlign.end, - crossAxisAlignment: CrossAxisAlignment.end, - textInputAction: TextInputAction.next, - focusNode: viaFocus, - nextFocusNode: mtuFocus, - onSaved: (v) { - if (v != null) { - route.via = v; - } - })), -//TODO: Android doesn't appear to support route based MTU, figure this out -// ConfigItem( -// label: Text('MTU'), -// content: PlatformTextFormField( -// placeholder: "", -// validator: mtuValidator(false), -// keyboardType: TextInputType.number, -// inputFormatters: [WhitelistingTextInputFormatter.digitsOnly], -// initialValue: route?.mtu.toString(), -// textAlign: TextAlign.end, -// textInputAction: TextInputAction.done, -// focusNode: mtuFocus, -// onSaved: (v) { -// route.mtu = int.tryParse(v); -// })), - ]), + initialValue: route.via ?? '', + ipOnly: true, + help: 'nebula ip', + textAlign: TextAlign.end, + crossAxisAlignment: CrossAxisAlignment.end, + textInputAction: TextInputAction.next, + focusNode: viaFocus, + nextFocusNode: mtuFocus, + onSaved: (v) { + if (v != null) { + route.via = v; + } + }, + ), + ), + //TODO: Android doesn't appear to support route based MTU, figure this out + // ConfigItem( + // label: Text('MTU'), + // content: PlatformTextFormField( + // placeholder: "", + // validator: mtuValidator(false), + // keyboardType: TextInputType.number, + // inputFormatters: [WhitelistingTextInputFormatter.digitsOnly], + // initialValue: route?.mtu.toString(), + // textAlign: TextAlign.end, + // textInputAction: TextInputAction.done, + // focusNode: mtuFocus, + // onSaved: (v) { + // route.mtu = int.tryParse(v); + // })), + ], + ), widget.onDelete != null ? Padding( - padding: EdgeInsets.only(top: 50, bottom: 10, left: 10, right: 10), - child: SizedBox( - width: double.infinity, - child: DangerButton( - child: Text('Delete'), - onPressed: () => Utils.confirmDelete(context, 'Delete unsafe route?', () { + padding: EdgeInsets.only(top: 50, bottom: 10, left: 10, right: 10), + child: SizedBox( + width: double.infinity, + child: DangerButton( + child: Text('Delete'), + onPressed: + () => Utils.confirmDelete(context, 'Delete unsafe route?', () { Navigator.of(context).pop(); widget.onDelete!(); }), - ))) - : Container() - ])); + ), + ), + ) + : Container(), + ], + ), + ); } _onSave() { diff --git a/lib/screens/siteConfig/UnsafeRoutesScreen.dart b/lib/screens/siteConfig/UnsafeRoutesScreen.dart index 08ba491..48c51f6 100644 --- a/lib/screens/siteConfig/UnsafeRoutesScreen.dart +++ b/lib/screens/siteConfig/UnsafeRoutesScreen.dart @@ -8,11 +8,7 @@ import 'package:mobile_nebula/screens/siteConfig/UnsafeRouteScreen.dart'; import 'package:mobile_nebula/services/utils.dart'; class UnsafeRoutesScreen extends StatefulWidget { - const UnsafeRoutesScreen({ - Key? key, - required this.unsafeRoutes, - required this.onSave, - }) : super(key: key); + const UnsafeRoutesScreen({Key? key, required this.unsafeRoutes, required this.onSave}) : super(key: key); final List unsafeRoutes; final ValueChanged>? onSave; @@ -38,12 +34,11 @@ class _UnsafeRoutesScreenState extends State { @override Widget build(BuildContext context) { return FormPage( - title: 'Unsafe Routes', - changed: changed, - onSave: _onSave, - child: ConfigSection( - children: _buildRoutes(), - )); + title: 'Unsafe Routes', + changed: changed, + onSave: _onSave, + child: ConfigSection(children: _buildRoutes()), + ); } _onSave() { @@ -57,14 +52,15 @@ class _UnsafeRoutesScreenState extends State { final double ipWidth = Utils.textSize("000.000.000.000/00", CupertinoTheme.of(context).textTheme.textStyle).width; List items = []; unsafeRoutes.forEach((key, route) { - items.add(ConfigPageItem( - disabled: widget.onSave == null, - label: Text(route.route ?? ''), - labelWidth: ipWidth, - content: Text('via ${route.via}', textAlign: TextAlign.end), - onPressed: () { - Utils.openPage(context, (context) { - return UnsafeRouteScreen( + items.add( + ConfigPageItem( + disabled: widget.onSave == null, + label: Text(route.route ?? ''), + labelWidth: ipWidth, + content: Text('via ${route.via}', textAlign: TextAlign.end), + onPressed: () { + Utils.openPage(context, (context) { + return UnsafeRouteScreen( route: route, onSave: (route) { setState(() { @@ -77,28 +73,33 @@ class _UnsafeRoutesScreenState extends State { changed = true; unsafeRoutes.remove(key); }); - }); - }); - }, - )); + }, + ); + }); + }, + ), + ); }); if (widget.onSave != null) { - items.add(ConfigButtonItem( - content: Text('Add a new route'), - onPressed: () { - Utils.openPage(context, (context) { - return UnsafeRouteScreen( + items.add( + ConfigButtonItem( + content: Text('Add a new route'), + onPressed: () { + Utils.openPage(context, (context) { + return UnsafeRouteScreen( route: UnsafeRoute(), onSave: (route) { setState(() { changed = true; unsafeRoutes[UniqueKey()] = route; }); - }); - }); - }, - )); + }, + ); + }); + }, + ), + ); } return items; diff --git a/lib/services/share.dart b/lib/services/share.dart index 23f6a4f..f3ba485 100644 --- a/lib/services/share.dart +++ b/lib/services/share.dart @@ -40,8 +40,12 @@ class Share { /// - title: Title of message or subject if sending an email /// - filePath: Path to the file to share /// - filename: An optional filename to override the existing file - static Future shareFile(BuildContext context, - {required String title, required String filePath, String? filename}) async { + static Future shareFile( + BuildContext context, { + required String title, + required String filePath, + String? filename, + }) async { assert(title.isNotEmpty); assert(filePath.isNotEmpty); @@ -51,8 +55,11 @@ class Share { // If we want to support that again we will need to save the file to a temporary directory, share that, // and then delete it final xFile = sp.XFile(filePath, name: filename); - final result = await sp.Share.shareXFiles([xFile], - subject: title, sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size); + final result = await sp.Share.shareXFiles( + [xFile], + subject: title, + sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size, + ); return result.status == sp.ShareResultStatus.success; } } diff --git a/lib/services/storage.dart b/lib/services/storage.dart index a3451c3..37552d5 100644 --- a/lib/services/storage.dart +++ b/lib/services/storage.dart @@ -20,11 +20,14 @@ class Storage { var completer = Completer>(); - Directory(parent).list().listen((FileSystemEntity entity) { - list.add(entity); - }).onDone(() { - completer.complete(list); - }); + Directory(parent) + .list() + .listen((FileSystemEntity entity) { + list.add(entity); + }) + .onDone(() { + completer.complete(list); + }); return completer.future; } diff --git a/lib/services/theme.dart b/lib/services/theme.dart index 42f2f08..c241bcf 100644 --- a/lib/services/theme.dart +++ b/lib/services/theme.dart @@ -339,16 +339,13 @@ class MaterialTheme { } ThemeData theme(ColorScheme colorScheme) => ThemeData( - useMaterial3: true, - brightness: colorScheme.brightness, - colorScheme: colorScheme, - textTheme: textTheme.apply( - bodyColor: colorScheme.onSurface, - displayColor: colorScheme.onSurface, - ), - scaffoldBackgroundColor: colorScheme.surface, - canvasColor: colorScheme.surface, - ); + useMaterial3: true, + brightness: colorScheme.brightness, + colorScheme: colorScheme, + textTheme: textTheme.apply(bodyColor: colorScheme.onSurface, displayColor: colorScheme.onSurface), + scaffoldBackgroundColor: colorScheme.surface, + canvasColor: colorScheme.surface, + ); List get extendedColors => []; } diff --git a/lib/services/utils.dart b/lib/services/utils.dart index ba54edf..32437c7 100644 --- a/lib/services/utils.dart +++ b/lib/services/utils.dart @@ -27,20 +27,16 @@ class Utils { } static Size textSize(String text, TextStyle style) { - final TextPainter textPainter = - TextPainter(text: TextSpan(text: text, style: style), maxLines: 1, textDirection: TextDirection.ltr) - ..layout(minWidth: 0, maxWidth: double.infinity); + final TextPainter textPainter = TextPainter( + text: TextSpan(text: text, style: style), + maxLines: 1, + textDirection: TextDirection.ltr, + )..layout(minWidth: 0, maxWidth: double.infinity); return textPainter.size; } static openPage(BuildContext context, WidgetBuilder pageToDisplayBuilder) { - Navigator.push( - context, - platformPageRoute( - context: context, - builder: pageToDisplayBuilder, - ), - ); + Navigator.push(context, platformPageRoute(context: context, builder: pageToDisplayBuilder)); } static String itemCountFormat(int items, {singleSuffix = "item", multiSuffix = "items"}) { @@ -56,14 +52,15 @@ class Utils { static Widget leadingBackWidget(BuildContext context, {label = 'Back', Function? onPressed}) { if (Platform.isIOS) { return CupertinoNavigationBarBackButton( - previousPageTitle: label, - onPressed: () { - if (onPressed == null) { - Navigator.pop(context); - } else { - onPressed(); - } - }); + previousPageTitle: label, + onPressed: () { + if (onPressed == null) { + Navigator.pop(context); + } else { + onPressed(); + } + }, + ); } return IconButton( @@ -82,37 +79,47 @@ class Utils { static Widget trailingSaveWidget(BuildContext context, Function onPressed) { return PlatformTextButton( - child: Text('Save'), padding: Platform.isAndroid ? null : EdgeInsets.zero, onPressed: () => onPressed()); + child: Text('Save'), + padding: Platform.isAndroid ? null : EdgeInsets.zero, + onPressed: () => onPressed(), + ); } /// Simple cross platform delete confirmation dialog - can also be used to confirm throwing away a change by swapping the deleteLabel - static confirmDelete(BuildContext context, String title, Function onConfirm, - {String deleteLabel = 'Delete', String cancelLabel = 'Cancel'}) { + static confirmDelete( + BuildContext context, + String title, + Function onConfirm, { + String deleteLabel = 'Delete', + String cancelLabel = 'Cancel', + }) { showDialog( - context: context, - barrierDismissible: false, - builder: (context) { - return PlatformAlertDialog( - title: Text(title), - actions: [ - PlatformDialogAction( - child: Text(deleteLabel, - style: - TextStyle(fontWeight: FontWeight.bold, color: CupertinoColors.systemRed.resolveFrom(context))), - onPressed: () { - Navigator.pop(context); - onConfirm(); - }, + context: context, + barrierDismissible: false, + builder: (context) { + return PlatformAlertDialog( + title: Text(title), + actions: [ + PlatformDialogAction( + child: Text( + deleteLabel, + style: TextStyle(fontWeight: FontWeight.bold, color: CupertinoColors.systemRed.resolveFrom(context)), ), - PlatformDialogAction( - child: Text(cancelLabel), - onPressed: () { - Navigator.of(context).pop(); - }, - ) - ], - ); - }); + onPressed: () { + Navigator.pop(context); + onConfirm(); + }, + ), + PlatformDialogAction( + child: Text(cancelLabel), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + ); + }, + ); } static popError(BuildContext context, String title, String error, {StackTrace? stack}) { @@ -121,33 +128,38 @@ class Utils { } showDialog( - context: context, - barrierDismissible: false, - builder: (context) { - if (Platform.isAndroid) { - return AlertDialog(title: Text(title), content: Text(error), actions: [ + context: context, + barrierDismissible: false, + builder: (context) { + if (Platform.isAndroid) { + return AlertDialog( + title: Text(title), + content: Text(error), + actions: [ TextButton( child: Text('Ok'), onPressed: () { Navigator.of(context).pop(); }, - ) - ]); - } - - return CupertinoAlertDialog( - title: Text(title), - content: Text(error), - actions: [ - CupertinoDialogAction( - child: Text('Ok'), - onPressed: () { - Navigator.of(context).pop(); - }, - ) + ), ], ); - }); + } + + return CupertinoAlertDialog( + title: Text(title), + content: Text(error), + actions: [ + CupertinoDialogAction( + child: Text('Ok'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + ); + }, + ); } static launchUrl(String url, BuildContext context) async {