mirror of
https://github.com/DefinedNet/mobile_nebula.git
synced 2025-09-07 11:46:03 +00:00
Compare commits
11 commits
768266b15f
...
4d21e1d6b8
Author | SHA1 | Date | |
---|---|---|---|
|
4d21e1d6b8 | ||
|
cbe2029378 | ||
|
a5b7d1c48c | ||
|
6e9d620911 | ||
|
d2be04fec8 | ||
|
f4228dd957 | ||
|
353f7ea954 | ||
|
9a3d288a16 | ||
|
2dee5305db | ||
|
fd991b9a02 | ||
|
2b844d27dd |
62 changed files with 497 additions and 445 deletions
42
.github/workflows/fluttercheck.yml
vendored
Normal file
42
.github/workflows/fluttercheck.yml
vendored
Normal file
|
@ -0,0 +1,42 @@
|
|||
name: Flutter check
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
paths:
|
||||
- '.github/workflows/fluttercheck.yml'
|
||||
- '**.dart'
|
||||
jobs:
|
||||
flutterfmt:
|
||||
name: Run flutter format
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install flutter
|
||||
uses: subosito/flutter-action@f2c4f6686ca8e8d6e6d0f28410eeef506ed66aff #v2.18.0
|
||||
with:
|
||||
flutter-version: '3.29.2'
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #4.2.2
|
||||
with:
|
||||
show-progress: false
|
||||
|
||||
- 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@f2c4f6686ca8e8d6e6d0f28410eeef506ed66aff #v2.18.0
|
||||
with:
|
||||
flutter-version: '3.29.2'
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #4.2.2
|
||||
with:
|
||||
show-progress: false
|
||||
|
||||
- name: Check linting
|
||||
run: dart fix --dry-run
|
27
.github/workflows/flutterfmt.yml
vendored
27
.github/workflows/flutterfmt.yml
vendored
|
@ -1,27 +0,0 @@
|
|||
name: Flutter format
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
paths:
|
||||
- '.github/workflows/flutterfmt.yml'
|
||||
- '.github/workflows/flutterfmt.sh'
|
||||
- '**.dart'
|
||||
jobs:
|
||||
flutterfmt:
|
||||
name: Run flutter format
|
||||
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 formating
|
||||
run: dart format -l120 lib/ --set-exit-if-changed --suppress-analytics --output none
|
4
.github/workflows/gofmt.yml
vendored
4
.github/workflows/gofmt.yml
vendored
|
@ -15,12 +15,12 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #4.2.2
|
||||
with:
|
||||
show-progress: false
|
||||
|
||||
- name: Set up Go 1.22
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 #5.3.0
|
||||
with:
|
||||
go-version: '1.22'
|
||||
cache-dependency-path: nebula/go.sum
|
||||
|
|
14
.github/workflows/release.yml
vendored
14
.github/workflows/release.yml
vendored
|
@ -12,26 +12,26 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #4.2.2
|
||||
with:
|
||||
show-progress: false
|
||||
fetch-depth: 25 # For sentry releases
|
||||
|
||||
- name: Set up Go 1.22
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 #5.3.0
|
||||
with:
|
||||
go-version: '1.22'
|
||||
cache-dependency-path: nebula/go.sum
|
||||
|
||||
- uses: actions/setup-java@v4
|
||||
- uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 #v4.7.0
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: '17'
|
||||
|
||||
- name: Install flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
uses: subosito/flutter-action@f2c4f6686ca8e8d6e6d0f28410eeef506ed66aff #v2.18.0
|
||||
with:
|
||||
flutter-version: '3.29.0'
|
||||
flutter-version: '3.29.2'
|
||||
|
||||
- name: Setup bundletool for APK generation
|
||||
uses: amyu/setup-bundletool@f7a6fdd8e04bb23d2fdf3c2f60c9257a6298a40a
|
||||
|
@ -101,7 +101,7 @@ jobs:
|
|||
fi
|
||||
|
||||
- name: Collect iOS artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 #4.6.1
|
||||
with:
|
||||
name: MobileNebula.ipa
|
||||
path: ios/MobileNebula.ipa
|
||||
|
@ -139,7 +139,7 @@ jobs:
|
|||
unzip -p build/app/outputs/apk/release/MobileNebula.apks universal.apk > build/app/outputs/apk/release/MobileNebula.apk
|
||||
|
||||
- name: Collect Android artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 #4.6.1
|
||||
with:
|
||||
name: MobileNebula.aab
|
||||
path: build/app/outputs/bundle/release/app-release.aab
|
||||
|
|
20
.github/workflows/smoke.yml
vendored
20
.github/workflows/smoke.yml
vendored
|
@ -13,25 +13,25 @@ jobs:
|
|||
runs-on: macos-latest
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #4.2.2
|
||||
with:
|
||||
show-progress: false
|
||||
|
||||
- name: Set up Go 1.22
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 #5.3.0
|
||||
with:
|
||||
go-version: '1.22'
|
||||
cache-dependency-path: nebula/go.sum
|
||||
|
||||
- uses: actions/setup-java@v4
|
||||
- uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 #v4.7.0
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: '17'
|
||||
|
||||
- name: Install flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
uses: subosito/flutter-action@f2c4f6686ca8e8d6e6d0f28410eeef506ed66aff #v2.18.0
|
||||
with:
|
||||
flutter-version: '3.29.0'
|
||||
flutter-version: '3.29.2'
|
||||
|
||||
- name: install dependencies
|
||||
env:
|
||||
|
@ -77,7 +77,7 @@ jobs:
|
|||
unzip -p build/app/outputs/apk/debug/app-debug.apks universal.apk > build/app/outputs/apk/debug/app-debug.apk
|
||||
- name: Collect debug apk
|
||||
if: github.ref == 'refs/heads/main'
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 #4.6.1
|
||||
with:
|
||||
name: MobileNebulaDebug.apk
|
||||
path: build/app/outputs/apk/debug/app-debug.apk
|
||||
|
@ -89,20 +89,20 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #4.2.2
|
||||
with:
|
||||
show-progress: false
|
||||
|
||||
- name: Set up Go 1.22
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 #5.3.0
|
||||
with:
|
||||
go-version: '1.22'
|
||||
cache-dependency-path: nebula/go.sum
|
||||
|
||||
- name: Install flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
uses: subosito/flutter-action@f2c4f6686ca8e8d6e6d0f28410eeef506ed66aff #v2.18.0
|
||||
with:
|
||||
flutter-version: '3.29.0'
|
||||
flutter-version: '3.29.2'
|
||||
|
||||
- name: install dependencies
|
||||
run: |
|
||||
|
|
2
.github/workflows/swiftfmt.yml
vendored
2
.github/workflows/swiftfmt.yml
vendored
|
@ -13,7 +13,7 @@ jobs:
|
|||
runs-on: macos-latest
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #4.2.2
|
||||
with:
|
||||
show-progress: false
|
||||
|
||||
|
|
41
DEVELOPING.md
Normal file
41
DEVELOPING.md
Normal file
|
@ -0,0 +1,41 @@
|
|||
**This project is not accepting PRs. These instructions are for employees of Defined Networking.**
|
||||
|
||||
## Setting up dev environment
|
||||
|
||||
Install all of the following things:
|
||||
|
||||
- [`xcode`](https://apps.apple.com/us/app/xcode/)
|
||||
- [`android-studio`](https://developer.android.com/studio)
|
||||
- [`flutter` 3.29.2](https://docs.flutter.dev/get-started/install)
|
||||
- [`gomobile`](https://pkg.go.dev/golang.org/x/mobile/cmd/gomobile)
|
||||
- [Flutter Android Studio Extension](https://docs.flutter.dev/get-started/editor?tab=androidstudio)
|
||||
|
||||
Ensure your path is set up correctly to execute flutter
|
||||
|
||||
Run `flutter doctor` and fix everything it complains before proceeding
|
||||
|
||||
*NOTE* on iOS, always open `Runner.xcworkspace` and NOT the `Runner.xccodeproj`
|
||||
|
||||
### Before first compile
|
||||
|
||||
- Copy `env.sh.example` and set it up for your machine
|
||||
- Ensure you have run `gomobile init`
|
||||
- In Android Studio, make sure you have the current NDK installed by going to Tools → SDK Manager, go to the SDK Tools tab, check the `Show package details` box, expand the NDK section and select `27.0.12077973` version.
|
||||
- Ensure you have downloaded an NDK via android studio, this is likely not the default one, and you need to check the
|
||||
`Show package details` box to select the correct version. The correct version comes from the error when you try and compile
|
||||
- Make sure you have `gem` installed with `sudo gem install`
|
||||
- If on macOS arm64, `sudo gem install ffi -- --enable-libffi-alloc`
|
||||
|
||||
If you are having issues with iOS pods, try blowing it all away! `cd ios && rm -rf Pods/ Podfile.lock && pod install --repo-update`
|
||||
|
||||
## Formatting
|
||||
|
||||
`dart format` can be used to format the code in `lib` and `test`. We use a line-length of 120 characters.
|
||||
|
||||
Use:
|
||||
```sh
|
||||
dart format lib/ test/ -l 120
|
||||
```
|
||||
|
||||
In Android Studio, set the line length using Preferences → Editor → Code Style → Dart → Line length, set it to 120. Enable auto-format with Preferences → Languages & Frameworks → Flutter → Format code on save.
|
||||
|
59
README.md
59
README.md
|
@ -2,61 +2,8 @@
|
|||
|
||||
[Play Store](https://play.google.com/store/apps/details?id=net.defined.mobile_nebula&hl=en_US&gl=US) | [App Store](https://apps.apple.com/us/app/mobile-nebula/id1509587936)
|
||||
|
||||
## Setting up dev environment
|
||||
Nebula is an overlay networking tool designed to be fast, secure, and scalable. Connect any number of hosts with on-demand, encrypted tunnels that work across any IP networks and without opening firewall ports.
|
||||
|
||||
Install all of the following things:
|
||||
Mobile Nebula brings this functionality to devices running Android and iOS.
|
||||
|
||||
- [`xcode`](https://apps.apple.com/us/app/xcode/)
|
||||
- [`android-studio`](https://developer.android.com/studio)
|
||||
- [`flutter` 3.29.0](https://docs.flutter.dev/get-started/install)
|
||||
- [`gomobile`](https://pkg.go.dev/golang.org/x/mobile/cmd/gomobile)
|
||||
- [Flutter Android Studio Extension](https://docs.flutter.dev/get-started/editor?tab=androidstudio)
|
||||
|
||||
Ensure your path is set up correctly to execute flutter
|
||||
|
||||
Run `flutter doctor` and fix everything it complains before proceeding
|
||||
|
||||
*NOTE* on iOS, always open `Runner.xcworkspace` and NOT the `Runner.xccodeproj`
|
||||
|
||||
### Before first compile
|
||||
|
||||
- Copy `env.sh.example` and set it up for your machine
|
||||
- Ensure you have run `gomobile init`
|
||||
- In Android Studio, make sure you have the current ndk installed by going to Tools -> SDK Manager, go to the SDK Tools tab, check the `Show package details` box, expand the NDK section and select `27.0.12077973` version.
|
||||
- Ensure you have downloaded an ndk via android studio, this is likely not the default one and you need to check the
|
||||
`Show package details` box to select the correct version. The correct version comes from the error when you try and compile
|
||||
- Make sure you have `gem` installed with `sudo gem install`
|
||||
- If on MacOS arm, `sudo gem install ffi -- --enable-libffi-alloc`
|
||||
|
||||
If you are having issues with iOS pods, try blowing it all away! `cd ios && rm -rf Pods/ Podfile.lock && pod install --repo-update`
|
||||
|
||||
# Formatting
|
||||
|
||||
`dart format` can be used to format the code in `lib` and `test`. We use a line-length of 120 characters.
|
||||
|
||||
Use:
|
||||
```sh
|
||||
dart format lib/ test/ -l 120
|
||||
```
|
||||
|
||||
In Android Studio, set the line length using Preferences -> Editor -> Code Style -> Dart -> Line length, set it to 120. Enable auto-format with Preferences -> Languages & Frameworks -> Flutter -> Format code on save.
|
||||
|
||||
`./swift-format.sh` can be used to format Swift code in the repo.
|
||||
|
||||
Once `swift-format` supports ignoring directories (<https://github.com/swiftlang/swift-format/issues/870>), we can move to a method of running it more like what <https://calebhearth.com/swift-format-github-action> describes.
|
||||
|
||||
# Release
|
||||
|
||||
Update `version` in `pubspec.yaml` to reflect this release, then
|
||||
|
||||
## Android
|
||||
|
||||
`flutter build appbundle`
|
||||
|
||||
This will create an android app bundle at `build/app/outputs/bundle/release/`
|
||||
|
||||
Upload the android bundle to the google play store https://play.google.com/apps/publish
|
||||
|
||||
## iOS
|
||||
|
||||
In xcode, Release -> Archive then follow the directions to upload to the app store. If you have issues, https://flutter.dev/docs/deployment/ios#create-a-build-archive
|
||||
This code is **source-available** meaning that the source is provided for viewing, but we are **not accepting contributions** and provide no license past what the [GitHub Terms of Service](https://docs.github.com/en/site-policy/github-terms/github-terms-of-service#5-license-grant-to-other-users) permit.
|
||||
|
|
16
RELEASING.md
Normal file
16
RELEASING.md
Normal file
|
@ -0,0 +1,16 @@
|
|||
# Releasing
|
||||
|
||||
## Prerelease
|
||||
|
||||
Push a git tag `v#.#.#-##`, e.g. `v0.5.1-0`, and `.github/release.yml` will build a draft release and publish it to iOS TestFlight and Android internal track.
|
||||
|
||||
`./swift-format.sh` can be used to format Swift code in the repo.
|
||||
|
||||
Once `swift-format` supports ignoring directories (<https://github.com/swiftlang/swift-format/issues/870>), we can move to a method of running it more like what <https://calebhearth.com/swift-format-github-action> describes.
|
||||
|
||||
## Release
|
||||
|
||||
1. Manually promote a prerelease build from TestFlight and Android internal track to the corresponding public app stores.
|
||||
2. Mark the associated draft release as published, removing the `-##` from it, ending with a release in the format `v#.#.#`, e.g. `v0.5.1`.
|
||||
3. Remove the old draft releases that will never be published.
|
||||
4. Add the notable changes to the app to the release summary, e.g.: <https://github.com/DefinedNet/mobile_nebula/releases/tag/v0.5.1>.
|
|
@ -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
|
||||
|
|
|
@ -2,13 +2,14 @@ import 'package:flutter/cupertino.dart';
|
|||
import 'package:flutter/services.dart';
|
||||
import 'package:mobile_nebula/components/SpecialTextField.dart';
|
||||
import 'package:mobile_nebula/models/CIDR.dart';
|
||||
|
||||
import '../services/utils.dart';
|
||||
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 +18,7 @@ class CIDRField extends StatefulWidget {
|
|||
this.textInputAction,
|
||||
this.ipController,
|
||||
this.bitsController,
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
final String ipHelp;
|
||||
final bool autoFocus;
|
||||
|
@ -52,57 +53,55 @@ class _CIDRFieldState extends State<CIDRField> {
|
|||
Widget build(BuildContext context) {
|
||||
var textStyle = CupertinoTheme.of(context).textTheme.textStyle;
|
||||
|
||||
return Container(
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.fromLTRB(6, 6, 2, 6),
|
||||
child: IPField(
|
||||
help: widget.ipHelp,
|
||||
ipOnly: true,
|
||||
textPadding: EdgeInsets.all(0),
|
||||
textInputAction: TextInputAction.next,
|
||||
textAlign: TextAlign.end,
|
||||
focusNode: widget.focusNode,
|
||||
nextFocusNode: bitsFocus,
|
||||
onChanged: (val) {
|
||||
if (widget.onChanged == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
cidr.ip = val;
|
||||
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,
|
||||
return Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.fromLTRB(6, 6, 2, 6),
|
||||
child: IPField(
|
||||
help: widget.ipHelp,
|
||||
ipOnly: true,
|
||||
textPadding: EdgeInsets.all(0),
|
||||
textInputAction: TextInputAction.next,
|
||||
textAlign: TextAlign.end,
|
||||
focusNode: widget.focusNode,
|
||||
nextFocusNode: bitsFocus,
|
||||
onChanged: (val) {
|
||||
if (widget.onChanged == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
cidr.bits = int.tryParse(val) ?? 0;
|
||||
cidr.ip = val;
|
||||
widget.onChanged!(cidr);
|
||||
},
|
||||
maxLength: 2,
|
||||
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
||||
textInputAction: widget.textInputAction ?? TextInputAction.done,
|
||||
placeholder: 'bits',
|
||||
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;
|
||||
}
|
||||
|
||||
cidr.bits = int.tryParse(val) ?? 0;
|
||||
widget.onChanged!(cidr);
|
||||
},
|
||||
maxLength: 2,
|
||||
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
||||
textInputAction: widget.textInputAction ?? TextInputAction.done,
|
||||
placeholder: 'bits',
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -6,21 +6,18 @@ import 'package:mobile_nebula/validators/ipValidator.dart';
|
|||
class CIDRFormField extends FormField<CIDR> {
|
||||
//TODO: onSaved, validator, auto-validate, enabled?
|
||||
CIDRFormField({
|
||||
Key? key,
|
||||
super.key,
|
||||
autoFocus = false,
|
||||
enableIPV6 = false,
|
||||
focusNode,
|
||||
nextFocusNode,
|
||||
ValueChanged<CIDR>? onChanged,
|
||||
FormFieldSetter<CIDR>? 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";
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -2,13 +2,14 @@ import 'package:flutter/cupertino.dart';
|
|||
import 'package:flutter/services.dart';
|
||||
import 'package:mobile_nebula/components/SpecialTextField.dart';
|
||||
import 'package:mobile_nebula/models/IPAndPort.dart';
|
||||
|
||||
import '../services/utils.dart';
|
||||
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 +21,7 @@ class IPAndPortField extends StatefulWidget {
|
|||
this.ipTextAlign,
|
||||
this.ipController,
|
||||
this.portController,
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
final String ipHelp;
|
||||
final bool ipOnly;
|
||||
|
@ -58,49 +59,47 @@ class _IPAndPortFieldState extends State<IPAndPortField> {
|
|||
Widget build(BuildContext context) {
|
||||
var textStyle = CupertinoTheme.of(context).textTheme.textStyle;
|
||||
|
||||
return Container(
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.fromLTRB(6, 6, 2, 6),
|
||||
child: IPField(
|
||||
help: widget.ipHelp,
|
||||
ipOnly: widget.ipOnly,
|
||||
nextFocusNode: _portFocus,
|
||||
textPadding: EdgeInsets.all(0),
|
||||
textInputAction: TextInputAction.next,
|
||||
focusNode: widget.focusNode,
|
||||
onChanged: (val) {
|
||||
_ipAndPort.ip = val;
|
||||
widget.onChanged(_ipAndPort);
|
||||
},
|
||||
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,
|
||||
return Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.fromLTRB(6, 6, 2, 6),
|
||||
child: IPField(
|
||||
help: widget.ipHelp,
|
||||
ipOnly: widget.ipOnly,
|
||||
nextFocusNode: _portFocus,
|
||||
textPadding: EdgeInsets.all(0),
|
||||
textInputAction: TextInputAction.next,
|
||||
focusNode: widget.focusNode,
|
||||
onChanged: (val) {
|
||||
_ipAndPort.port = int.tryParse(val);
|
||||
_ipAndPort.ip = val;
|
||||
widget.onChanged(_ipAndPort);
|
||||
},
|
||||
maxLength: 5,
|
||||
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
||||
textInputAction: TextInputAction.done,
|
||||
placeholder: 'port',
|
||||
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',
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ import 'IPAndPortField.dart';
|
|||
class IPAndPortFormField extends FormField<IPAndPort> {
|
||||
//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<IPAndPort> {
|
|||
focusNode,
|
||||
nextFocusNode,
|
||||
ValueChanged<IPAndPort>? onChanged,
|
||||
FormFieldSetter<IPAndPort>? 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<IPAndPort> {
|
|||
@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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -9,7 +9,7 @@ import 'IPField.dart';
|
|||
class IPFormField extends FormField<String> {
|
||||
//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<String> {
|
|||
focusNode,
|
||||
nextFocusNode,
|
||||
ValueChanged<String>? onChanged,
|
||||
FormFieldSetter<String>? onSaved,
|
||||
super.onSaved,
|
||||
textPadding = const EdgeInsets.all(6.0),
|
||||
textInputAction,
|
||||
initialValue,
|
||||
|
@ -25,9 +25,7 @@ class IPFormField extends FormField<String> {
|
|||
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<String> {
|
|||
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;
|
||||
|
|
|
@ -6,7 +6,7 @@ import 'package:mobile_nebula/components/SpecialTextField.dart';
|
|||
class PlatformTextFormField extends FormField<String> {
|
||||
//TODO: auto-validate, enabled?
|
||||
PlatformTextFormField({
|
||||
Key? key,
|
||||
super.key,
|
||||
widgetKey,
|
||||
this.controller,
|
||||
focusNode,
|
||||
|
@ -28,11 +28,9 @@ class PlatformTextFormField extends FormField<String> {
|
|||
String? initialValue,
|
||||
String? placeholder,
|
||||
FormFieldValidator<String>? validator,
|
||||
ValueChanged<String?>? 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<String> {
|
|||
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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -6,15 +6,15 @@ 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;
|
||||
final void Function()? onPressed;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final borderColor =
|
||||
site.errors.length > 0
|
||||
site.errors.isNotEmpty
|
||||
? CupertinoColors.systemRed.resolveFrom(context)
|
||||
: site.connected
|
||||
? CupertinoColors.systemGreen.resolveFrom(context)
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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<SpecialButton> 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<SpecialButton> 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<SpecialButton> 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)),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -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<SpecialTextField> {
|
|||
|
||||
@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!;
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -5,10 +5,10 @@ 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;
|
||||
final void Function()? onPressed;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
|
|
@ -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: <Widget>[
|
||||
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(),
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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: <Widget>[
|
||||
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))),
|
||||
],
|
||||
),
|
||||
|
|
|
@ -7,25 +7,25 @@ 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;
|
||||
final double labelWidth;
|
||||
final CrossAxisAlignment crossAxisAlignment;
|
||||
final onPressed;
|
||||
final void Function()? onPressed;
|
||||
final bool disabled;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var theme;
|
||||
dynamic theme;
|
||||
|
||||
if (Platform.isAndroid) {
|
||||
final origTheme = Theme.of(context);
|
||||
|
@ -44,7 +44,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 +52,9 @@ class ConfigPageItem extends StatelessWidget {
|
|||
child: Row(
|
||||
crossAxisAlignment: crossAxisAlignment,
|
||||
children: <Widget>[
|
||||
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),
|
||||
],
|
||||
|
|
|
@ -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<Widget> 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<Widget> _children = [];
|
||||
List<Widget> 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),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<void> main() async {
|
||||
usePathUrlStrategy();
|
||||
|
||||
var settings = Settings();
|
||||
if (settings.trackErrors) {
|
||||
await SentryFlutter.init((options) {
|
||||
|
@ -34,12 +33,16 @@ Future<void> 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<App> {
|
|||
],
|
||||
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<App> {
|
|||
context: context,
|
||||
builder:
|
||||
(context) =>
|
||||
EnrollmentScreen(code: EnrollmentScreen.parseCode(settings.name!), stream: this.dnEnrolled),
|
||||
EnrollmentScreen(code: EnrollmentScreen.parseCode(settings.name!), stream: dnEnrolled),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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<String, dynamic> 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<String, dynamic> 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<String, dynamic> json) : valid = json['Valid'], reason = json['Reason'];
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import 'package:flutter/services.dart';
|
|||
import 'package:mobile_nebula/models/HostInfo.dart';
|
||||
import 'package:mobile_nebula/models/UnsafeRoute.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
import 'Certificate.dart';
|
||||
import 'StaticHosts.dart';
|
||||
|
||||
|
@ -15,7 +16,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;
|
||||
|
@ -53,45 +54,31 @@ class Site {
|
|||
late List<String> errors;
|
||||
|
||||
Site({
|
||||
String name = '',
|
||||
this.name = '',
|
||||
String? id,
|
||||
Map<String, StaticHost>? staticHostmap,
|
||||
List<CertificateInfo>? ca,
|
||||
CertificateInfo? certInfo,
|
||||
int lhDuration = 0,
|
||||
int port = 0,
|
||||
String cipher = "aes",
|
||||
int sortKey = 0,
|
||||
int mtu = 1300,
|
||||
bool connected = false,
|
||||
String status = '',
|
||||
String logFile = '',
|
||||
String logVerbosity = 'info',
|
||||
this.certInfo,
|
||||
this.lhDuration = 0,
|
||||
this.port = 0,
|
||||
this.cipher = "aes",
|
||||
this.sortKey = 0,
|
||||
this.mtu = 1300,
|
||||
this.connected = false,
|
||||
this.status = '',
|
||||
this.logFile = '',
|
||||
this.logVerbosity = 'info',
|
||||
List<String>? errors,
|
||||
List<UnsafeRoute>? unsafeRoutes,
|
||||
bool managed = false,
|
||||
String? rawConfig,
|
||||
DateTime? lastManagedUpdate,
|
||||
this.managed = false,
|
||||
this.rawConfig,
|
||||
this.lastManagedUpdate,
|
||||
}) {
|
||||
this.name = name;
|
||||
this.id = id ?? uuid.v4();
|
||||
this.staticHostmap = staticHostmap ?? {};
|
||||
this.ca = ca ?? [];
|
||||
this.certInfo = certInfo;
|
||||
this.lhDuration = lhDuration;
|
||||
this.port = port;
|
||||
this.cipher = cipher;
|
||||
this.sortKey = sortKey;
|
||||
this.mtu = mtu;
|
||||
this.connected = connected;
|
||||
this.status = status;
|
||||
this.logFile = logFile;
|
||||
this.logVerbosity = logVerbosity;
|
||||
this.errors = errors ?? [];
|
||||
this.unsafeRoutes = unsafeRoutes ?? [];
|
||||
this.managed = managed;
|
||||
this.rawConfig = rawConfig;
|
||||
this.lastManagedUpdate = lastManagedUpdate;
|
||||
|
||||
_updates = EventChannel('net.defined.nebula/${this.id}');
|
||||
_updates.receiveBroadcastStream().listen(
|
||||
|
@ -139,25 +126,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<String, dynamic> json) {
|
||||
|
@ -169,15 +156,15 @@ class Site {
|
|||
|
||||
List<dynamic> rawUnsafeRoutes = json['unsafeRoutes'];
|
||||
List<UnsafeRoute> unsafeRoutes = [];
|
||||
rawUnsafeRoutes.forEach((val) {
|
||||
for (var val in rawUnsafeRoutes) {
|
||||
unsafeRoutes.add(UnsafeRoute.fromJson(val));
|
||||
});
|
||||
}
|
||||
|
||||
List<dynamic> rawCA = json['ca'];
|
||||
List<CertificateInfo> ca = [];
|
||||
rawCA.forEach((val) {
|
||||
for (var val in rawCA) {
|
||||
ca.add(CertificateInfo.fromJson(val));
|
||||
});
|
||||
}
|
||||
|
||||
CertificateInfo? certInfo;
|
||||
if (json['cert'] != null) {
|
||||
|
@ -186,9 +173,9 @@ class Site {
|
|||
|
||||
List<dynamic> rawErrors = json["errors"];
|
||||
List<String> errors = [];
|
||||
rawErrors.forEach((error) {
|
||||
for (var error in rawErrors) {
|
||||
errors.add(error);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
"name": json["name"],
|
||||
|
@ -295,9 +282,9 @@ class Site {
|
|||
|
||||
List<dynamic> f = jsonDecode(ret);
|
||||
List<HostInfo> hosts = [];
|
||||
f.forEach((v) {
|
||||
for (var v in f) {
|
||||
hosts.add(HostInfo.fromJson(v));
|
||||
});
|
||||
}
|
||||
|
||||
return hosts;
|
||||
} on PlatformException catch (err) {
|
||||
|
@ -317,9 +304,9 @@ class Site {
|
|||
|
||||
List<dynamic> f = jsonDecode(ret);
|
||||
List<HostInfo> hosts = [];
|
||||
f.forEach((v) {
|
||||
for (var v in f) {
|
||||
hosts.add(HostInfo.fromJson(v));
|
||||
});
|
||||
}
|
||||
|
||||
return hosts;
|
||||
} on PlatformException catch (err) {
|
||||
|
@ -331,7 +318,7 @@ class Site {
|
|||
|
||||
Future<Map<String, List<HostInfo>>> 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();
|
||||
|
|
|
@ -10,9 +10,9 @@ class StaticHost {
|
|||
var list = json['destinations'] as List<dynamic>;
|
||||
var result = <IPAndPort>[];
|
||||
|
||||
list.forEach((item) {
|
||||
for (var item in list) {
|
||||
result.add(IPAndPort.fromString(item));
|
||||
});
|
||||
}
|
||||
|
||||
return StaticHost(lighthouse: json['lighthouse'], destinations: result);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -49,6 +49,7 @@ class _EnrollmentScreenState extends State<EnrollmentScreen> {
|
|||
|
||||
static const platform = MethodChannel('net.defined.mobileNebula/NebulaVpnService');
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
code = widget.code;
|
||||
super.initState();
|
||||
|
@ -94,27 +95,30 @@ class _EnrollmentScreenState extends State<EnrollmentScreen> {
|
|||
} 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<EnrollmentScreen> {
|
|||
],
|
||||
),
|
||||
),
|
||||
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<EnrollmentScreen> {
|
|||
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<EnrollmentScreen> {
|
|||
);
|
||||
}
|
||||
|
||||
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<FormState> _formKey = GlobalKey<FormState>();
|
||||
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
|
||||
|
||||
String? validator(String? value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
|
@ -182,7 +183,7 @@ class _EnrollmentScreenState extends State<EnrollmentScreen> {
|
|||
}
|
||||
|
||||
Future<void> 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<EnrollmentScreen> {
|
|||
),
|
||||
);
|
||||
|
||||
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')))]),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
|
|
@ -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<HostInfoScreen> {
|
|||
}
|
||||
|
||||
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<HostInfoScreen> {
|
|||
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<HostInfoScreen> {
|
|||
},
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
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<HostInfoScreen> {
|
|||
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<HostInfoScreen> {
|
|||
checked: currentRemote == remote,
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return ConfigSection(label: items.length > 0 ? 'REMOTES' : null, children: items);
|
||||
return ConfigSection(label: items.isNotEmpty ? 'REMOTES' : null, children: items);
|
||||
}
|
||||
|
||||
Widget _buildClose() {
|
||||
|
|
|
@ -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<MainScreen> {
|
|||
|
||||
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<MainScreen> {
|
|||
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<MainScreen> {
|
|||
}
|
||||
|
||||
Widget _buildSites() {
|
||||
if (sites == null || sites!.length == 0) {
|
||||
if (sites == null || sites!.isEmpty) {
|
||||
return _buildNoSites();
|
||||
}
|
||||
|
||||
List<Widget> items = [];
|
||||
sites!.forEach((site) {
|
||||
for (var site in sites!) {
|
||||
items.add(
|
||||
SiteItem(
|
||||
key: Key(site.id),
|
||||
|
@ -223,7 +223,7 @@ class _MainScreenState extends State<MainScreen> {
|
|||
},
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Widget child = ReorderableListView(
|
||||
shrinkWrap: true,
|
||||
|
@ -260,7 +260,7 @@ class _MainScreenState extends State<MainScreen> {
|
|||
);
|
||||
|
||||
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
|
||||
|
|
|
@ -27,7 +27,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||
void initState() {
|
||||
//TODO: we need to unregister on dispose?
|
||||
settings.onChange().listen((_) {
|
||||
if (this.mounted) {
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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<SiteDetailScreen> {
|
|||
}
|
||||
|
||||
Widget _buildErrors() {
|
||||
if (site.errors.length == 0) {
|
||||
if (site.errors.isEmpty) {
|
||||
return Container();
|
||||
}
|
||||
|
||||
List<Widget> 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<SiteDetailScreen> {
|
|||
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,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
@ -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<SiteLogsScreen> {
|
|||
refreshController.loadComplete();
|
||||
},
|
||||
refreshController: refreshController,
|
||||
bottomBar: _buildBottomBar(),
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(5),
|
||||
constraints: logBoxConstraints(context),
|
||||
|
@ -75,7 +76,6 @@ class _SiteLogsScreenState extends State<SiteLogsScreen> {
|
|||
}, style: TextStyle(fontFamily: 'RobotoMono', fontSize: 14)),
|
||||
),
|
||||
),
|
||||
bottomBar: _buildBottomBar(),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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<HostInfo> tunnels;
|
||||
|
@ -77,7 +77,7 @@ class _SiteTunnelsScreenState extends State<SiteTunnelsScreen> {
|
|||
),
|
||||
),
|
||||
label: Row(
|
||||
children: <Widget>[Padding(child: icon, padding: EdgeInsets.only(right: 10)), Text(hostInfo.vpnIp)],
|
||||
children: <Widget>[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<SiteTunnelsScreen> {
|
|||
}).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),
|
||||
};
|
||||
|
||||
|
|
|
@ -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<AddCertificateScreen> {
|
|||
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<AddCertificateScreen> {
|
|||
var rawCerts = await platform.invokeMethod("nebula.parseCerts", <String, String>{"certs": rawCert});
|
||||
|
||||
List<dynamic> certs = jsonDecode(rawCerts);
|
||||
if (certs.length > 0) {
|
||||
if (certs.isNotEmpty) {
|
||||
var tryCertInfo = CertificateInfo.fromJson(certs.first);
|
||||
if (tryCertInfo.cert.details.isCa) {
|
||||
return Utils.popError(
|
||||
|
|
|
@ -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<Advanced> onSave;
|
||||
|
@ -85,7 +85,7 @@ class _AdvancedScreenState extends State<AdvancedScreen> {
|
|||
//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,
|
||||
|
|
|
@ -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<CertificateInfo> cas;
|
||||
final ValueChanged<List<CertificateInfo>>? onSave;
|
||||
|
@ -39,9 +39,9 @@ class _CAListScreenState extends State<CAListScreen> {
|
|||
|
||||
@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<CAListScreen> {
|
|||
List<Widget> items = [];
|
||||
final caItems = _buildCAs();
|
||||
|
||||
if (caItems.length > 0) {
|
||||
if (caItems.isNotEmpty) {
|
||||
items.add(ConfigSection(children: caItems));
|
||||
}
|
||||
|
||||
|
@ -115,14 +115,14 @@ class _CAListScreenState extends State<CAListScreen> {
|
|||
var ignored = 0;
|
||||
|
||||
List<dynamic> 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<CAListScreen> {
|
|||
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) {
|
||||
|
|
|
@ -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<CertificateDetailsScreen> {
|
|||
|
||||
Widget _buildFilters() {
|
||||
List<Widget> 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();
|
||||
}
|
||||
|
|
|
@ -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<String> onSave;
|
||||
|
|
|
@ -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<String> onSave;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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<ScanQRScreen> createState() => _ScanQRScreenState();
|
||||
}
|
||||
|
|
|
@ -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<SiteConfigScreen> {
|
|||
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<SiteConfigScreen> {
|
|||
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<SiteConfigScreen> {
|
|||
children: <Widget>[
|
||||
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<SiteConfigScreen> {
|
|||
children: <Widget>[
|
||||
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<SiteConfigScreen> {
|
|||
alignment: WrapAlignment.end,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: <Widget>[
|
||||
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)),
|
||||
],
|
||||
|
|
|
@ -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<IPAndPort> destinations;
|
||||
final String nebulaIp;
|
||||
|
@ -51,11 +50,11 @@ class _StaticHostmapScreenState extends State<StaticHostmapScreen> {
|
|||
_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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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<String, StaticHost> hostmap;
|
||||
final ValueChanged<Map<String, StaticHost>>? onSave;
|
||||
|
@ -32,7 +32,7 @@ class StaticHostsScreen extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _StaticHostsScreenState extends State<StaticHostsScreen> {
|
||||
Map<Key, _Hostmap> _hostmap = {};
|
||||
final Map<Key, _Hostmap> _hostmap = {};
|
||||
bool changed = false;
|
||||
|
||||
@override
|
||||
|
@ -80,17 +80,17 @@ class _StaticHostsScreenState extends State<StaticHostsScreen> {
|
|||
label: Row(
|
||||
children: <Widget>[
|
||||
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(
|
||||
|
|
|
@ -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<UnsafeRoute> onSave;
|
||||
|
|
|
@ -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<UnsafeRoute> unsafeRoutes;
|
||||
final ValueChanged<List<UnsafeRoute>>? onSave;
|
||||
|
@ -24,9 +24,9 @@ class _UnsafeRoutesScreenState extends State<UnsafeRoutesScreen> {
|
|||
@override
|
||||
void initState() {
|
||||
unsafeRoutes = {};
|
||||
widget.unsafeRoutes.forEach((route) {
|
||||
for (var route in widget.unsafeRoutes) {
|
||||
unsafeRoutes[UniqueKey()] = route;
|
||||
});
|
||||
}
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
|
|
@ -10,8 +10,8 @@ bool DEFAULT_TRACK_ERRORS = true;
|
|||
|
||||
class Settings {
|
||||
final _storage = Storage();
|
||||
StreamController _change = StreamController.broadcast();
|
||||
var _settings = Map<String, dynamic>();
|
||||
final StreamController _change = StreamController.broadcast();
|
||||
var _settings = <String, dynamic>{};
|
||||
|
||||
bool get useSystemColors {
|
||||
return _getBool('systemDarkMode', true);
|
||||
|
|
|
@ -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'),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ bool dnsValidator(str, {requireTld = true, allowUnderscore = false}) {
|
|||
return false;
|
||||
}
|
||||
|
||||
List parts = str.split('.');
|
||||
List<String> parts = str.split('.');
|
||||
if (requireTld) {
|
||||
var tld = parts.removeLast();
|
||||
if (parts.isEmpty || !RegExp(r'^[a-z]{2,}$').hasMatch(tld)) {
|
||||
|
|
20
pubspec.lock
20
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"
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue