diff --git a/.github/workflows/flutterfmt.yml b/.github/workflows/fluttercheck.yml similarity index 54% rename from .github/workflows/flutterfmt.yml rename to .github/workflows/fluttercheck.yml index 997f798..e31d015 100644 --- a/.github/workflows/flutterfmt.yml +++ b/.github/workflows/fluttercheck.yml @@ -1,12 +1,11 @@ -name: Flutter format +name: Flutter check on: push: branches: - main pull_request: paths: - - '.github/workflows/flutterfmt.yml' - - '.github/workflows/flutterfmt.sh' + - '.github/workflows/fluttercheck.yml' - '**.dart' jobs: flutterfmt: @@ -25,3 +24,19 @@ jobs: - name: Check formating run: dart format -l120 lib/ --set-exit-if-changed --suppress-analytics --output none + flutterlint: + name: Run flutter lint + runs-on: ubuntu-latest + steps: + - name: Install flutter + uses: subosito/flutter-action@v2 + with: + flutter-version: '3.29.0' + + - name: Check out code + uses: actions/checkout@v4 + with: + show-progress: false + + - name: Check linting + run: dart fix --dry-run diff --git a/analysis_options.yaml b/analysis_options.yaml index bf04573..80c8ab4 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,3 +1,35 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/tools/linter-rules. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at # https://dart.dev/tools/analysis formatter: page_width: 120 + +analyzer: + exclude: + # This is a generated file, let's ignore it. + - lib/services/theme.dart diff --git a/lib/components/CIDRField.dart b/lib/components/CIDRField.dart index d85356d..ae584ca 100644 --- a/lib/components/CIDRField.dart +++ b/lib/components/CIDRField.dart @@ -8,7 +8,7 @@ import 'IPField.dart'; //TODO: Support initialValue class CIDRField extends StatefulWidget { const CIDRField({ - Key? key, + super.key, this.ipHelp = "ip address", this.autoFocus = false, this.focusNode, @@ -17,7 +17,7 @@ class CIDRField extends StatefulWidget { this.textInputAction, this.ipController, this.bitsController, - }) : super(key: key); + }); final String ipHelp; final bool autoFocus; diff --git a/lib/components/CIDRFormField.dart b/lib/components/CIDRFormField.dart index 915f28d..9f11dec 100644 --- a/lib/components/CIDRFormField.dart +++ b/lib/components/CIDRFormField.dart @@ -6,21 +6,18 @@ import 'package:mobile_nebula/validators/ipValidator.dart'; class CIDRFormField extends FormField { //TODO: onSaved, validator, auto-validate, enabled? CIDRFormField({ - Key? key, + super.key, autoFocus = false, enableIPV6 = false, focusNode, nextFocusNode, ValueChanged? onChanged, - FormFieldSetter? onSaved, + super.onSaved, textInputAction, - CIDR? initialValue, + super.initialValue, this.ipController, this.bitsController, }) : super( - key: key, - initialValue: initialValue, - onSaved: onSaved, validator: (cidr) { if (cidr == null) { return "Please fill out this field"; diff --git a/lib/components/DangerButton.dart b/lib/components/DangerButton.dart index bbc029b..cd8c9cd 100644 --- a/lib/components/DangerButton.dart +++ b/lib/components/DangerButton.dart @@ -4,7 +4,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; class DangerButton extends StatelessWidget { - const DangerButton({Key? key, required this.child, this.onPressed}) : super(key: key); + const DangerButton({super.key, required this.child, this.onPressed}); final Widget child; final GestureTapCallback? onPressed; @@ -14,11 +14,11 @@ class DangerButton extends StatelessWidget { if (Platform.isAndroid) { return FilledButton( onPressed: onPressed, - child: child, style: FilledButton.styleFrom( backgroundColor: Theme.of(context).colorScheme.error, foregroundColor: Theme.of(context).colorScheme.onError, ), + child: child, ); } else { // Workaround for https://github.com/flutter/flutter/issues/161590 @@ -26,9 +26,9 @@ class DangerButton extends StatelessWidget { return CupertinoTheme( data: themeData.copyWith(primaryColor: CupertinoColors.white), child: CupertinoButton( - child: child, onPressed: onPressed, color: CupertinoColors.systemRed.resolveFrom(context), + child: child, ), ); } diff --git a/lib/components/FormPage.dart b/lib/components/FormPage.dart index b471718..298e24b 100644 --- a/lib/components/FormPage.dart +++ b/lib/components/FormPage.dart @@ -6,14 +6,14 @@ 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, + super.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; diff --git a/lib/components/IPAndPortField.dart b/lib/components/IPAndPortField.dart index 3d71955..3f48722 100644 --- a/lib/components/IPAndPortField.dart +++ b/lib/components/IPAndPortField.dart @@ -8,7 +8,7 @@ import 'IPField.dart'; //TODO: Support initialValue class IPAndPortField extends StatefulWidget { const IPAndPortField({ - Key? key, + super.key, this.ipOnly = false, this.ipHelp = "ip address", this.autoFocus = false, @@ -20,7 +20,7 @@ class IPAndPortField extends StatefulWidget { this.ipTextAlign, this.ipController, this.portController, - }) : super(key: key); + }); final String ipHelp; final bool ipOnly; diff --git a/lib/components/IPAndPortFormField.dart b/lib/components/IPAndPortFormField.dart index 416fb3d..27e925a 100644 --- a/lib/components/IPAndPortFormField.dart +++ b/lib/components/IPAndPortFormField.dart @@ -8,7 +8,7 @@ import 'IPAndPortField.dart'; class IPAndPortFormField extends FormField { //TODO: onSaved, validator, auto-validate, enabled? IPAndPortFormField({ - Key? key, + super.key, ipOnly = false, enableIPV6 = false, ipHelp = "ip address", @@ -16,17 +16,14 @@ class IPAndPortFormField extends FormField { focusNode, nextFocusNode, ValueChanged? onChanged, - FormFieldSetter? onSaved, + super.onSaved, textInputAction, - IPAndPort? initialValue, + super.initialValue, noBorder, ipTextAlign = TextAlign.center, this.ipController, this.portController, }) : super( - key: key, - initialValue: initialValue, - onSaved: onSaved, validator: (ipAndPort) { if (ipAndPort == null) { return "Please fill out this field"; @@ -114,10 +111,7 @@ 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 ?? "")); bool shouldUpdate = false; if (widget.ipController != oldWidget.ipController) { diff --git a/lib/components/IPField.dart b/lib/components/IPField.dart index 96c38c6..1c2a05b 100644 --- a/lib/components/IPField.dart +++ b/lib/components/IPField.dart @@ -17,7 +17,7 @@ class IPField extends StatelessWidget { final textAlign; const IPField({ - Key? key, + super.key, this.ipOnly = false, this.help = "ip address", this.autoFocus = false, @@ -28,7 +28,7 @@ class IPField extends StatelessWidget { this.textInputAction, this.controller, this.textAlign = TextAlign.center, - }) : super(key: key); + }); @override Widget build(BuildContext context) { @@ -48,7 +48,7 @@ class IPField extends StatelessWidget { maxLength: ipOnly ? 15 : null, maxLengthEnforcement: ipOnly ? MaxLengthEnforcement.enforced : MaxLengthEnforcement.none, inputFormatters: ipOnly ? [IPTextInputFormatter()] : [FilteringTextInputFormatter.allow(RegExp(r'[^\s]+'))], - textInputAction: this.textInputAction, + textInputAction: textInputAction, placeholder: help, ), ); @@ -72,7 +72,7 @@ class IPTextInputFormatter extends TextInputFormatter { TextEditingValue _selectionAwareTextManipulation( TextEditingValue value, - String substringManipulation(String substring), + String Function(String substring) substringManipulation, ) { final int selectionStartIndex = value.selection.start; final int selectionEndIndex = value.selection.end; diff --git a/lib/components/IPFormField.dart b/lib/components/IPFormField.dart index bde80d5..6d6ab64 100644 --- a/lib/components/IPFormField.dart +++ b/lib/components/IPFormField.dart @@ -9,7 +9,7 @@ import 'IPField.dart'; class IPFormField extends FormField { //TODO: validator, auto-validate, enabled? IPFormField({ - Key? key, + super.key, ipOnly = false, enableIPV6 = false, help = "ip address", @@ -17,7 +17,7 @@ class IPFormField extends FormField { focusNode, nextFocusNode, ValueChanged? onChanged, - FormFieldSetter? onSaved, + super.onSaved, textPadding = const EdgeInsets.all(6.0), textInputAction, initialValue, @@ -25,9 +25,7 @@ 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"; @@ -108,8 +106,9 @@ class _IPFormField extends FormFieldState { oldWidget.controller?.removeListener(_handleControllerChanged); widget.controller?.addListener(_handleControllerChanged); - if (oldWidget.controller != null && widget.controller == null) + if (oldWidget.controller != null && widget.controller == null) { _controller = TextEditingController.fromValue(oldWidget.controller!.value); + } if (widget.controller != null) { setValue(widget.controller!.text); if (oldWidget.controller == null) _controller = null; diff --git a/lib/components/PlatformTextFormField.dart b/lib/components/PlatformTextFormField.dart index 624d78a..72d09d2 100644 --- a/lib/components/PlatformTextFormField.dart +++ b/lib/components/PlatformTextFormField.dart @@ -6,7 +6,7 @@ import 'package:mobile_nebula/components/SpecialTextField.dart'; class PlatformTextFormField extends FormField { //TODO: auto-validate, enabled? PlatformTextFormField({ - Key? key, + super.key, widgetKey, this.controller, focusNode, @@ -28,11 +28,9 @@ class PlatformTextFormField extends FormField { String? initialValue, String? placeholder, FormFieldValidator? validator, - ValueChanged? onSaved, + super.onSaved, }) : super( - key: key, initialValue: controller != null ? controller.text : (initialValue ?? ''), - onSaved: onSaved, validator: (str) { if (validator != null) { return validator(str); @@ -117,8 +115,9 @@ class _PlatformTextFormFieldState extends FormFieldState { oldWidget.controller?.removeListener(_handleControllerChanged); widget.controller?.addListener(_handleControllerChanged); - if (oldWidget.controller != null && widget.controller == null) + if (oldWidget.controller != null && widget.controller == null) { _controller = TextEditingController.fromValue(oldWidget.controller!.value); + } if (widget.controller != null) { setValue(widget.controller!.text); if (oldWidget.controller == null) _controller = null; diff --git a/lib/components/SimplePage.dart b/lib/components/SimplePage.dart index 78d584d..76c466f 100644 --- a/lib/components/SimplePage.dart +++ b/lib/components/SimplePage.dart @@ -6,7 +6,7 @@ enum SimpleScrollable { none, vertical, horizontal, both } class SimplePage extends StatelessWidget { const SimplePage({ - Key? key, + super.key, required this.title, required this.child, this.leadingAction, @@ -19,7 +19,7 @@ class SimplePage extends StatelessWidget { this.onLoading, this.alignment, this.refreshController, - }) : super(key: key); + }); final Widget title; final Widget child; @@ -43,13 +43,13 @@ class SimplePage extends StatelessWidget { @override Widget build(BuildContext context) { Widget realChild = child; - var addScrollbar = this.scrollbar; + var addScrollbar = scrollbar; if (scrollable == SimpleScrollable.vertical || scrollable == SimpleScrollable.both) { realChild = SingleChildScrollView( scrollDirection: Axis.vertical, - child: realChild, controller: refreshController == null ? scrollController : null, + child: realChild, ); addScrollbar = true; } @@ -69,10 +69,10 @@ class SimplePage extends StatelessWidget { onRefresh: onRefresh, onLoading: onLoading, controller: refreshController!, - child: realChild, enablePullUp: onLoading != null, enablePullDown: onRefresh != null, footer: ClassicFooter(loadStyle: LoadStyle.ShowWhenLoading), + child: realChild, ), ); addScrollbar = true; @@ -83,7 +83,7 @@ class SimplePage extends StatelessWidget { } if (alignment != null) { - realChild = Align(alignment: this.alignment!, child: realChild); + realChild = Align(alignment: alignment!, child: realChild); } if (bottomBar != null) { diff --git a/lib/components/SiteItem.dart b/lib/components/SiteItem.dart index d1d8d4f..165b9dc 100644 --- a/lib/components/SiteItem.dart +++ b/lib/components/SiteItem.dart @@ -6,7 +6,7 @@ import 'package:mobile_nebula/models/Site.dart'; import 'package:mobile_nebula/services/utils.dart'; class SiteItem extends StatelessWidget { - const SiteItem({Key? key, required this.site, this.onPressed}) : super(key: key); + const SiteItem({super.key, required this.site, this.onPressed}); final Site site; final onPressed; @@ -14,7 +14,7 @@ class SiteItem extends StatelessWidget { @override Widget build(BuildContext context) { final borderColor = - site.errors.length > 0 + site.errors.isNotEmpty ? CupertinoColors.systemRed.resolveFrom(context) : site.connected ? CupertinoColors.systemGreen.resolveFrom(context) diff --git a/lib/components/SiteTitle.dart b/lib/components/SiteTitle.dart index f85f476..12f3c2f 100644 --- a/lib/components/SiteTitle.dart +++ b/lib/components/SiteTitle.dart @@ -4,7 +4,7 @@ import 'package:flutter_svg/svg.dart'; import '../models/Site.dart'; class SiteTitle extends StatelessWidget { - const SiteTitle({Key? key, required this.site}) : super(key: key); + const SiteTitle({super.key, required this.site}); final Site site; diff --git a/lib/components/SpecialButton.dart b/lib/components/SpecialButton.dart index 0779531..a337a56 100644 --- a/lib/components/SpecialButton.dart +++ b/lib/components/SpecialButton.dart @@ -5,8 +5,14 @@ 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); + const SpecialButton({ + super.key, + this.child, + this.color, + this.onPressed, + this.useButtonTheme = false, + this.decoration, + }); final Widget? child; final Color? color; @@ -26,7 +32,7 @@ class _SpecialButtonState extends State with SingleTickerProvider } Widget _buildAndroid() { - var textStyle; + TextStyle? textStyle; if (widget.useButtonTheme) { textStyle = Theme.of(context).textTheme.labelLarge; } @@ -36,7 +42,7 @@ class _SpecialButtonState extends State with SingleTickerProvider child: Ink( decoration: widget.decoration, color: widget.color, - child: InkWell(child: widget.child, onTap: widget.onPressed), + child: InkWell(onTap: widget.onPressed, child: widget.child), ), ); } @@ -59,7 +65,7 @@ class _SpecialButtonState extends State with SingleTickerProvider button: true, child: FadeTransition( opacity: _opacityAnimation!, - child: DefaultTextStyle(style: textStyle, child: Container(child: widget.child, color: widget.color)), + child: DefaultTextStyle(style: textStyle, child: Container(color: widget.color, child: widget.child)), ), ), ), diff --git a/lib/components/SpecialTextField.dart b/lib/components/SpecialTextField.dart index 1912aac..c3e3682 100644 --- a/lib/components/SpecialTextField.dart +++ b/lib/components/SpecialTextField.dart @@ -5,7 +5,7 @@ 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, + super.key, this.placeholder, this.suffix, this.controller, @@ -28,7 +28,7 @@ class SpecialTextField extends StatefulWidget { this.keyboardAppearance, this.textAlignVertical, this.inputFormatters, - }) : super(key: key); + }); final String? placeholder; final TextEditingController? controller; @@ -64,7 +64,7 @@ class _SpecialTextFieldState extends State { @override void initState() { - if (widget.inputFormatters == null || formatters.length == 0) { + if (widget.inputFormatters == null || formatters.isEmpty) { formatters = [FilteringTextInputFormatter.allow(RegExp(r'[^\t]'))]; } else { formatters = widget.inputFormatters!; diff --git a/lib/components/buttons/PrimaryButton.dart b/lib/components/buttons/PrimaryButton.dart index 449d523..56ef3da 100644 --- a/lib/components/buttons/PrimaryButton.dart +++ b/lib/components/buttons/PrimaryButton.dart @@ -4,7 +4,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; class PrimaryButton extends StatelessWidget { - const PrimaryButton({Key? key, required this.child, this.onPressed}) : super(key: key); + const PrimaryButton({super.key, required this.child, this.onPressed}); final Widget child; final GestureTapCallback? onPressed; @@ -14,8 +14,8 @@ class PrimaryButton extends StatelessWidget { if (Platform.isAndroid) { return FilledButton( onPressed: onPressed, - child: child, style: FilledButton.styleFrom(backgroundColor: Theme.of(context).colorScheme.primary), + child: child, ); } else { // Workaround for https://github.com/flutter/flutter/issues/161590 @@ -23,9 +23,9 @@ class PrimaryButton extends StatelessWidget { return CupertinoTheme( data: themeData.copyWith(primaryColor: CupertinoColors.white), child: CupertinoButton( - child: child, onPressed: onPressed, color: CupertinoColors.secondaryLabel.resolveFrom(context), + child: child, ), ); } diff --git a/lib/components/config/ConfigButtonItem.dart b/lib/components/config/ConfigButtonItem.dart index de484f2..fb9a23e 100644 --- a/lib/components/config/ConfigButtonItem.dart +++ b/lib/components/config/ConfigButtonItem.dart @@ -5,7 +5,7 @@ import 'package:mobile_nebula/services/utils.dart'; // A config item that detects tapping and calls back on a tap class ConfigButtonItem extends StatelessWidget { - const ConfigButtonItem({Key? key, this.content, this.onPressed}) : super(key: key); + const ConfigButtonItem({super.key, this.content, this.onPressed}); final Widget? content; final onPressed; diff --git a/lib/components/config/ConfigCheckboxItem.dart b/lib/components/config/ConfigCheckboxItem.dart index 7d37c8b..f3933b8 100644 --- a/lib/components/config/ConfigCheckboxItem.dart +++ b/lib/components/config/ConfigCheckboxItem.dart @@ -4,13 +4,13 @@ import 'package:mobile_nebula/services/utils.dart'; class ConfigCheckboxItem extends StatelessWidget { const ConfigCheckboxItem({ - Key? key, + super.key, this.label, this.content, this.labelWidth = 100, this.onChanged, this.checked = false, - }) : super(key: key); + }); final Widget? label; final Widget? content; @@ -26,8 +26,8 @@ class ConfigCheckboxItem extends StatelessWidget { child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ - label != null ? Container(width: labelWidth, child: label) : Container(), - Expanded(child: Container(child: content, padding: EdgeInsets.only(right: 10))), + label != null ? SizedBox(width: labelWidth, child: label) : Container(), + Expanded(child: Container(padding: EdgeInsets.only(right: 10), child: content)), checked ? Icon(CupertinoIcons.check_mark, color: CupertinoColors.systemBlue.resolveFrom(context)) : Container(), diff --git a/lib/components/config/ConfigHeader.dart b/lib/components/config/ConfigHeader.dart index 032e147..6154a3f 100644 --- a/lib/components/config/ConfigHeader.dart +++ b/lib/components/config/ConfigHeader.dart @@ -9,7 +9,7 @@ TextStyle basicTextStyle(BuildContext context) => const double _headerFontSize = 13.0; class ConfigHeader extends StatelessWidget { - const ConfigHeader({Key? key, required this.label, this.color}) : super(key: key); + const ConfigHeader({super.key, required this.label, this.color}); final String label; final Color? color; diff --git a/lib/components/config/ConfigItem.dart b/lib/components/config/ConfigItem.dart index 81b0667..0956fff 100644 --- a/lib/components/config/ConfigItem.dart +++ b/lib/components/config/ConfigItem.dart @@ -6,12 +6,12 @@ import 'package:mobile_nebula/services/utils.dart'; class ConfigItem extends StatelessWidget { const ConfigItem({ - Key? key, + super.key, this.label, required this.content, this.labelWidth = 100, this.crossAxisAlignment = CrossAxisAlignment.center, - }) : super(key: key); + }); final Widget? label; final Widget content; @@ -20,7 +20,7 @@ class ConfigItem extends StatelessWidget { @override Widget build(BuildContext context) { - var textStyle; + TextStyle textStyle; if (Platform.isAndroid) { textStyle = Theme.of(context).textTheme.labelLarge!.copyWith(fontWeight: FontWeight.normal); } else { @@ -34,7 +34,7 @@ class ConfigItem extends StatelessWidget { child: Row( crossAxisAlignment: crossAxisAlignment, children: [ - Container(width: labelWidth, child: DefaultTextStyle(style: textStyle, child: Container(child: label))), + SizedBox(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 1b93839..32906a5 100644 --- a/lib/components/config/ConfigPageItem.dart +++ b/lib/components/config/ConfigPageItem.dart @@ -2,19 +2,20 @@ import 'dart:io'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; + import 'package:mobile_nebula/components/SpecialButton.dart'; import 'package:mobile_nebula/services/utils.dart'; class ConfigPageItem extends StatelessWidget { const ConfigPageItem({ - Key? key, + super.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; @@ -25,7 +26,7 @@ class ConfigPageItem extends StatelessWidget { @override Widget build(BuildContext context) { - var theme; + dynamic theme; if (Platform.isAndroid) { final origTheme = Theme.of(context); @@ -44,7 +45,7 @@ class ConfigPageItem extends StatelessWidget { Widget _buildContent(BuildContext context) { return SpecialButton( - onPressed: this.disabled ? null : onPressed, + onPressed: disabled ? null : onPressed, color: Utils.configItemBackground(context), child: Container( padding: EdgeInsets.symmetric(vertical: 6, horizontal: 15), @@ -52,9 +53,9 @@ class ConfigPageItem extends StatelessWidget { 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 + label != null ? SizedBox(width: labelWidth, child: label) : Container(), + Expanded(child: Container(padding: EdgeInsets.only(right: 10), child: content)), + 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 58cf3da..15f813c 100644 --- a/lib/components/config/ConfigSection.dart +++ b/lib/components/config/ConfigSection.dart @@ -4,8 +4,7 @@ import 'package:mobile_nebula/services/utils.dart'; import 'ConfigHeader.dart'; class ConfigSection extends StatelessWidget { - const ConfigSection({Key? key, this.label, required this.children, this.borderColor, this.labelColor}) - : super(key: key); + const ConfigSection({super.key, this.label, required this.children, this.borderColor, this.labelColor}); final List children; final String? label; @@ -16,21 +15,21 @@ class ConfigSection extends StatelessWidget { Widget build(BuildContext context) { final border = BorderSide(color: borderColor ?? Utils.configSectionBorder(context)); - List _children = []; + List mappedChildren = []; final len = children.length; for (var i = 0; i < len; i++) { - _children.add(children[i]); + mappedChildren.add(children[i]); if (i < len - 1) { double pad = 15; if (children[i + 1].runtimeType.toString() == 'ConfigButtonItem') { pad = 0; } - _children.add( + mappedChildren.add( Padding( - child: Divider(height: 1, color: Utils.configSectionBorder(context)), padding: EdgeInsets.only(left: pad), + child: Divider(height: 1, color: Utils.configSectionBorder(context)), ), ); } @@ -45,7 +44,7 @@ class ConfigSection extends StatelessWidget { border: Border(top: border, bottom: border), color: Utils.configItemBackground(context), ), - child: Column(children: _children), + child: Column(children: mappedChildren), ), ], ); diff --git a/lib/components/config/ConfigTextItem.dart b/lib/components/config/ConfigTextItem.dart index 577f559..663e4ed 100644 --- a/lib/components/config/ConfigTextItem.dart +++ b/lib/components/config/ConfigTextItem.dart @@ -2,11 +2,11 @@ import 'package:flutter/cupertino.dart'; class ConfigTextItem extends StatelessWidget { const ConfigTextItem({ - Key? key, + super.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 61fd999..15f3a17 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -9,14 +9,13 @@ import 'package:flutter_platform_widgets/flutter_platform_widgets.dart'; import 'package:mobile_nebula/screens/MainScreen.dart'; import 'package:mobile_nebula/screens/EnrollmentScreen.dart'; import 'package:mobile_nebula/services/settings.dart'; -import 'package:flutter_web_plugins/url_strategy.dart'; import 'package:mobile_nebula/services/theme.dart'; import 'package:mobile_nebula/services/utils.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; +import 'package:flutter_web_plugins/url_strategy.dart'; Future main() async { usePathUrlStrategy(); - var settings = Settings(); if (settings.trackErrors) { await SentryFlutter.init((options) { @@ -34,12 +33,16 @@ Future main() async { //TODO: EventChannel might be better than the stream controller we are using now class Main extends StatelessWidget { + const Main({super.key}); + // This widget is the root of your application. @override Widget build(BuildContext context) => App(); } class App extends StatefulWidget { + const App({super.key}); + @override _AppState createState() => _AppState(); } @@ -98,15 +101,16 @@ class _AppState extends State { ], title: 'Nebula', material: (_, __) { - return new MaterialAppData( + return 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) { + print(settings); if (settings.name == '/') { - return platformPageRoute(context: context, builder: (context) => MainScreen(this.dnEnrolled)); + return platformPageRoute(context: context, builder: (context) => MainScreen(dnEnrolled)); } final uri = Uri.parse(settings.name!); @@ -116,7 +120,7 @@ class _AppState extends State { context: context, builder: (context) => - EnrollmentScreen(code: EnrollmentScreen.parseCode(settings.name!), stream: this.dnEnrolled), + EnrollmentScreen(code: EnrollmentScreen.parseCode(settings.name!), stream: dnEnrolled), ); } diff --git a/lib/models/Certificate.dart b/lib/models/Certificate.dart index 7d9a2a0..61571b0 100644 --- a/lib/models/Certificate.dart +++ b/lib/models/Certificate.dart @@ -3,9 +3,7 @@ class CertificateInfo { String? rawCert; CertificateValidity? validity; - CertificateInfo.debug({this.rawCert = ""}) - : this.cert = Certificate.debug(), - this.validity = CertificateValidity.debug(); + CertificateInfo.debug({this.rawCert = ""}) : cert = Certificate.debug(), validity = CertificateValidity.debug(); CertificateInfo.fromJson(Map json) : cert = Certificate.fromJson(json['Cert']), @@ -24,7 +22,7 @@ class Certificate { String fingerprint; String signature; - Certificate.debug() : this.details = CertificateDetails.debug(), this.fingerprint = "DEBUG", this.signature = "DEBUG"; + Certificate.debug() : details = CertificateDetails.debug(), fingerprint = "DEBUG", signature = "DEBUG"; Certificate.fromJson(Map json) : details = CertificateDetails.fromJson(json['details']), @@ -44,7 +42,7 @@ class CertificateDetails { String issuer; CertificateDetails.debug() - : this.name = "DEBUG", + : name = "DEBUG", notBefore = DateTime.now(), notAfter = DateTime.now(), publicKey = "", @@ -70,7 +68,7 @@ class CertificateValidity { bool valid; String reason; - CertificateValidity.debug() : this.valid = true, this.reason = ""; + CertificateValidity.debug() : valid = true, reason = ""; CertificateValidity.fromJson(Map json) : valid = json['Valid'], reason = json['Reason']; } diff --git a/lib/models/Site.dart b/lib/models/Site.dart index 861b100..0f81ed6 100644 --- a/lib/models/Site.dart +++ b/lib/models/Site.dart @@ -15,7 +15,7 @@ class Site { late EventChannel _updates; /// Signals that something about this site has changed. onError is called with an error string if there was an error - StreamController _change = StreamController.broadcast(); + final StreamController _change = StreamController.broadcast(); // Identifiers late String name; @@ -139,25 +139,25 @@ class Site { _updateFromJson(String json) { var decoded = Site._fromJson(jsonDecode(json)); - this.name = decoded["name"]; - this.id = decoded['id']; // TODO update EventChannel - this.staticHostmap = decoded['staticHostmap']; - this.ca = decoded['ca']; - this.certInfo = decoded['certInfo']; - this.lhDuration = decoded['lhDuration']; - this.port = decoded['port']; - this.cipher = decoded['cipher']; - this.sortKey = decoded['sortKey']; - this.mtu = decoded['mtu']; - this.connected = decoded['connected']; - this.status = decoded['status']; - this.logFile = decoded['logFile']; - this.logVerbosity = decoded['logVerbosity']; - this.errors = decoded['errors']; - this.unsafeRoutes = decoded['unsafeRoutes']; - this.managed = decoded['managed']; - this.rawConfig = decoded['rawConfig']; - this.lastManagedUpdate = decoded['lastManagedUpdate']; + name = decoded["name"]; + id = decoded['id']; // TODO update EventChannel + staticHostmap = decoded['staticHostmap']; + ca = decoded['ca']; + certInfo = decoded['certInfo']; + lhDuration = decoded['lhDuration']; + port = decoded['port']; + cipher = decoded['cipher']; + sortKey = decoded['sortKey']; + mtu = decoded['mtu']; + connected = decoded['connected']; + status = decoded['status']; + logFile = decoded['logFile']; + logVerbosity = decoded['logVerbosity']; + errors = decoded['errors']; + unsafeRoutes = decoded['unsafeRoutes']; + managed = decoded['managed']; + rawConfig = decoded['rawConfig']; + lastManagedUpdate = decoded['lastManagedUpdate']; } static _fromJson(Map json) { @@ -169,15 +169,15 @@ class Site { List rawUnsafeRoutes = json['unsafeRoutes']; List unsafeRoutes = []; - rawUnsafeRoutes.forEach((val) { + for (var val in rawUnsafeRoutes) { unsafeRoutes.add(UnsafeRoute.fromJson(val)); - }); + } List rawCA = json['ca']; List ca = []; - rawCA.forEach((val) { + for (var val in rawCA) { ca.add(CertificateInfo.fromJson(val)); - }); + } CertificateInfo? certInfo; if (json['cert'] != null) { @@ -186,9 +186,9 @@ class Site { List rawErrors = json["errors"]; List errors = []; - rawErrors.forEach((error) { + for (var error in rawErrors) { errors.add(error); - }); + } return { "name": json["name"], @@ -295,9 +295,9 @@ class Site { List f = jsonDecode(ret); List hosts = []; - f.forEach((v) { + for (var v in f) { hosts.add(HostInfo.fromJson(v)); - }); + } return hosts; } on PlatformException catch (err) { @@ -317,9 +317,9 @@ class Site { List f = jsonDecode(ret); List hosts = []; - f.forEach((v) { + for (var v in f) { hosts.add(HostInfo.fromJson(v)); - }); + } return hosts; } on PlatformException catch (err) { @@ -331,7 +331,7 @@ class Site { Future>> listAllHostmaps() async { try { - var res = await Future.wait([this.listHostmap(), this.listPendingHostmap()]); + var res = await Future.wait([listHostmap(), listPendingHostmap()]); return {"active": res[0], "pending": res[1]}; } on PlatformException catch (err) { throw err.details ?? err.message ?? err.toString(); diff --git a/lib/models/StaticHosts.dart b/lib/models/StaticHosts.dart index 8c853fa..489ee9a 100644 --- a/lib/models/StaticHosts.dart +++ b/lib/models/StaticHosts.dart @@ -10,9 +10,9 @@ class StaticHost { var list = json['destinations'] as List; var result = []; - list.forEach((item) { + for (var item in list) { result.add(IPAndPort.fromString(item)); - }); + } return StaticHost(lighthouse: json['lighthouse'], destinations: result); } diff --git a/lib/screens/AboutScreen.dart b/lib/screens/AboutScreen.dart index 1f4655c..1c7c8ba 100644 --- a/lib/screens/AboutScreen.dart +++ b/lib/screens/AboutScreen.dart @@ -10,7 +10,7 @@ import 'package:mobile_nebula/services/utils.dart'; import 'package:package_info_plus/package_info_plus.dart'; class AboutScreen extends StatefulWidget { - const AboutScreen({Key? key}) : super(key: key); + const AboutScreen({super.key}); @override _AboutScreenState createState() => _AboutScreenState(); diff --git a/lib/screens/EnrollmentScreen.dart b/lib/screens/EnrollmentScreen.dart index a7e517d..41c1fde 100644 --- a/lib/screens/EnrollmentScreen.dart +++ b/lib/screens/EnrollmentScreen.dart @@ -49,6 +49,7 @@ class _EnrollmentScreenState extends State { static const platform = MethodChannel('net.defined.mobileNebula/NebulaVpnService'); + @override void initState() { code = widget.code; super.initState(); @@ -94,27 +95,30 @@ class _EnrollmentScreenState extends State { } else { // No code, show the error child = Padding( + padding: EdgeInsets.only(top: 20), 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), ); } - } else if (this.error != null) { + } else if (error != null) { // Error while enrolling, display it child = Center( child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, children: [ Padding( + padding: EdgeInsets.symmetric(horizontal: 16, vertical: 20), 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( + padding: EdgeInsets.symmetric(horizontal: 16, vertical: 10), child: SelectableText.rich( TextSpan( children: [ @@ -134,22 +138,19 @@ class _EnrollmentScreenState extends State { ], ), ), - padding: EdgeInsets.symmetric(horizontal: 16, vertical: 10), ), Container( - child: Padding(child: SelectableText(this.error!), padding: EdgeInsets.all(16)), color: Theme.of(context).colorScheme.errorContainer, + child: Padding(padding: EdgeInsets.all(16), child: SelectableText(error!)), ), ], - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, ), ); - } else if (this.enrolled) { + } else if (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)), ); } else { // Have a code and actively enrolling @@ -157,7 +158,7 @@ class _EnrollmentScreenState extends State { child = Center( child: Column( children: [ - Padding(child: Text('Contacting DN for enrollment'), padding: EdgeInsets.only(bottom: 25)), + Padding(padding: EdgeInsets.only(bottom: 25), child: Text('Contacting DN for enrollment')), PlatformCircularProgressIndicator( cupertino: (_, __) { return CupertinoProgressIndicatorData(radius: 50); @@ -168,11 +169,11 @@ class _EnrollmentScreenState extends State { ); } - return SimplePage(title: Text('Enroll with Managed Nebula'), child: child, alignment: alignment); + return SimplePage(title: Text('Enroll with Managed Nebula'), alignment: alignment, child: child); } Widget _codeEntry() { - final GlobalKey _formKey = GlobalKey(); + final GlobalKey formKey = GlobalKey(); String? validator(String? value) { if (value == null || value.isEmpty) { @@ -182,7 +183,7 @@ class _EnrollmentScreenState extends State { } Future onSubmit() async { - final bool isValid = _formKey.currentState?.validate() ?? false; + final bool isValid = formKey.currentState?.validate() ?? false; if (!isValid) { return; } @@ -205,14 +206,14 @@ class _EnrollmentScreenState extends State { ), ); - final form = Form(key: _formKey, child: Platform.isAndroid ? input : ConfigSection(children: [input])); + 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(onPressed: onSubmit, child: Text('Submit')))]), ), ], ); diff --git a/lib/screens/HostInfoScreen.dart b/lib/screens/HostInfoScreen.dart index 3a899e9..3408b23 100644 --- a/lib/screens/HostInfoScreen.dart +++ b/lib/screens/HostInfoScreen.dart @@ -15,14 +15,14 @@ import 'package:pull_to_refresh/pull_to_refresh.dart'; class HostInfoScreen extends StatefulWidget { const HostInfoScreen({ - Key? key, + super.key, required this.hostInfo, required this.isLighthouse, required this.pending, this.onChanged, required this.site, required this.supportsQRScanning, - }) : super(key: key); + }); final bool isLighthouse; final bool pending; @@ -108,7 +108,7 @@ class _HostInfoScreenState extends State { } Widget _buildRemotes() { - if (hostInfo.remoteAddresses.length == 0) { + if (hostInfo.remoteAddresses.isEmpty) { return ConfigSection( label: 'REMOTES', children: [ConfigItem(content: Text('No remote addresses yet'), labelWidth: 0)], @@ -124,7 +124,7 @@ class _HostInfoScreenState extends State { final double ipWidth = Utils.textSize("000.000.000.000:000000", CupertinoTheme.of(context).textTheme.textStyle).width; - hostInfo.remoteAddresses.forEach((remoteObj) { + for (var remoteObj in hostInfo.remoteAddresses) { String remote = remoteObj.toString(); items.add( ConfigCheckboxItem( @@ -148,9 +148,9 @@ class _HostInfoScreenState extends State { }, ), ); - }); + } - return ConfigSection(label: items.length > 0 ? 'Tap to change the active address' : null, children: items); + return ConfigSection(label: items.isNotEmpty ? 'Tap to change the active address' : null, children: items); } Widget _buildStaticRemotes() { @@ -159,7 +159,7 @@ class _HostInfoScreenState extends State { final double ipWidth = Utils.textSize("000.000.000.000:000000", CupertinoTheme.of(context).textTheme.textStyle).width; - hostInfo.remoteAddresses.forEach((remoteObj) { + for (var remoteObj in hostInfo.remoteAddresses) { String remote = remoteObj.toString(); items.add( ConfigCheckboxItem( @@ -169,9 +169,9 @@ class _HostInfoScreenState extends State { checked: currentRemote == remote, ), ); - }); + } - return ConfigSection(label: items.length > 0 ? 'REMOTES' : null, children: items); + return ConfigSection(label: items.isNotEmpty ? 'REMOTES' : null, children: items); } Widget _buildClose() { diff --git a/lib/screens/MainScreen.dart b/lib/screens/MainScreen.dart index a61fe5f..bfd82d0 100644 --- a/lib/screens/MainScreen.dart +++ b/lib/screens/MainScreen.dart @@ -60,7 +60,7 @@ MAIH7gzreMGgrH/yR6rZpIHR3DxJ3E0aHtEI }; class MainScreen extends StatefulWidget { - const MainScreen(this.dnEnrollStream, {Key? key}) : super(key: key); + const MainScreen(this.dnEnrollStream, {super.key}); final StreamController dnEnrollStream; @@ -115,8 +115,8 @@ class _MainScreenState extends State { if (kDebugMode) { debugSite = Row( - children: [_debugSave(badDebugSave), _debugSave(goodDebugSave), _debugClearKeys()], mainAxisAlignment: MainAxisAlignment.center, + children: [_debugSave(badDebugSave), _debugSave(goodDebugSave), _debugClearKeys()], ); } @@ -168,12 +168,12 @@ class _MainScreenState extends State { if (error != null) { return Center( child: Padding( + padding: EdgeInsets.symmetric(vertical: 0, horizontal: 10), child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: error!, ), - padding: EdgeInsets.symmetric(vertical: 0, horizontal: 10), ), ); } @@ -202,12 +202,12 @@ class _MainScreenState extends State { } Widget _buildSites() { - if (sites == null || sites!.length == 0) { + if (sites == null || sites!.isEmpty) { return _buildNoSites(); } List items = []; - sites!.forEach((site) { + for (var site in sites!) { items.add( SiteItem( key: Key(site.id), @@ -223,7 +223,7 @@ class _MainScreenState extends State { }, ), ); - }); + } Widget child = ReorderableListView( shrinkWrap: true, @@ -260,7 +260,7 @@ class _MainScreenState extends State { ); if (Platform.isIOS) { - child = CupertinoTheme(child: child, data: CupertinoTheme.of(context)); + child = CupertinoTheme(data: CupertinoTheme.of(context), child: child); } // The theme here is to remove the hardcoded canvas border reordering forces on us diff --git a/lib/screens/SettingsScreen.dart b/lib/screens/SettingsScreen.dart index c9b465f..3c4a1a3 100644 --- a/lib/screens/SettingsScreen.dart +++ b/lib/screens/SettingsScreen.dart @@ -27,7 +27,7 @@ class _SettingsScreenState extends State { void initState() { //TODO: we need to unregister on dispose? settings.onChange().listen((_) { - if (this.mounted) { + if (mounted) { setState(() {}); } }); diff --git a/lib/screens/SiteDetailScreen.dart b/lib/screens/SiteDetailScreen.dart index d99505d..601b485 100644 --- a/lib/screens/SiteDetailScreen.dart +++ b/lib/screens/SiteDetailScreen.dart @@ -23,8 +23,7 @@ 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({super.key, required this.site, this.onChanged, required this.supportsQRScanning}); final Site site; final Function? onChanged; @@ -113,19 +112,19 @@ class _SiteDetailScreenState extends State { } Widget _buildErrors() { - if (site.errors.length == 0) { + if (site.errors.isEmpty) { return Container(); } List items = []; - site.errors.forEach((error) { + for (var error in site.errors) { items.add( ConfigItem( labelWidth: 0, content: Padding(padding: EdgeInsets.symmetric(vertical: 10), child: SelectableText(error)), ), ); - }); + } return ConfigSection( label: 'ERRORS', @@ -166,7 +165,7 @@ class _SiteDetailScreenState extends State { Switch.adaptive( value: widget.site.connected, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - onChanged: widget.site.errors.length > 0 && !widget.site.connected ? null : handleChange, + onChanged: widget.site.errors.isNotEmpty && !widget.site.connected ? null : handleChange, ), ], ), diff --git a/lib/screens/SiteLogsScreen.dart b/lib/screens/SiteLogsScreen.dart index a3269ed..630b9c3 100644 --- a/lib/screens/SiteLogsScreen.dart +++ b/lib/screens/SiteLogsScreen.dart @@ -15,7 +15,7 @@ import 'package:pull_to_refresh/pull_to_refresh.dart'; import '../components/SiteTitle.dart'; class SiteLogsScreen extends StatefulWidget { - const SiteLogsScreen({Key? key, required this.site}) : super(key: key); + const SiteLogsScreen({super.key, required this.site}); final Site site; @@ -59,6 +59,7 @@ class _SiteLogsScreenState extends State { refreshController.loadComplete(); }, refreshController: refreshController, + bottomBar: _buildBottomBar(), child: Container( padding: EdgeInsets.all(5), constraints: logBoxConstraints(context), @@ -75,7 +76,6 @@ class _SiteLogsScreenState extends State { }, style: TextStyle(fontFamily: 'RobotoMono', fontSize: 14)), ), ), - bottomBar: _buildBottomBar(), ); } diff --git a/lib/screens/SiteTunnelsScreen.dart b/lib/screens/SiteTunnelsScreen.dart index 51ba6df..114bfa6 100644 --- a/lib/screens/SiteTunnelsScreen.dart +++ b/lib/screens/SiteTunnelsScreen.dart @@ -11,13 +11,13 @@ import 'package:pull_to_refresh/pull_to_refresh.dart'; class SiteTunnelsScreen extends StatefulWidget { const SiteTunnelsScreen({ - Key? key, + super.key, required this.site, required this.tunnels, required this.pending, required this.onChanged, required this.supportsQRScanning, - }) : super(key: key); + }); final Site site; final List tunnels; @@ -77,7 +77,7 @@ class _SiteTunnelsScreenState extends State { ), ), label: Row( - children: [Padding(child: icon, padding: EdgeInsets.only(right: 10)), Text(hostInfo.vpnIp)], + children: [Padding(padding: EdgeInsets.only(right: 10), child: icon), Text(hostInfo.vpnIp)], ), labelWidth: ipWidth, content: Container(alignment: Alignment.centerRight, child: Text(hostInfo.cert?.details.name ?? "")), @@ -85,7 +85,7 @@ class _SiteTunnelsScreenState extends State { }).toList(); final Widget child = switch (children.length) { - 0 => Center(child: Padding(child: Text('No tunnels to show'), padding: EdgeInsets.only(top: 30))), + 0 => Center(child: Padding(padding: EdgeInsets.only(top: 30), child: Text('No tunnels to show'))), _ => ConfigSection(children: children), }; diff --git a/lib/screens/siteConfig/AddCertificateScreen.dart b/lib/screens/siteConfig/AddCertificateScreen.dart index c803fc4..3dde5ec 100644 --- a/lib/screens/siteConfig/AddCertificateScreen.dart +++ b/lib/screens/siteConfig/AddCertificateScreen.dart @@ -26,13 +26,13 @@ class CertificateResult { class AddCertificateScreen extends StatefulWidget { const AddCertificateScreen({ - Key? key, + super.key, this.onSave, this.onReplace, required this.pubKey, required this.privKey, required this.supportsQRScanning, - }) : super(key: key); + }); // onSave will pop a new CertificateDetailsScreen. // If onSave is null, onReplace must be set. @@ -223,7 +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) => ScanQRScreen()), ); if (result != null) { _addCertEntry(result); @@ -245,7 +245,7 @@ class _AddCertificateScreenState extends State { var rawCerts = await platform.invokeMethod("nebula.parseCerts", {"certs": rawCert}); List certs = jsonDecode(rawCerts); - if (certs.length > 0) { + if (certs.isNotEmpty) { var tryCertInfo = CertificateInfo.fromJson(certs.first); if (tryCertInfo.cert.details.isCa) { return Utils.popError( diff --git a/lib/screens/siteConfig/AdvancedScreen.dart b/lib/screens/siteConfig/AdvancedScreen.dart index fa886c6..0ebc040 100644 --- a/lib/screens/siteConfig/AdvancedScreen.dart +++ b/lib/screens/siteConfig/AdvancedScreen.dart @@ -40,7 +40,7 @@ class Advanced { } class AdvancedScreen extends StatefulWidget { - const AdvancedScreen({Key? key, required this.site, required this.onSave}) : super(key: key); + const AdvancedScreen({super.key, required this.site, required this.onSave}); final Site site; final ValueChanged onSave; @@ -85,7 +85,7 @@ class _AdvancedScreenState extends State { //TODO: Auto select on focus? content: widget.site.managed - ? Text(settings.lhDuration.toString() + " seconds", textAlign: TextAlign.right) + ? Text("${settings.lhDuration} seconds", textAlign: TextAlign.right) : PlatformTextFormField( initialValue: settings.lhDuration.toString(), keyboardType: TextInputType.number, diff --git a/lib/screens/siteConfig/CAListScreen.dart b/lib/screens/siteConfig/CAListScreen.dart index 689af17..b37de67 100644 --- a/lib/screens/siteConfig/CAListScreen.dart +++ b/lib/screens/siteConfig/CAListScreen.dart @@ -18,7 +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({super.key, required this.cas, this.onSave, required this.supportsQRScanning}); final List cas; final ValueChanged>? onSave; @@ -39,9 +39,9 @@ class _CAListScreenState extends State { @override void initState() { - widget.cas.forEach((ca) { + for (var ca in widget.cas) { cas[ca.cert.fingerprint] = ca; - }); + } super.initState(); } @@ -51,7 +51,7 @@ class _CAListScreenState extends State { List items = []; final caItems = _buildCAs(); - if (caItems.length > 0) { + if (caItems.isNotEmpty) { items.add(ConfigSection(children: caItems)); } @@ -115,14 +115,14 @@ class _CAListScreenState extends State { var ignored = 0; List certs = jsonDecode(rawCerts); - certs.forEach((rawCert) { + for (var rawCert in certs) { final info = CertificateInfo.fromJson(rawCert); if (!info.cert.details.isCa) { ignored++; - return; + continue; } cas[info.cert.fingerprint] = info; - }); + } if (ignored > 0) { error = 'One or more certificates were ignored because they were not certificate authorities.'; @@ -236,7 +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) => ScanQRScreen()), ); if (result != null) { _addCAEntry(result, (err) { diff --git a/lib/screens/siteConfig/CertificateDetailsScreen.dart b/lib/screens/siteConfig/CertificateDetailsScreen.dart index e601fe2..9a9c9b5 100644 --- a/lib/screens/siteConfig/CertificateDetailsScreen.dart +++ b/lib/screens/siteConfig/CertificateDetailsScreen.dart @@ -11,7 +11,7 @@ import 'package:mobile_nebula/services/utils.dart'; /// Displays the details of a CertificateInfo object. Respects incomplete objects (missing validity or rawCert) class CertificateDetailsScreen extends StatefulWidget { const CertificateDetailsScreen({ - Key? key, + super.key, required this.certInfo, this.onDelete, this.onSave, @@ -19,7 +19,7 @@ class CertificateDetailsScreen extends StatefulWidget { this.pubKey, this.privKey, required this.supportsQRScanning, - }) : super(key: key); + }); final CertificateInfo certInfo; @@ -120,19 +120,19 @@ class _CertificateDetailsScreenState extends State { Widget _buildFilters() { List items = []; - if (certInfo.cert.details.groups.length > 0) { + if (certInfo.cert.details.groups.isNotEmpty) { items.add(ConfigItem(label: Text('Groups'), content: SelectableText(certInfo.cert.details.groups.join(', ')))); } - if (certInfo.cert.details.ips.length > 0) { + if (certInfo.cert.details.ips.isNotEmpty) { items.add(ConfigItem(label: Text('IPs'), content: SelectableText(certInfo.cert.details.ips.join(', ')))); } - if (certInfo.cert.details.subnets.length > 0) { + if (certInfo.cert.details.subnets.isNotEmpty) { items.add(ConfigItem(label: Text('Subnets'), content: SelectableText(certInfo.cert.details.subnets.join(', ')))); } - return items.length > 0 + return items.isNotEmpty ? ConfigSection(label: certInfo.cert.details.isCa ? 'FILTERS' : 'DETAILS', children: items) : Container(); } diff --git a/lib/screens/siteConfig/CipherScreen.dart b/lib/screens/siteConfig/CipherScreen.dart index fdcc399..1f4715c 100644 --- a/lib/screens/siteConfig/CipherScreen.dart +++ b/lib/screens/siteConfig/CipherScreen.dart @@ -6,7 +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({super.key, required this.cipher, required this.onSave}); final String cipher; final ValueChanged onSave; diff --git a/lib/screens/siteConfig/LogVerbosityScreen.dart b/lib/screens/siteConfig/LogVerbosityScreen.dart index bf1ca07..ec66693 100644 --- a/lib/screens/siteConfig/LogVerbosityScreen.dart +++ b/lib/screens/siteConfig/LogVerbosityScreen.dart @@ -6,7 +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({super.key, required this.verbosity, required this.onSave}); final String verbosity; final ValueChanged onSave; diff --git a/lib/screens/siteConfig/RenderedConfigScreen.dart b/lib/screens/siteConfig/RenderedConfigScreen.dart index fb6de2d..c31d31a 100644 --- a/lib/screens/siteConfig/RenderedConfigScreen.dart +++ b/lib/screens/siteConfig/RenderedConfigScreen.dart @@ -7,7 +7,7 @@ class RenderedConfigScreen extends StatelessWidget { final String config; final String name; - RenderedConfigScreen({Key? key, required this.config, required this.name}) : super(key: key); + const RenderedConfigScreen({super.key, required this.config, required this.name}); @override Widget build(BuildContext context) { diff --git a/lib/screens/siteConfig/ScanQRScreen.dart b/lib/screens/siteConfig/ScanQRScreen.dart index 0a04d97..9dfbb3a 100644 --- a/lib/screens/siteConfig/ScanQRScreen.dart +++ b/lib/screens/siteConfig/ScanQRScreen.dart @@ -2,6 +2,8 @@ import 'package:flutter/material.dart'; import 'package:mobile_scanner/mobile_scanner.dart'; class ScanQRScreen extends StatefulWidget { + const ScanQRScreen({super.key}); + @override State createState() => _ScanQRScreenState(); } diff --git a/lib/screens/siteConfig/SiteConfigScreen.dart b/lib/screens/siteConfig/SiteConfigScreen.dart index 1e274eb..0a406cc 100644 --- a/lib/screens/siteConfig/SiteConfigScreen.dart +++ b/lib/screens/siteConfig/SiteConfigScreen.dart @@ -23,8 +23,7 @@ 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({super.key, this.site, required this.onSave, required this.supportsQRScanning}); final Site? site; @@ -105,7 +104,7 @@ class _SiteConfigScreenState extends State { Widget _debugConfig() { var data = ""; try { - final encoder = new JsonEncoder.withIndent(' '); + final encoder = JsonEncoder.withIndent(' '); data = encoder.convert(site); } catch (err) { data = err.toString(); @@ -162,13 +161,13 @@ class _SiteConfigScreenState extends State { final certError = site.certInfo == null || site.certInfo!.validity == null || !site.certInfo!.validity!.valid; var caError = false; if (!site.managed) { - caError = site.ca.length == 0; + caError = site.ca.isEmpty; if (!caError) { - site.ca.forEach((ca) { + for (var ca in site.ca) { if (ca.validity == null || !ca.validity!.valid) { caError = true; } - }); + } } } @@ -183,8 +182,8 @@ class _SiteConfigScreenState extends State { children: [ certError ? 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), ) : Container(), certError ? Text('Needs attention') : Text(site.certInfo?.cert.details.name ?? 'Unknown certificate'), @@ -234,8 +233,8 @@ class _SiteConfigScreenState extends State { 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), ) : Container(), caError ? Text('Needs attention') : Text(Utils.itemCountFormat(site.ca.length)), @@ -273,13 +272,13 @@ class _SiteConfigScreenState extends State { alignment: WrapAlignment.end, crossAxisAlignment: WrapCrossAlignment.center, children: [ - site.staticHostmap.length == 0 + site.staticHostmap.isEmpty ? 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), ) : Container(), - site.staticHostmap.length == 0 + site.staticHostmap.isEmpty ? Text('Needs attention') : Text(Utils.itemCountFormat(site.staticHostmap.length)), ], diff --git a/lib/screens/siteConfig/StaticHostmapScreen.dart b/lib/screens/siteConfig/StaticHostmapScreen.dart index 1e06009..c8c1bff 100644 --- a/lib/screens/siteConfig/StaticHostmapScreen.dart +++ b/lib/screens/siteConfig/StaticHostmapScreen.dart @@ -21,14 +21,13 @@ class _IPAndPort { class StaticHostmapScreen extends StatefulWidget { StaticHostmapScreen({ - Key? key, + super.key, this.nebulaIp = '', destinations, this.lighthouse = false, this.onDelete, required this.onSave, - }) : this.destinations = destinations ?? [], - super(key: key); + }) : destinations = destinations ?? []; final List destinations; final String nebulaIp; @@ -51,11 +50,11 @@ class _StaticHostmapScreenState extends State { _nebulaIp = widget.nebulaIp; _lighthouse = widget.lighthouse; _destinations = {}; - widget.destinations.forEach((dest) { + for (var dest in widget.destinations) { _destinations[UniqueKey()] = _IPAndPort(focusNode: FocusNode(), destination: dest); - }); + } - if (_destinations.length == 0) { + if (_destinations.isEmpty) { _addDestination(); } diff --git a/lib/screens/siteConfig/StaticHostsScreen.dart b/lib/screens/siteConfig/StaticHostsScreen.dart index a442d8b..49c0c5d 100644 --- a/lib/screens/siteConfig/StaticHostsScreen.dart +++ b/lib/screens/siteConfig/StaticHostsScreen.dart @@ -22,7 +22,7 @@ class _Hostmap { } class StaticHostsScreen extends StatefulWidget { - const StaticHostsScreen({Key? key, required this.hostmap, required this.onSave}) : super(key: key); + const StaticHostsScreen({super.key, required this.hostmap, required this.onSave}); final Map hostmap; final ValueChanged>? onSave; @@ -32,7 +32,7 @@ class StaticHostsScreen extends StatefulWidget { } class _StaticHostsScreenState extends State { - Map _hostmap = {}; + final Map _hostmap = {}; bool changed = false; @override @@ -80,17 +80,17 @@ class _StaticHostsScreenState extends State { label: Row( children: [ Padding( + padding: EdgeInsets.only(right: 10), 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), + content: Text('${host.destinations.length} items', textAlign: TextAlign.end), onPressed: () { Utils.openPage(context, (context) { return StaticHostmapScreen( diff --git a/lib/screens/siteConfig/UnsafeRouteScreen.dart b/lib/screens/siteConfig/UnsafeRouteScreen.dart index 9b16120..2d31107 100644 --- a/lib/screens/siteConfig/UnsafeRouteScreen.dart +++ b/lib/screens/siteConfig/UnsafeRouteScreen.dart @@ -10,7 +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({super.key, required this.route, required this.onSave, this.onDelete}); final UnsafeRoute route; final ValueChanged onSave; diff --git a/lib/screens/siteConfig/UnsafeRoutesScreen.dart b/lib/screens/siteConfig/UnsafeRoutesScreen.dart index 48c51f6..1621ec6 100644 --- a/lib/screens/siteConfig/UnsafeRoutesScreen.dart +++ b/lib/screens/siteConfig/UnsafeRoutesScreen.dart @@ -8,7 +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({super.key, required this.unsafeRoutes, required this.onSave}); final List unsafeRoutes; final ValueChanged>? onSave; @@ -24,9 +24,9 @@ class _UnsafeRoutesScreenState extends State { @override void initState() { unsafeRoutes = {}; - widget.unsafeRoutes.forEach((route) { + for (var route in widget.unsafeRoutes) { unsafeRoutes[UniqueKey()] = route; - }); + } super.initState(); } diff --git a/lib/services/settings.dart b/lib/services/settings.dart index 67c802d..5144c3e 100644 --- a/lib/services/settings.dart +++ b/lib/services/settings.dart @@ -10,8 +10,8 @@ bool DEFAULT_TRACK_ERRORS = true; class Settings { final _storage = Storage(); - StreamController _change = StreamController.broadcast(); - var _settings = Map(); + final StreamController _change = StreamController.broadcast(); + var _settings = {}; bool get useSystemColors { return _getBool('systemDarkMode', true); diff --git a/lib/services/utils.dart b/lib/services/utils.dart index 32437c7..b625b7b 100644 --- a/lib/services/utils.dart +++ b/lib/services/utils.dart @@ -39,12 +39,12 @@ class Utils { Navigator.push(context, platformPageRoute(context: context, builder: pageToDisplayBuilder)); } - static String itemCountFormat(int items, {singleSuffix = "item", multiSuffix = "items"}) { + static String itemCountFormat(int items, {String singleSuffix = "item", String multiSuffix = "items"}) { if (items == 1) { - return items.toString() + " " + singleSuffix; + return "$items $singleSuffix"; } - return items.toString() + " " + multiSuffix; + return "$items $multiSuffix"; } /// Builds a simple leading widget that pops the current screen. @@ -79,9 +79,9 @@ 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'), ); } diff --git a/pubspec.lock b/pubspec.lock index f83d462..9682df0 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -142,6 +142,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" + url: "https://pub.dev" + source: hosted + version: "5.0.0" flutter_oss_licenses: dependency: "direct dev" description: @@ -180,7 +188,7 @@ packages: source: sdk version: "0.0.0" flutter_web_plugins: - dependency: transitive + dependency: "direct main" description: flutter source: sdk version: "0.0.0" @@ -264,6 +272,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.1" + lints: + dependency: transitive + description: + name: lints + sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 + url: "https://pub.dev" + source: hosted + version: "5.1.1" matcher: dependency: transitive description: @@ -321,7 +337,7 @@ packages: source: hosted version: "3.0.2" path: - dependency: transitive + dependency: "direct main" description: name: path sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" diff --git a/pubspec.yaml b/pubspec.yaml index e232d3a..8310147 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -19,6 +19,8 @@ environment: dependencies: flutter: sdk: flutter + flutter_web_plugins: + sdk: flutter # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. @@ -37,11 +39,13 @@ dependencies: sentry_flutter: ^8.9.0 sentry_dart_plugin: ^2.0.0 mobile_scanner: ^7.0.0-beta.3 + path: ^1.9.1 dev_dependencies: flutter_test: sdk: flutter flutter_oss_licenses: ^3.0.4 + flutter_lints: ^5.0.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec