diff --git a/README.md b/README.md
index bf3b1fa..833f966 100644
--- a/README.md
+++ b/README.md
@@ -1,16 +1,30 @@
-# Dependencies
+## Setting up dev environment
-- [`flutter`](https://flutter.dev/docs/get-started/install)
-- [`gomobile`](https://godoc.org/golang.org/x/mobile/cmd/gomobile)
+Install all of the following things:
+
+- [`xcode`](https://apps.apple.com/us/app/xcode/)
- [`android-studio`](https://developer.android.com/studio)
-- [Enable NDK](https://developer.android.com/studio/projects/install-ndk) Check local.properties for current NDK version
+- [`flutter` 3.3.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)
-Currently using flutter 2.0.5
+Ensure your path is set up correctly to execute flutter
-Copy env.sh.example to env.sh and update your PATH variable to expose both flutter and go bin directories
+Run `flutter doctor` and fix everything it complains before proceeding
- ```export PATH="$PATH:/path/to/go/bin:/path/to/flutter/bin"```
+*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 `21.1.6352462` 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
@@ -21,7 +35,6 @@ Use:
flutter format lib/ test/ -l 120
```
-
# Release
Update `version` in `pubspec.yaml` to reflect this release, then
diff --git a/android/.gitignore b/android/.gitignore
index e71e2bf..5e071fd 100644
--- a/android/.gitignore
+++ b/android/.gitignore
@@ -6,3 +6,4 @@ gradle-wrapper.jar
/local.properties
GeneratedPluginRegistrant.java
/build/build-attribution/
+/mobileNebula/mobileNebula.aar
diff --git a/android/app/build.gradle b/android/app/build.gradle
index e1c0bcb..0bf0c5c 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -32,23 +32,28 @@ if (keystorePropertiesFile.exists()) {
}
android {
- compileSdkVersion 30
+ compileSdkVersion flutter.compileSdkVersion
+ ndkVersion flutter.ndkVersion
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
- lintOptions {
- disable 'InvalidPackage'
- }
-
defaultConfig {
applicationId "net.defined.mobile_nebula"
- minSdkVersion 29
- targetSdkVersion 30
+ minSdkVersion 29 //flutter.minSdkVersion
+ targetSdkVersion 30 //flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
- testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
signingConfigs {
@@ -62,17 +67,18 @@ android {
buildTypes {
release {
- signingConfig signingConfigs.release
-
- // We are disabling minification and proguard because it wrecks the crypto for storing keys
- // Ideally we would turn these on. We had issues with gson as well but resolved those with proguardFiles
- shrinkResources false
- minifyEnabled false
- useProguard false
- proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
-
+// signingConfig signingConfigs.release
+//
+// // We are disabling minification and proguard because it wrecks the crypto for storing keys
+// // Ideally we would turn these on. We had issues with gson as well but resolved those with proguardFiles
+// shrinkResources false
+// minifyEnabled false
+// useProguard false
+// proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+//
resValue 'string', 'app_name', '"Nebula"'
}
+
debug {
resValue 'string', 'app_name', '"Nebula-DEBUG"'
applicationIdSuffix '.debug'
@@ -84,26 +90,9 @@ flutter {
source '../..'
}
-repositories {
- flatDir {
- dirs 'src/main/libs'
- }
-}
-
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
- implementation "androidx.security:security-crypto:1.0.0-rc02"
+ implementation "androidx.security:security-crypto:1.0.0"
implementation 'com.google.code.gson:gson:2.8.6'
-
- testImplementation 'junit:junit:4.12'
- androidTestImplementation 'androidx.test:runner:1.1.1'
- androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
- implementation (name:'mobileNebula', ext:'aar') {
- exec {
- workingDir '../../'
- environment("ANDROID_NDK_HOME", android.ndkDirectory)
- environment("ANDROID_HOME", android.sdkDirectory)
- commandLine './gen-artifacts.sh', 'android'
- }
- }
-}
+ implementation project(':mobileNebula')
+}
\ No newline at end of file
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 6459ffc..5a9c21e 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -9,11 +9,12 @@
@@ -21,6 +22,7 @@
plugins.load(reader) }
-}
+assert localPropertiesFile.exists()
+localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
-plugins.each { name, path ->
- def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
- include ":$name"
- project(":$name").projectDir = pluginDirectory
-}
+def flutterSdkPath = properties.getProperty("flutter.sdk")
+assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
+apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
\ No newline at end of file
diff --git a/android/settings_aar.gradle b/android/settings_aar.gradle
deleted file mode 100644
index e7b4def..0000000
--- a/android/settings_aar.gradle
+++ /dev/null
@@ -1 +0,0 @@
-include ':app'
diff --git a/gen-artifacts.sh b/gen-artifacts.sh
index 2c8893b..c915adc 100755
--- a/gen-artifacts.sh
+++ b/gen-artifacts.sh
@@ -16,9 +16,9 @@ if [ "$1" = "ios" ]; then
elif [ "$1" = "android" ]; then
# Build nebula for android
make mobileNebula.aar
- mkdir -p ../android/app/src/main/libs
- rm -rf ../android/app/src/main/libs/mobileNebula.aar
- cp mobileNebula.aar ../android/app/src/main/libs/mobileNebula.aar
+ mkdir -p ../android/mobileNebula
+ rm -rf ../android/mobileNebula/mobileNebula.aar
+ cp mobileNebula.aar ../android/mobileNebula/mobileNebula.aar
else
echo "Error: unsupported target os $1"
diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist
index f2872cf..4f8d4d2 100644
--- a/ios/Flutter/AppFrameworkInfo.plist
+++ b/ios/Flutter/AppFrameworkInfo.plist
@@ -21,6 +21,6 @@
CFBundleVersion
1.0
MinimumOSVersion
- 9.0
+ 11.0
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index 9e51132..2537f26 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -1,64 +1,57 @@
PODS:
- - barcode_scan (0.0.1):
- - Flutter
- - MTBBarcodeScanner
- - SwiftProtobuf
- - DKImagePickerController/Core (4.3.0):
+ - DKImagePickerController/Core (4.3.4):
- DKImagePickerController/ImageDataManager
- DKImagePickerController/Resource
- - DKImagePickerController/ImageDataManager (4.3.0)
- - DKImagePickerController/PhotoGallery (4.3.0):
+ - DKImagePickerController/ImageDataManager (4.3.4)
+ - DKImagePickerController/PhotoGallery (4.3.4):
- DKImagePickerController/Core
- DKPhotoGallery
- - DKImagePickerController/Resource (4.3.0)
- - DKPhotoGallery (0.0.15):
- - DKPhotoGallery/Core (= 0.0.15)
- - DKPhotoGallery/Model (= 0.0.15)
- - DKPhotoGallery/Preview (= 0.0.15)
- - DKPhotoGallery/Resource (= 0.0.15)
+ - DKImagePickerController/Resource (4.3.4)
+ - DKPhotoGallery (0.0.17):
+ - DKPhotoGallery/Core (= 0.0.17)
+ - DKPhotoGallery/Model (= 0.0.17)
+ - DKPhotoGallery/Preview (= 0.0.17)
+ - DKPhotoGallery/Resource (= 0.0.17)
- SDWebImage
- - SDWebImageFLPlugin
- - DKPhotoGallery/Core (0.0.15):
+ - SwiftyGif
+ - DKPhotoGallery/Core (0.0.17):
- DKPhotoGallery/Model
- DKPhotoGallery/Preview
- SDWebImage
- - SDWebImageFLPlugin
- - DKPhotoGallery/Model (0.0.15):
+ - SwiftyGif
+ - DKPhotoGallery/Model (0.0.17):
- SDWebImage
- - SDWebImageFLPlugin
- - DKPhotoGallery/Preview (0.0.15):
+ - SwiftyGif
+ - DKPhotoGallery/Preview (0.0.17):
- DKPhotoGallery/Model
- DKPhotoGallery/Resource
- SDWebImage
- - SDWebImageFLPlugin
- - DKPhotoGallery/Resource (0.0.15):
+ - SwiftyGif
+ - DKPhotoGallery/Resource (0.0.17):
- SDWebImage
- - SDWebImageFLPlugin
+ - SwiftyGif
- file_picker (0.0.1):
- DKImagePickerController/PhotoGallery
- Flutter
- - FLAnimatedImage (1.0.12)
- Flutter (1.0.0)
- - MTBBarcodeScanner (5.0.11)
+ - flutter_barcode_scanner (2.0.0):
+ - Flutter
- package_info (0.0.1):
- Flutter
- path_provider_ios (0.0.1):
- Flutter
- - SDWebImage (5.8.0):
- - SDWebImage/Core (= 5.8.0)
- - SDWebImage/Core (5.8.0)
- - SDWebImageFLPlugin (0.4.0):
- - FLAnimatedImage (>= 1.0.11)
- - SDWebImage/Core (~> 5.6)
- - SwiftProtobuf (1.9.0)
+ - SDWebImage (5.13.3):
+ - SDWebImage/Core (= 5.13.3)
+ - SDWebImage/Core (5.13.3)
+ - SwiftyGif (5.4.3)
- SwiftyJSON (5.0.1)
- url_launcher_ios (0.0.1):
- Flutter
DEPENDENCIES:
- - barcode_scan (from `.symlinks/plugins/barcode_scan/ios`)
- file_picker (from `.symlinks/plugins/file_picker/ios`)
- Flutter (from `Flutter`)
+ - flutter_barcode_scanner (from `.symlinks/plugins/flutter_barcode_scanner/ios`)
- package_info (from `.symlinks/plugins/package_info/ios`)
- path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`)
- SwiftyJSON (~> 5.0)
@@ -68,20 +61,17 @@ SPEC REPOS:
trunk:
- DKImagePickerController
- DKPhotoGallery
- - FLAnimatedImage
- - MTBBarcodeScanner
- SDWebImage
- - SDWebImageFLPlugin
- - SwiftProtobuf
+ - SwiftyGif
- SwiftyJSON
EXTERNAL SOURCES:
- barcode_scan:
- :path: ".symlinks/plugins/barcode_scan/ios"
file_picker:
:path: ".symlinks/plugins/file_picker/ios"
Flutter:
:path: Flutter
+ flutter_barcode_scanner:
+ :path: ".symlinks/plugins/flutter_barcode_scanner/ios"
package_info:
:path: ".symlinks/plugins/package_info/ios"
path_provider_ios:
@@ -90,21 +80,18 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/url_launcher_ios/ios"
SPEC CHECKSUMS:
- barcode_scan: a5c27959edfafaa0c771905bad0b29d6d39e4479
- DKImagePickerController: 397702a3590d4958fad336e9a77079935c500ddb
- DKPhotoGallery: e880aef16c108333240e1e7327896f2ea380f4f0
- file_picker: 3e6c3790de664ccf9b882732d9db5eaf6b8d4eb1
- FLAnimatedImage: 4a0b56255d9b05f18b6dd7ee06871be5d3b89e31
- Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a
- MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb
+ DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac
+ DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179
+ file_picker: 817ab1d8cd2da9d2da412a417162deee3500fc95
+ Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
+ flutter_barcode_scanner: 7a1144744c28dc0c57a8de7218ffe5ec59a9e4bf
package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62
path_provider_ios: 7d7ce634493af4477d156294792024ec3485acd5
- SDWebImage: 84000f962cbfa70c07f19d2234cbfcf5d779b5dc
- SDWebImageFLPlugin: 6c2295fb1242d44467c6c87dc5db6b0a13228fd8
- SwiftProtobuf: ecbec1be9036d15655f6b3443a1c4ea693c97932
+ SDWebImage: af5bbffef2cde09f148d826f9733dcde1a9414cd
+ SwiftyGif: 6c3eafd0ce693cad58bb63d2b2fb9bacb8552780
SwiftyJSON: 2f33a42c6fbc52764d96f13368585094bfd8aa5e
url_launcher_ios: 02f1989d4e14e998335b02b67a7590fa34f971af
PODFILE CHECKSUM: 92e176614f91c6517d4254a0edec8b66f076c77e
-COCOAPODS: 1.10.1
+COCOAPODS: 1.11.3
diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
index 57ebed7..a584599 100644
--- a/ios/Runner.xcodeproj/project.pbxproj
+++ b/ios/Runner.xcodeproj/project.pbxproj
@@ -339,14 +339,11 @@
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh",
"${BUILT_PRODUCTS_DIR}/DKImagePickerController/DKImagePickerController.framework",
"${BUILT_PRODUCTS_DIR}/DKPhotoGallery/DKPhotoGallery.framework",
- "${BUILT_PRODUCTS_DIR}/FLAnimatedImage/FLAnimatedImage.framework",
- "${BUILT_PRODUCTS_DIR}/MTBBarcodeScanner/MTBBarcodeScanner.framework",
"${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework",
- "${BUILT_PRODUCTS_DIR}/SDWebImageFLPlugin/SDWebImageFLPlugin.framework",
- "${BUILT_PRODUCTS_DIR}/SwiftProtobuf/SwiftProtobuf.framework",
+ "${BUILT_PRODUCTS_DIR}/SwiftyGif/SwiftyGif.framework",
"${BUILT_PRODUCTS_DIR}/SwiftyJSON/SwiftyJSON.framework",
- "${BUILT_PRODUCTS_DIR}/barcode_scan/barcode_scan.framework",
"${BUILT_PRODUCTS_DIR}/file_picker/file_picker.framework",
+ "${BUILT_PRODUCTS_DIR}/flutter_barcode_scanner/flutter_barcode_scanner.framework",
"${BUILT_PRODUCTS_DIR}/package_info/package_info.framework",
"${BUILT_PRODUCTS_DIR}/path_provider_ios/path_provider_ios.framework",
"${BUILT_PRODUCTS_DIR}/url_launcher_ios/url_launcher_ios.framework",
@@ -355,14 +352,11 @@
outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DKImagePickerController.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DKPhotoGallery.framework",
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FLAnimatedImage.framework",
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MTBBarcodeScanner.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImage.framework",
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImageFLPlugin.framework",
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftProtobuf.framework",
+ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftyGif.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftyJSON.framework",
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/barcode_scan.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/file_picker.framework",
+ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_barcode_scanner.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/package_info.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider_ios.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/url_launcher_ios.framework",
@@ -558,7 +552,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
- CURRENT_PROJECT_VERSION = 1;
+ CURRENT_PROJECT_VERSION = 4;
DEVELOPMENT_TEAM = 576H3XS7FP;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
@@ -596,7 +590,7 @@
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = NebulaNetworkExtension/NebulaNetworkExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 1;
+ CURRENT_PROJECT_VERSION = 4;
DEVELOPMENT_TEAM = 576H3XS7FP;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
@@ -637,7 +631,7 @@
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = NebulaNetworkExtension/NebulaNetworkExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 1;
+ CURRENT_PROJECT_VERSION = 4;
DEVELOPMENT_TEAM = 576H3XS7FP;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
@@ -675,7 +669,7 @@
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = NebulaNetworkExtension/NebulaNetworkExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 1;
+ CURRENT_PROJECT_VERSION = 4;
DEVELOPMENT_TEAM = 576H3XS7FP;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
@@ -816,7 +810,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
- CURRENT_PROJECT_VERSION = 1;
+ CURRENT_PROJECT_VERSION = 4;
DEVELOPMENT_TEAM = 576H3XS7FP;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
@@ -852,7 +846,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
- CURRENT_PROJECT_VERSION = 1;
+ CURRENT_PROJECT_VERSION = 4;
DEVELOPMENT_TEAM = 576H3XS7FP;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
index ebbc462..05db08d 100644
--- a/ios/Runner/Info.plist
+++ b/ios/Runner/Info.plist
@@ -47,5 +47,7 @@
UIViewControllerBasedStatusBarAppearance
+ CADisableMinimumFrameDurationOnPhone
+
diff --git a/ios/Runner/Sites.swift b/ios/Runner/Sites.swift
index a8cf879..a21479e 100644
--- a/ios/Runner/Sites.swift
+++ b/ios/Runner/Sites.swift
@@ -133,13 +133,13 @@ class SiteUpdater: NSObject, FlutterStreamHandler {
let connected = self.site.connected
self.site.status = statusString[self.site.manager!.connection.status]
self.site.connected = statusMap[self.site.manager!.connection.status]
-
+
// Check to see if we just moved to connected and if we have a start function to call when that happens
if self.site.connected! && connected != self.site.connected && self.startFunc != nil {
self.startFunc!()
self.startFunc = nil
}
-
+
let d: Dictionary = [
"connected": self.site.connected!,
"status": self.site.status!,
diff --git a/lib/components/CIDRField.dart b/lib/components/CIDRField.dart
index 5a98321..1f3d472 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,
+ Key? key,
this.ipHelp = "ip address",
this.autoFocus = false,
this.focusNode,
@@ -21,12 +21,12 @@ class CIDRField extends StatefulWidget {
final String ipHelp;
final bool autoFocus;
- final FocusNode focusNode;
- final FocusNode nextFocusNode;
- final ValueChanged onChanged;
- final TextInputAction textInputAction;
- final TextEditingController ipController;
- final TextEditingController bitsController;
+ final FocusNode? focusNode;
+ final FocusNode? nextFocusNode;
+ final ValueChanged? onChanged;
+ final TextInputAction? textInputAction;
+ final TextEditingController? ipController;
+ final TextEditingController? bitsController;
@override
_CIDRFieldState createState() => _CIDRFieldState();
@@ -44,7 +44,7 @@ class _CIDRFieldState extends State {
void initState() {
//TODO: this won't track external controller changes appropriately
cidr.ip = widget.ipController?.text ?? "";
- cidr.bits = int.tryParse(widget.bitsController?.text ?? "");
+ cidr.bits = int.tryParse(widget.bitsController?.text ?? "") ?? 0;
super.initState();
}
@@ -66,8 +66,12 @@ class _CIDRFieldState extends State {
focusNode: widget.focusNode,
nextFocusNode: bitsFocus,
onChanged: (val) {
+ if (widget.onChanged == null) {
+ return;
+ }
+
cidr.ip = val;
- widget.onChanged(cidr);
+ widget.onChanged!(cidr);
},
controller: widget.ipController,
))),
@@ -81,8 +85,12 @@ class _CIDRFieldState extends State {
nextFocusNode: widget.nextFocusNode,
controller: widget.bitsController,
onChanged: (val) {
- cidr.bits = int.tryParse(val ?? "");
- widget.onChanged(cidr);
+ if (widget.onChanged == null) {
+ return;
+ }
+
+ cidr.bits = int.tryParse(val) ?? 0;
+ widget.onChanged!(cidr);
},
maxLength: 2,
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
diff --git a/lib/components/CIDRFormField.dart b/lib/components/CIDRFormField.dart
index 0a18742..ff00553 100644
--- a/lib/components/CIDRFormField.dart
+++ b/lib/components/CIDRFormField.dart
@@ -6,15 +6,15 @@ import 'package:mobile_nebula/validators/ipValidator.dart';
class CIDRFormField extends FormField {
//TODO: onSaved, validator, auto-validate, enabled?
CIDRFormField({
- Key key,
+ Key? key,
autoFocus = false,
enableIPV6 = false,
focusNode,
nextFocusNode,
- ValueChanged onChanged,
- FormFieldSetter onSaved,
+ ValueChanged? onChanged,
+ FormFieldSetter? onSaved,
textInputAction,
- CIDR initialValue,
+ CIDR? initialValue,
this.ipController,
this.bitsController,
}) : super(
@@ -30,14 +30,14 @@ class CIDRFormField extends FormField {
return 'Please enter a valid ip address';
}
- if (cidr.bits == null || cidr.bits > 32 || cidr.bits < 0) {
+ if (cidr.bits > 32 || cidr.bits < 0) {
return "Please enter a valid number of bits";
}
return null;
},
builder: (FormFieldState field) {
- final _CIDRFormField state = field;
+ final _CIDRFormField state = field as _CIDRFormField;
void onChangedHandler(CIDR value) {
if (onChanged != null) {
@@ -57,50 +57,50 @@ class CIDRFormField extends FormField {
bitsController: state._effectiveBitsController,
),
field.hasError
- ? Text(field.errorText,
+ ? Text(field.errorText ?? "Unknown error",
style: TextStyle(color: CupertinoColors.systemRed.resolveFrom(field.context), fontSize: 13),
textAlign: TextAlign.end)
: Container(height: 0)
]);
});
- final TextEditingController ipController;
- final TextEditingController bitsController;
+ final TextEditingController? ipController;
+ final TextEditingController? bitsController;
@override
_CIDRFormField createState() => _CIDRFormField();
}
class _CIDRFormField extends FormFieldState {
- TextEditingController _ipController;
- TextEditingController _bitsController;
+ TextEditingController? _ipController = TextEditingController();
+ TextEditingController? _bitsController = TextEditingController();
- TextEditingController get _effectiveIPController => widget.ipController ?? _ipController;
- TextEditingController get _effectiveBitsController => widget.bitsController ?? _bitsController;
+ TextEditingController get _effectiveIPController => widget.ipController ?? _ipController!;
+ TextEditingController get _effectiveBitsController => widget.bitsController ?? _bitsController!;
@override
- CIDRFormField get widget => super.widget;
+ CIDRFormField get widget => super.widget as CIDRFormField;
@override
void initState() {
super.initState();
if (widget.ipController == null) {
- _ipController = TextEditingController(text: widget.initialValue.ip);
+ _ipController = TextEditingController(text: widget.initialValue?.ip);
} else {
- widget.ipController.addListener(_handleControllerChanged);
+ widget.ipController!.addListener(_handleControllerChanged);
}
if (widget.bitsController == null) {
_bitsController = TextEditingController(text: widget.initialValue?.bits?.toString() ?? "");
} else {
- widget.bitsController.addListener(_handleControllerChanged);
+ widget.bitsController!.addListener(_handleControllerChanged);
}
}
@override
void didUpdateWidget(CIDRFormField oldWidget) {
super.didUpdateWidget(oldWidget);
- var update = CIDR(ip: widget.ipController?.text, bits: int.tryParse(widget.bitsController?.text ?? "") ?? null);
+ var update = CIDR(ip: widget.ipController?.text ?? "", bits: int.tryParse(widget.bitsController?.text ?? "") ?? 0);
bool shouldUpdate = false;
if (widget.ipController != oldWidget.ipController) {
@@ -108,12 +108,12 @@ class _CIDRFormField extends FormFieldState {
widget.ipController?.addListener(_handleControllerChanged);
if (oldWidget.ipController != null && widget.ipController == null) {
- _ipController = TextEditingController.fromValue(oldWidget.ipController.value);
+ _ipController = TextEditingController.fromValue(oldWidget.ipController!.value);
}
if (widget.ipController != null) {
shouldUpdate = true;
- update.ip = widget.ipController.text;
+ update.ip = widget.ipController!.text;
if (oldWidget.ipController == null) _ipController = null;
}
}
@@ -123,12 +123,12 @@ class _CIDRFormField extends FormFieldState {
widget.bitsController?.addListener(_handleControllerChanged);
if (oldWidget.bitsController != null && widget.bitsController == null) {
- _bitsController = TextEditingController.fromValue(oldWidget.bitsController.value);
+ _bitsController = TextEditingController.fromValue(oldWidget.bitsController!.value);
}
if (widget.bitsController != null) {
shouldUpdate = true;
- update.bits = int.parse(widget.bitsController.text);
+ update.bits = int.parse(widget.bitsController!.text);
if (oldWidget.bitsController == null) _bitsController = null;
}
}
@@ -149,8 +149,8 @@ class _CIDRFormField extends FormFieldState {
void reset() {
super.reset();
setState(() {
- _effectiveIPController.text = widget.initialValue.ip;
- _effectiveBitsController.text = widget.initialValue.bits.toString();
+ _effectiveIPController.text = widget.initialValue?.ip ?? "";
+ _effectiveBitsController.text = widget.initialValue?.bits.toString() ?? "";
});
}
@@ -163,7 +163,11 @@ class _CIDRFormField extends FormFieldState {
// example, the reset() method. In such cases, the FormField value will
// already have been set.
final effectiveBits = int.parse(_effectiveBitsController.text);
- if (_effectiveIPController.text != value.ip || effectiveBits != value.bits) {
+ if (value == null) {
+ return;
+ }
+
+ if (_effectiveIPController.text != value!.ip || effectiveBits != value!.bits) {
didChange(CIDR(ip: _effectiveIPController.text, bits: effectiveBits));
}
}
diff --git a/lib/components/FormPage.dart b/lib/components/FormPage.dart
index 3634f72..fcfcfb8 100644
--- a/lib/components/FormPage.dart
+++ b/lib/components/FormPage.dart
@@ -8,11 +8,11 @@ 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,
- this.title,
- @required this.child,
- @required this.onSave,
- @required this.changed,
+ {Key? key,
+ required this.title,
+ required this.child,
+ required this.onSave,
+ required this.changed,
this.hideSave = false,
this.scrollController})
: super(key: key);
@@ -20,7 +20,7 @@ class FormPage extends StatefulWidget {
final String title;
final Function onSave;
final Widget child;
- final ScrollController scrollController;
+ final ScrollController? scrollController;
/// If you need the page to progress to a certain point before saving, control it here
final bool hideSave;
@@ -90,11 +90,15 @@ class _FormPageState extends State {
Utils.trailingSaveWidget(
context,
() {
- if (!_formKey.currentState.validate()) {
+ if (_formKey.currentState == null) {
return;
}
- _formKey.currentState.save();
+ if (!_formKey.currentState!.validate()) {
+ return;
+ }
+
+ _formKey.currentState!.save();
widget.onSave();
},
)
diff --git a/lib/components/IPAndPortField.dart b/lib/components/IPAndPortField.dart
index f70a564..463000d 100644
--- a/lib/components/IPAndPortField.dart
+++ b/lib/components/IPAndPortField.dart
@@ -8,13 +8,13 @@ import 'IPField.dart';
//TODO: Support initialValue
class IPAndPortField extends StatefulWidget {
const IPAndPortField({
- Key key,
+ Key? key,
this.ipOnly = false,
this.ipHelp = "ip address",
this.autoFocus = false,
this.focusNode,
this.nextFocusNode,
- this.onChanged,
+ required this.onChanged,
this.textInputAction,
this.noBorder = false,
this.ipTextAlign,
@@ -25,14 +25,14 @@ class IPAndPortField extends StatefulWidget {
final String ipHelp;
final bool ipOnly;
final bool autoFocus;
- final FocusNode focusNode;
- final FocusNode nextFocusNode;
+ final FocusNode? focusNode;
+ final FocusNode? nextFocusNode;
final ValueChanged onChanged;
- final TextInputAction textInputAction;
+ final TextInputAction? textInputAction;
final bool noBorder;
- final TextAlign ipTextAlign;
- final TextEditingController ipController;
- final TextEditingController portController;
+ final TextAlign? ipTextAlign;
+ final TextEditingController? ipController;
+ final TextEditingController? portController;
@override
_IPAndPortFieldState createState() => _IPAndPortFieldState();
diff --git a/lib/components/IPAndPortFormField.dart b/lib/components/IPAndPortFormField.dart
index 0171db0..c9e8c4c 100644
--- a/lib/components/IPAndPortFormField.dart
+++ b/lib/components/IPAndPortFormField.dart
@@ -8,17 +8,17 @@ import 'IPAndPortField.dart';
class IPAndPortFormField extends FormField {
//TODO: onSaved, validator, auto-validate, enabled?
IPAndPortFormField({
- Key key,
+ Key? key,
ipOnly = false,
enableIPV6 = false,
ipHelp = "ip address",
autoFocus = false,
focusNode,
nextFocusNode,
- ValueChanged onChanged,
- FormFieldSetter onSaved,
+ ValueChanged? onChanged,
+ FormFieldSetter? onSaved,
textInputAction,
- IPAndPort initialValue,
+ IPAndPort? initialValue,
noBorder,
ipTextAlign = TextAlign.center,
this.ipController,
@@ -36,14 +36,14 @@ class IPAndPortFormField extends FormField {
return ipOnly ? 'Please enter a valid ip address' : 'Please enter a valid ip address or dns name';
}
- if (ipAndPort.port == null || ipAndPort.port > 65535 || ipAndPort.port < 0) {
+ if (ipAndPort.port == null || ipAndPort.port! > 65535 || ipAndPort.port! < 0) {
return "Please enter a valid port";
}
return null;
},
builder: (FormFieldState field) {
- final _IPAndPortFormField state = field;
+ final _IPAndPortFormField state = field as _IPAndPortFormField;
void onChangedHandler(IPAndPort value) {
if (onChanged != null) {
@@ -67,42 +67,42 @@ class IPAndPortFormField extends FormField {
ipTextAlign: ipTextAlign,
),
field.hasError
- ? Text(field.errorText,
+ ? Text(field.errorText!,
style: TextStyle(color: CupertinoColors.systemRed.resolveFrom(field.context), fontSize: 13))
: Container(height: 0)
]);
});
- final TextEditingController ipController;
- final TextEditingController portController;
+ final TextEditingController? ipController;
+ final TextEditingController? portController;
@override
_IPAndPortFormField createState() => _IPAndPortFormField();
}
class _IPAndPortFormField extends FormFieldState {
- TextEditingController _ipController;
- TextEditingController _portController;
+ TextEditingController? _ipController;
+ TextEditingController? _portController;
- TextEditingController get _effectiveIPController => widget.ipController ?? _ipController;
- TextEditingController get _effectivePortController => widget.portController ?? _portController;
+ TextEditingController get _effectiveIPController => widget.ipController ?? _ipController!;
+ TextEditingController get _effectivePortController => widget.portController ?? _portController!;
@override
- IPAndPortFormField get widget => super.widget;
+ IPAndPortFormField get widget => super.widget as IPAndPortFormField;
@override
void initState() {
super.initState();
if (widget.ipController == null) {
- _ipController = TextEditingController(text: widget.initialValue.ip);
+ _ipController = TextEditingController(text: widget.initialValue?.ip ?? "");
} else {
- widget.ipController.addListener(_handleControllerChanged);
+ widget.ipController!.addListener(_handleControllerChanged);
}
if (widget.portController == null) {
_portController = TextEditingController(text: widget.initialValue?.port?.toString() ?? "");
} else {
- widget.portController.addListener(_handleControllerChanged);
+ widget.portController!.addListener(_handleControllerChanged);
}
}
@@ -118,12 +118,12 @@ class _IPAndPortFormField extends FormFieldState {
widget.ipController?.addListener(_handleControllerChanged);
if (oldWidget.ipController != null && widget.ipController == null) {
- _ipController = TextEditingController.fromValue(oldWidget.ipController.value);
+ _ipController = TextEditingController.fromValue(oldWidget.ipController!.value);
}
if (widget.ipController != null) {
shouldUpdate = true;
- update.ip = widget.ipController.text;
+ update.ip = widget.ipController!.text;
if (oldWidget.ipController == null) _ipController = null;
}
}
@@ -133,12 +133,12 @@ class _IPAndPortFormField extends FormFieldState {
widget.portController?.addListener(_handleControllerChanged);
if (oldWidget.portController != null && widget.portController == null) {
- _portController = TextEditingController.fromValue(oldWidget.portController.value);
+ _portController = TextEditingController.fromValue(oldWidget.portController!.value);
}
if (widget.portController != null) {
shouldUpdate = true;
- update.port = int.parse(widget.portController.text);
+ update.port = int.parse(widget.portController!.text);
if (oldWidget.portController == null) _portController = null;
}
}
@@ -159,8 +159,8 @@ class _IPAndPortFormField extends FormFieldState {
void reset() {
super.reset();
setState(() {
- _effectiveIPController.text = widget.initialValue.ip;
- _effectivePortController.text = widget.initialValue.port.toString();
+ _effectiveIPController.text = widget.initialValue?.ip ?? "";
+ _effectivePortController.text = widget.initialValue?.port?.toString() ?? "";
});
}
@@ -173,7 +173,11 @@ class _IPAndPortFormField extends FormFieldState {
// example, the reset() method. In such cases, the FormField value will
// already have been set.
final effectivePort = int.parse(_effectivePortController.text);
- if (_effectiveIPController.text != value.ip || effectivePort != value.port) {
+ if (value == null) {
+ return;
+ }
+
+ if (_effectiveIPController.text != value!.ip || effectivePort != value!.port) {
didChange(IPAndPort(ip: _effectiveIPController.text, port: effectivePort));
}
}
diff --git a/lib/components/IPField.dart b/lib/components/IPField.dart
index 38bc527..0abadd3 100644
--- a/lib/components/IPField.dart
+++ b/lib/components/IPField.dart
@@ -8,16 +8,16 @@ class IPField extends StatelessWidget {
final String help;
final bool ipOnly;
final bool autoFocus;
- final FocusNode focusNode;
- final FocusNode nextFocusNode;
- final ValueChanged onChanged;
+ final FocusNode? focusNode;
+ final FocusNode? nextFocusNode;
+ final ValueChanged? onChanged;
final EdgeInsetsGeometry textPadding;
- final TextInputAction textInputAction;
+ final TextInputAction? textInputAction;
final controller;
final textAlign;
const IPField(
- {Key key,
+ {Key? key,
this.ipOnly = false,
this.help = "ip address",
this.autoFocus = false,
@@ -33,7 +33,7 @@ class IPField extends StatelessWidget {
@override
Widget build(BuildContext context) {
var textStyle = CupertinoTheme.of(context).textTheme.textStyle;
- final double ipWidth = ipOnly ? Utils.textSize("000000000000000", textStyle).width + 12 : null;
+ final double? ipWidth = ipOnly ? Utils.textSize("000000000000000", textStyle).width + 12 : null;
return SizedBox(
width: ipWidth,
@@ -64,7 +64,7 @@ class IPTextInputFormatter extends TextInputFormatter {
(String substring) {
return whitelistedPattern
.allMatches(substring)
- .map((Match match) => match.group(0))
+ .map((Match match) => match.group(0)!)
.join()
.replaceAll(RegExp(r','), '.');
},
@@ -79,7 +79,7 @@ TextEditingValue _selectionAwareTextManipulation(
final int selectionStartIndex = value.selection.start;
final int selectionEndIndex = value.selection.end;
String manipulatedText;
- TextSelection manipulatedSelection;
+ TextSelection? manipulatedSelection;
if (selectionStartIndex < 0 || selectionEndIndex < 0) {
manipulatedText = substringManipulation(value.text);
} else {
diff --git a/lib/components/IPFormField.dart b/lib/components/IPFormField.dart
index 8156179..4811db9 100644
--- a/lib/components/IPFormField.dart
+++ b/lib/components/IPFormField.dart
@@ -9,15 +9,15 @@ import 'IPField.dart';
class IPFormField extends FormField {
//TODO: validator, auto-validate, enabled?
IPFormField({
- Key key,
+ Key? key,
ipOnly = false,
enableIPV6 = false,
help = "ip address",
autoFocus = false,
focusNode,
nextFocusNode,
- ValueChanged onChanged,
- FormFieldSetter onSaved,
+ ValueChanged? onChanged,
+ FormFieldSetter? onSaved,
textPadding = const EdgeInsets.all(6.0),
textInputAction,
initialValue,
@@ -41,7 +41,7 @@ class IPFormField extends FormField {
return null;
},
builder: (FormFieldState field) {
- final _IPFormField state = field;
+ final _IPFormField state = field as _IPFormField;
void onChangedHandler(String value) {
if (onChanged != null) {
@@ -64,7 +64,7 @@ class IPFormField extends FormField {
textAlign: textAlign),
field.hasError
? Text(
- field.errorText,
+ field.errorText!,
style: TextStyle(color: CupertinoColors.systemRed.resolveFrom(field.context), fontSize: 13),
textAlign: textAlign,
)
@@ -72,19 +72,19 @@ class IPFormField extends FormField {
]);
});
- final TextEditingController controller;
+ final TextEditingController? controller;
@override
_IPFormField createState() => _IPFormField();
}
class _IPFormField extends FormFieldState {
- TextEditingController _controller;
+ TextEditingController? _controller;
- TextEditingController get _effectiveController => widget.controller ?? _controller;
+ TextEditingController get _effectiveController => widget.controller ?? _controller!;
@override
- IPFormField get widget => super.widget;
+ IPFormField get widget => super.widget as IPFormField;
@override
void initState() {
@@ -92,7 +92,7 @@ class _IPFormField extends FormFieldState {
if (widget.controller == null) {
_controller = TextEditingController(text: widget.initialValue);
} else {
- widget.controller.addListener(_handleControllerChanged);
+ widget.controller!.addListener(_handleControllerChanged);
}
}
@@ -104,9 +104,9 @@ class _IPFormField extends FormFieldState {
widget.controller?.addListener(_handleControllerChanged);
if (oldWidget.controller != null && widget.controller == null)
- _controller = TextEditingController.fromValue(oldWidget.controller.value);
+ _controller = TextEditingController.fromValue(oldWidget.controller!.value);
if (widget.controller != null) {
- setValue(widget.controller.text);
+ setValue(widget.controller!.text);
if (oldWidget.controller == null) _controller = null;
}
}
@@ -122,7 +122,7 @@ class _IPFormField extends FormFieldState {
void reset() {
super.reset();
setState(() {
- _effectiveController.text = widget.initialValue;
+ _effectiveController.text = widget.initialValue ?? "";
});
}
diff --git a/lib/components/PlatformTextFormField.dart b/lib/components/PlatformTextFormField.dart
index 9129f0b..6ff9107 100644
--- a/lib/components/PlatformTextFormField.dart
+++ b/lib/components/PlatformTextFormField.dart
@@ -6,14 +6,14 @@ import 'package:mobile_nebula/components/SpecialTextField.dart';
class PlatformTextFormField extends FormField {
//TODO: auto-validate, enabled?
PlatformTextFormField(
- {Key key,
+ {Key? key,
widgetKey,
this.controller,
focusNode,
nextFocusNode,
- TextInputType keyboardType,
+ TextInputType? keyboardType,
textInputAction,
- List inputFormatters,
+ List? inputFormatters,
textAlign,
autofocus,
maxLines = 1,
@@ -25,10 +25,10 @@ class PlatformTextFormField extends FormField {
expands,
suffix,
textAlignVertical,
- String initialValue,
- String placeholder,
- FormFieldValidator validator,
- ValueChanged onSaved})
+ String? initialValue,
+ String? placeholder,
+ FormFieldValidator? validator,
+ ValueChanged? onSaved})
: super(
key: key,
initialValue: controller != null ? controller.text : (initialValue ?? ''),
@@ -41,7 +41,7 @@ class PlatformTextFormField extends FormField {
return null;
},
builder: (FormFieldState field) {
- final _PlatformTextFormFieldState state = field;
+ final _PlatformTextFormFieldState state = field as _PlatformTextFormFieldState;
void onChangedHandler(String value) {
if (onChanged != null) {
@@ -73,7 +73,7 @@ class PlatformTextFormField extends FormField {
suffix: suffix),
field.hasError
? Text(
- field.errorText,
+ field.errorText!,
style: TextStyle(color: CupertinoColors.systemRed.resolveFrom(field.context), fontSize: 13),
textAlign: textAlign,
)
@@ -81,19 +81,19 @@ class PlatformTextFormField extends FormField {
]);
});
- final TextEditingController controller;
+ final TextEditingController? controller;
@override
_PlatformTextFormFieldState createState() => _PlatformTextFormFieldState();
}
class _PlatformTextFormFieldState extends FormFieldState {
- TextEditingController _controller;
+ TextEditingController? _controller;
- TextEditingController get _effectiveController => widget.controller ?? _controller;
+ TextEditingController get _effectiveController => widget.controller ?? _controller!;
@override
- PlatformTextFormField get widget => super.widget;
+ PlatformTextFormField get widget => super.widget as PlatformTextFormField;
@override
void initState() {
@@ -101,7 +101,7 @@ class _PlatformTextFormFieldState extends FormFieldState {
if (widget.controller == null) {
_controller = TextEditingController(text: widget.initialValue);
} else {
- widget.controller.addListener(_handleControllerChanged);
+ widget.controller!.addListener(_handleControllerChanged);
}
}
@@ -113,9 +113,9 @@ class _PlatformTextFormFieldState extends FormFieldState {
widget.controller?.addListener(_handleControllerChanged);
if (oldWidget.controller != null && widget.controller == null)
- _controller = TextEditingController.fromValue(oldWidget.controller.value);
+ _controller = TextEditingController.fromValue(oldWidget.controller!.value);
if (widget.controller != null) {
- setValue(widget.controller.text);
+ setValue(widget.controller!.text);
if (oldWidget.controller == null) _controller = null;
}
}
@@ -131,7 +131,7 @@ class _PlatformTextFormFieldState extends FormFieldState {
void reset() {
super.reset();
setState(() {
- _effectiveController.text = widget.initialValue;
+ _effectiveController.text = widget.initialValue ?? "";
});
}
diff --git a/lib/components/SimplePage.dart b/lib/components/SimplePage.dart
index 3ce8a5a..d21bde4 100644
--- a/lib/components/SimplePage.dart
+++ b/lib/components/SimplePage.dart
@@ -13,9 +13,9 @@ enum SimpleScrollable {
class SimplePage extends StatelessWidget {
const SimplePage(
- {Key key,
- this.title,
- @required this.child,
+ {Key? key,
+ required this.title,
+ required this.child,
this.leadingAction,
this.trailingActions = const [],
this.scrollable = SimpleScrollable.vertical,
@@ -30,20 +30,20 @@ class SimplePage extends StatelessWidget {
final String title;
final Widget child;
final SimpleScrollable scrollable;
- final ScrollController scrollController;
+ final ScrollController? scrollController;
/// Set this to true to force draw a scrollbar without a scroll view, this is helpful for pages with Reorder-able listviews
/// This is set to true if you have any scrollable other than none
final bool scrollbar;
- final Widget bottomBar;
+ final Widget? bottomBar;
/// If no leading action is provided then a default "Back" widget than pops the page will be provided
- final Widget leadingAction;
+ final Widget? leadingAction;
final List trailingActions;
- final VoidCallback onRefresh;
- final VoidCallback onLoading;
- final RefreshController refreshController;
+ final VoidCallback? onRefresh;
+ final VoidCallback? onLoading;
+ final RefreshController? refreshController;
@override
Widget build(BuildContext context) {
@@ -72,7 +72,7 @@ class SimplePage extends StatelessWidget {
scrollController: scrollController,
onRefresh: onRefresh,
onLoading: onLoading,
- controller: refreshController,
+ controller: refreshController!,
child: realChild,
enablePullUp: onLoading != null,
enablePullDown: onRefresh != null,
@@ -88,7 +88,7 @@ class SimplePage extends StatelessWidget {
if (bottomBar != null) {
realChild = Column(children: [
Expanded(child: realChild),
- bottomBar,
+ bottomBar!,
]);
}
diff --git a/lib/components/SiteItem.dart b/lib/components/SiteItem.dart
index cf24fb8..10550e4 100644
--- a/lib/components/SiteItem.dart
+++ b/lib/components/SiteItem.dart
@@ -4,7 +4,7 @@ import 'package:mobile_nebula/models/Site.dart';
import 'package:mobile_nebula/services/utils.dart';
class SiteItem extends StatelessWidget {
- const SiteItem({Key key, this.site, this.onPressed}) : super(key: key);
+ const SiteItem({Key? key, required this.site, this.onPressed}) : super(key: key);
final Site site;
final onPressed;
@@ -27,8 +27,8 @@ class SiteItem extends StatelessWidget {
Widget _buildContent(BuildContext context) {
final border = BorderSide(color: Utils.configSectionBorder(context));
var ip = "Error";
- if (site.certInfo != null && site.certInfo.cert.details.ips.length > 0) {
- ip = site.certInfo.cert.details.ips[0];
+ if (site.certInfo != null && site.certInfo!.cert.details.ips.length > 0) {
+ ip = site.certInfo!.cert.details.ips[0];
}
return SpecialButton(
diff --git a/lib/components/SpecialButton.dart b/lib/components/SpecialButton.dart
index 381e9f4..7915418 100644
--- a/lib/components/SpecialButton.dart
+++ b/lib/components/SpecialButton.dart
@@ -5,15 +5,15 @@ 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})
+ const SpecialButton({Key? key, this.child, this.color, this.onPressed, this.useButtonTheme = false, this.decoration})
: super(key: key);
- final Widget child;
- final Color color;
+ final Widget? child;
+ final Color? color;
final bool useButtonTheme;
- final BoxDecoration decoration;
+ final BoxDecoration? decoration;
- final Function onPressed;
+ final GestureTapCallback? onPressed;
@override
_SpecialButtonState createState() => _SpecialButtonState();
@@ -59,7 +59,7 @@ class _SpecialButtonState extends State with SingleTickerProvider
child: Semantics(
button: true,
child: FadeTransition(
- opacity: _opacityAnimation,
+ opacity: _opacityAnimation!,
child: DefaultTextStyle(style: textStyle, child: Container(child: widget.child, color: widget.color)),
),
),
@@ -71,8 +71,8 @@ class _SpecialButtonState extends State with SingleTickerProvider
static const Duration kFadeInDuration = Duration(milliseconds: 100);
final Tween _opacityTween = Tween(begin: 1.0);
- AnimationController _animationController;
- Animation _opacityAnimation;
+ AnimationController? _animationController;
+ Animation? _opacityAnimation;
@override
void initState() {
@@ -82,7 +82,7 @@ class _SpecialButtonState extends State with SingleTickerProvider
value: 0.0,
vsync: this,
);
- _opacityAnimation = _animationController.drive(CurveTween(curve: Curves.decelerate)).drive(_opacityTween);
+ _opacityAnimation = _animationController!.drive(CurveTween(curve: Curves.decelerate)).drive(_opacityTween);
_setTween();
}
@@ -98,8 +98,7 @@ class _SpecialButtonState extends State with SingleTickerProvider
@override
void dispose() {
- _animationController.dispose();
- _animationController = null;
+ _animationController?.dispose();
super.dispose();
}
@@ -127,14 +126,14 @@ class _SpecialButtonState extends State with SingleTickerProvider
}
void _animate() {
- if (_animationController.isAnimating) {
+ if (_animationController == null || _animationController!.isAnimating) {
return;
}
final bool wasHeldDown = _buttonHeldDown;
final TickerFuture ticker = _buttonHeldDown
- ? _animationController.animateTo(1.0, duration: kFadeOutDuration)
- : _animationController.animateTo(0.0, duration: kFadeInDuration);
+ ? _animationController!.animateTo(1.0, duration: kFadeOutDuration)
+ : _animationController!.animateTo(0.0, duration: kFadeInDuration);
ticker.then((void value) {
if (mounted && wasHeldDown != _buttonHeldDown) {
diff --git a/lib/components/SpecialTextField.dart b/lib/components/SpecialTextField.dart
index af86a0e..1e09a6f 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,
+ {Key? key,
this.placeholder,
this.suffix,
this.controller,
@@ -30,43 +30,44 @@ class SpecialTextField extends StatefulWidget {
this.inputFormatters})
: super(key: key);
- final String placeholder;
- final TextEditingController controller;
- final FocusNode focusNode;
- final FocusNode nextFocusNode;
- final bool autocorrect;
- final int minLines;
- final int maxLines;
- final int maxLength;
- final MaxLengthEnforcement maxLengthEnforcement;
- final Widget suffix;
- final TextStyle style;
- final TextInputType keyboardType;
- final Brightness keyboardAppearance;
+ final String? placeholder;
+ final TextEditingController? controller;
+ final FocusNode? focusNode;
+ final FocusNode? nextFocusNode;
+ final bool? autocorrect;
+ final int? minLines;
+ final int? maxLines;
+ final int? maxLength;
+ final MaxLengthEnforcement? maxLengthEnforcement;
+ final Widget? suffix;
+ final TextStyle? style;
+ final TextInputType? keyboardType;
+ final Brightness? keyboardAppearance;
- final TextInputAction textInputAction;
- final TextCapitalization textCapitalization;
- final TextAlign textAlign;
- final TextAlignVertical textAlignVertical;
+ final TextInputAction? textInputAction;
+ final TextCapitalization? textCapitalization;
+ final TextAlign? textAlign;
+ final TextAlignVertical? textAlignVertical;
- final bool autofocus;
- final ValueChanged onChanged;
- final bool enabled;
- final List inputFormatters;
- final bool expands;
+ final bool? autofocus;
+ final ValueChanged? onChanged;
+ final bool? enabled;
+ final List? inputFormatters;
+ final bool? expands;
@override
_SpecialTextFieldState createState() => _SpecialTextFieldState();
}
class _SpecialTextFieldState extends State {
- List formatters;
+ List formatters = [];
@override
void initState() {
- formatters = widget.inputFormatters;
- if (formatters == null || formatters.length == 0) {
+ if (widget.inputFormatters == null || formatters.length == 0) {
formatters = [FilteringTextInputFormatter.allow(RegExp(r'[^\t]'))];
+ } else {
+ formatters = widget.inputFormatters!;
}
super.initState();
diff --git a/lib/components/config/ConfigButtonItem.dart b/lib/components/config/ConfigButtonItem.dart
index 73e9d92..955f9d5 100644
--- a/lib/components/config/ConfigButtonItem.dart
+++ b/lib/components/config/ConfigButtonItem.dart
@@ -5,9 +5,9 @@ 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({Key? key, this.content, this.onPressed}) : super(key: key);
- final Widget content;
+ final Widget? content;
final onPressed;
@override
diff --git a/lib/components/config/ConfigCheckboxItem.dart b/lib/components/config/ConfigCheckboxItem.dart
index 17b84a6..f013378 100644
--- a/lib/components/config/ConfigCheckboxItem.dart
+++ b/lib/components/config/ConfigCheckboxItem.dart
@@ -3,14 +3,15 @@ import 'package:mobile_nebula/components/SpecialButton.dart';
import 'package:mobile_nebula/services/utils.dart';
class ConfigCheckboxItem extends StatelessWidget {
- const ConfigCheckboxItem({Key key, this.label, this.content, this.labelWidth = 100, this.onChanged, this.checked})
+ const ConfigCheckboxItem(
+ {Key? key, this.label, this.content, this.labelWidth = 100, this.onChanged, this.checked = false})
: super(key: key);
- final Widget label;
- final Widget content;
+ final Widget? label;
+ final Widget? content;
final double labelWidth;
final bool checked;
- final Function onChanged;
+ final Function? onChanged;
@override
Widget build(BuildContext context) {
@@ -34,7 +35,7 @@ class ConfigCheckboxItem extends StatelessWidget {
child: item,
onPressed: () {
if (onChanged != null) {
- onChanged();
+ onChanged!();
}
},
);
diff --git a/lib/components/config/ConfigHeader.dart b/lib/components/config/ConfigHeader.dart
index 7e572de..5e5389e 100644
--- a/lib/components/config/ConfigHeader.dart
+++ b/lib/components/config/ConfigHeader.dart
@@ -4,15 +4,15 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
TextStyle basicTextStyle(BuildContext context) =>
- Platform.isIOS ? CupertinoTheme.of(context).textTheme.textStyle : Theme.of(context).textTheme.subtitle1;
+ Platform.isIOS ? CupertinoTheme.of(context).textTheme.textStyle : Theme.of(context).textTheme.subtitle1!;
const double _headerFontSize = 13.0;
class ConfigHeader extends StatelessWidget {
- const ConfigHeader({Key key, this.label, this.color}) : super(key: key);
+ const ConfigHeader({Key? key, required this.label, this.color}) : super(key: key);
final String label;
- final Color color;
+ final Color? color;
@override
Widget build(BuildContext context) {
diff --git a/lib/components/config/ConfigItem.dart b/lib/components/config/ConfigItem.dart
index f4fef54..7afec63 100644
--- a/lib/components/config/ConfigItem.dart
+++ b/lib/components/config/ConfigItem.dart
@@ -4,10 +4,14 @@ import 'package:mobile_nebula/services/utils.dart';
class ConfigItem extends StatelessWidget {
const ConfigItem(
- {Key key, this.label, this.content, this.labelWidth = 100, this.crossAxisAlignment = CrossAxisAlignment.center})
+ {Key? key,
+ this.label,
+ required this.content,
+ this.labelWidth = 100,
+ this.crossAxisAlignment = CrossAxisAlignment.center})
: super(key: key);
- final Widget label;
+ final Widget? label;
final Widget content;
final double labelWidth;
final CrossAxisAlignment crossAxisAlignment;
diff --git a/lib/components/config/ConfigPageItem.dart b/lib/components/config/ConfigPageItem.dart
index 09a7c68..800fed5 100644
--- a/lib/components/config/ConfigPageItem.dart
+++ b/lib/components/config/ConfigPageItem.dart
@@ -7,7 +7,7 @@ import 'package:mobile_nebula/services/utils.dart';
class ConfigPageItem extends StatelessWidget {
const ConfigPageItem(
- {Key key,
+ {Key? key,
this.label,
this.content,
this.labelWidth = 100,
@@ -15,8 +15,8 @@ class ConfigPageItem extends StatelessWidget {
this.crossAxisAlignment = CrossAxisAlignment.center})
: super(key: key);
- final Widget label;
- final Widget content;
+ final Widget? label;
+ final Widget? content;
final double labelWidth;
final CrossAxisAlignment crossAxisAlignment;
final onPressed;
@@ -28,8 +28,8 @@ class ConfigPageItem extends StatelessWidget {
if (Platform.isAndroid) {
final origTheme = Theme.of(context);
theme = origTheme.copyWith(
- textTheme:
- origTheme.textTheme.copyWith(button: origTheme.textTheme.button.copyWith(fontWeight: FontWeight.normal)));
+ textTheme: origTheme.textTheme
+ .copyWith(button: origTheme.textTheme.button!.copyWith(fontWeight: FontWeight.normal)));
return Theme(data: theme, child: _buildContent(context));
} else {
final origTheme = CupertinoTheme.of(context);
diff --git a/lib/components/config/ConfigSection.dart b/lib/components/config/ConfigSection.dart
index 6568f7f..544b960 100644
--- a/lib/components/config/ConfigSection.dart
+++ b/lib/components/config/ConfigSection.dart
@@ -4,12 +4,13 @@ import 'package:mobile_nebula/services/utils.dart';
import 'ConfigHeader.dart';
class ConfigSection extends StatelessWidget {
- const ConfigSection({Key key, this.label, this.children, this.borderColor, this.labelColor}) : super(key: key);
+ const ConfigSection({Key? key, this.label, required this.children, this.borderColor, this.labelColor})
+ : super(key: key);
final List children;
- final String label;
- final Color borderColor;
- final Color labelColor;
+ final String? label;
+ final Color? borderColor;
+ final Color? labelColor;
@override
Widget build(BuildContext context) {
@@ -32,7 +33,7 @@ class ConfigSection extends StatelessWidget {
}
return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
- label != null ? ConfigHeader(label: label, color: labelColor) : Container(height: 20),
+ label != null ? ConfigHeader(label: label!, color: labelColor) : Container(height: 20),
Container(
decoration:
BoxDecoration(border: Border(top: border, bottom: border), color: Utils.configItemBackground(context)),
diff --git a/lib/components/config/ConfigTextItem.dart b/lib/components/config/ConfigTextItem.dart
index b911723..3d0eaf0 100644
--- a/lib/components/config/ConfigTextItem.dart
+++ b/lib/components/config/ConfigTextItem.dart
@@ -5,10 +5,12 @@ import 'package:flutter/material.dart';
import 'package:mobile_nebula/components/SpecialTextField.dart';
class ConfigTextItem extends StatelessWidget {
- const ConfigTextItem({Key key, this.placeholder, this.controller, this.style = const TextStyle(fontFamily: 'RobotoMono')}) : super(key: key);
+ const ConfigTextItem(
+ {Key? key, this.placeholder, this.controller, this.style = const TextStyle(fontFamily: 'RobotoMono')})
+ : super(key: key);
- final String placeholder;
- final TextEditingController controller;
+ final String? placeholder;
+ final TextEditingController? controller;
final TextStyle style;
@override
diff --git a/lib/models/CIDR.dart b/lib/models/CIDR.dart
index 788805e..c5746a8 100644
--- a/lib/models/CIDR.dart
+++ b/lib/models/CIDR.dart
@@ -1,5 +1,5 @@
class CIDR {
- CIDR({this.ip, this.bits});
+ CIDR({this.ip = '', this.bits = 0});
String ip;
int bits;
@@ -13,13 +13,15 @@ class CIDR {
return toString();
}
- CIDR.fromString(String val) {
+ factory CIDR.fromString(String val) {
final parts = val.split('/');
if (parts.length != 2) {
throw 'Invalid CIDR string';
}
- ip = parts[0];
- bits = int.parse(parts[1]);
+ return CIDR(
+ ip: parts[0],
+ bits: int.parse(parts[1]),
+ );
}
}
diff --git a/lib/models/Certificate.dart b/lib/models/Certificate.dart
index 64bffc2..b48d984 100644
--- a/lib/models/Certificate.dart
+++ b/lib/models/Certificate.dart
@@ -1,7 +1,7 @@
class CertificateInfo {
Certificate cert;
- String rawCert;
- CertificateValidity validity;
+ String? rawCert;
+ CertificateValidity? validity;
CertificateInfo.debug({this.rawCert = ""})
: this.cert = Certificate.debug(),
@@ -12,10 +12,10 @@ class CertificateInfo {
rawCert = json['RawCert'],
validity = CertificateValidity.fromJson(json['Validity']);
- CertificateInfo({this.cert, this.rawCert, this.validity});
+ CertificateInfo({required this.cert, this.rawCert, this.validity});
static List fromJsonList(List list) {
- return list.map((v) => CertificateInfo.fromJson(v));
+ return list.map((v) => CertificateInfo.fromJson(v)).toList();
}
}
@@ -59,8 +59,8 @@ class CertificateDetails {
CertificateDetails.fromJson(Map json)
: name = json['name'],
- notBefore = DateTime.tryParse(json['notBefore']),
- notAfter = DateTime.tryParse(json['notAfter']),
+ notBefore = DateTime.parse(json['notBefore']),
+ notAfter = DateTime.parse(json['notAfter']),
publicKey = json['publicKey'],
groups = List.from(json['groups']),
ips = List.from(json['ips']),
diff --git a/lib/models/HostInfo.dart b/lib/models/HostInfo.dart
index 82204e5..d90851a 100644
--- a/lib/models/HostInfo.dart
+++ b/lib/models/HostInfo.dart
@@ -6,31 +6,48 @@ class HostInfo {
int remoteIndex;
List remoteAddresses;
int cachedPackets;
- Certificate cert;
- UDPAddress currentRemote;
+ Certificate? cert;
+ UDPAddress? currentRemote;
int messageCounter;
- HostInfo.fromJson(Map json) {
- vpnIp = json['vpnIp'];
- localIndex = json['localIndex'];
- remoteIndex = json['remoteIndex'];
- cachedPackets = json['cachedPackets'];
+ HostInfo({
+ required this.vpnIp,
+ required this.localIndex,
+ required this.remoteIndex,
+ required this.remoteAddresses,
+ required this.cachedPackets,
+ required this.messageCounter,
+ this.cert,
+ this.currentRemote,
+ });
+ factory HostInfo.fromJson(Map json) {
+ UDPAddress? currentRemote;
if (json['currentRemote'] != null) {
currentRemote = UDPAddress.fromJson(json['currentRemote']);
}
+ Certificate? cert;
if (json['cert'] != null) {
cert = Certificate.fromJson(json['cert']);
}
List addrs = json['remoteAddrs'];
- remoteAddresses = [];
- addrs?.forEach((val) {
+ List remoteAddresses = [];
+ addrs.forEach((val) {
remoteAddresses.add(UDPAddress.fromJson(val));
});
- messageCounter = json['messageCounter'];
+ return HostInfo(
+ vpnIp: json['vpnIp'],
+ localIndex: json['localIndex'],
+ remoteIndex: json['remoteIndex'],
+ remoteAddresses: remoteAddresses,
+ cachedPackets: json['cachedPackets'],
+ messageCounter: json['messageCounter'],
+ cert: cert,
+ currentRemote: currentRemote,
+ );
}
}
diff --git a/lib/models/Hostmap.dart b/lib/models/Hostmap.dart
index f30cdd6..ee899c4 100644
--- a/lib/models/Hostmap.dart
+++ b/lib/models/Hostmap.dart
@@ -5,5 +5,5 @@ class Hostmap {
List destinations;
bool lighthouse;
- Hostmap({this.nebulaIp, this.destinations, this.lighthouse});
+ Hostmap({required this.nebulaIp, required this.destinations, required this.lighthouse});
}
diff --git a/lib/models/IPAndPort.dart b/lib/models/IPAndPort.dart
index 76de2d5..82aaf76 100644
--- a/lib/models/IPAndPort.dart
+++ b/lib/models/IPAndPort.dart
@@ -1,12 +1,12 @@
class IPAndPort {
- String ip;
- int port;
+ String? ip;
+ int? port;
IPAndPort({this.ip, this.port});
@override
String toString() {
- if (ip.contains(':')) {
+ if (ip != null && ip!.contains(':')) {
return '[$ip]:$port';
}
@@ -17,10 +17,13 @@ class IPAndPort {
return toString();
}
- IPAndPort.fromString(String val) {
+ factory IPAndPort.fromString(String val) {
//TODO: Uri.parse is as close as I could get to parsing both ipv4 and v6 addresses with a port without bringing a whole mess of code into here
final uri = Uri.parse("ugh://$val");
- this.ip = uri.host;
- this.port = uri.port;
+
+ return IPAndPort(
+ ip: uri.host,
+ port: uri.port,
+ );
}
}
diff --git a/lib/models/Site.dart b/lib/models/Site.dart
index 3af3067..e630208 100644
--- a/lib/models/Site.dart
+++ b/lib/models/Site.dart
@@ -12,107 +12,75 @@ var uuid = Uuid();
class Site {
static const platform = MethodChannel('net.defined.mobileNebula/NebulaVpnService');
- EventChannel _updates;
+ 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();
// Identifiers
- String name;
- String id;
+ late String name;
+ late String id;
// static_host_map
- Map staticHostmap;
- List unsafeRoutes;
+ late Map staticHostmap;
+ late List unsafeRoutes;
// pki fields
- List ca;
- CertificateInfo certInfo;
- String key;
+ late List ca;
+ String? key;
+ late CertificateInfo? certInfo;
// lighthouse options
- int lhDuration; // in seconds
+ late int lhDuration; // in seconds
// listen settings
- int port;
- int mtu;
+ late int port;
+ late int mtu;
- String cipher;
- int sortKey;
- bool connected;
- String status;
- String logFile;
- String logVerbosity;
+ late String cipher;
+ late int sortKey;
+ late bool connected;
+ late String status;
+ late String logFile;
+ late String logVerbosity;
// A list of errors encountered while loading the site
- List errors;
+ late List errors;
- Site(
- {this.name,
- id,
- staticHostmap,
- ca,
- this.certInfo,
- this.lhDuration = 0,
- this.port = 0,
- this.cipher = "aes",
- this.sortKey,
- this.mtu = 1300,
- this.connected,
- this.status,
- this.logFile,
- this.logVerbosity = 'info',
- errors,
- unsafeRoutes})
- : staticHostmap = staticHostmap ?? {},
- unsafeRoutes = unsafeRoutes ?? [],
- errors = errors ?? [],
- ca = ca ?? [],
- id = id ?? uuid.v4();
-
- Site.fromJson(Map json) {
- name = json['name'];
- id = json['id'];
-
- Map rawHostmap = json['staticHostmap'];
- staticHostmap = {};
- rawHostmap.forEach((key, val) {
- staticHostmap[key] = StaticHost.fromJson(val);
- });
-
- List rawUnsafeRoutes = json['unsafeRoutes'];
- unsafeRoutes = [];
- if (rawUnsafeRoutes != null) {
- rawUnsafeRoutes.forEach((val) {
- unsafeRoutes.add(UnsafeRoute.fromJson(val));
- });
- }
-
- List rawCA = json['ca'];
- ca = [];
- rawCA.forEach((val) {
- ca.add(CertificateInfo.fromJson(val));
- });
-
- if (json['cert'] != null) {
- certInfo = CertificateInfo.fromJson(json['cert']);
- }
-
- lhDuration = json['lhDuration'];
- port = json['port'];
- mtu = json['mtu'];
- cipher = json['cipher'];
- sortKey = json['sortKey'];
- logFile = json['logFile'];
- logVerbosity = json['logVerbosity'];
- connected = json['connected'] ?? false;
- status = json['status'] ?? "";
-
- errors = [];
- List rawErrors = json["errors"];
- rawErrors.forEach((error) {
- errors.add(error);
- });
+ Site({
+ String name = '',
+ String? id,
+ Map? staticHostmap,
+ List? 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',
+ List? errors,
+ List? unsafeRoutes,
+ }) {
+ 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 ?? [];
_updates = EventChannel('net.defined.nebula/$id');
_updates.receiveBroadcastStream().listen((d) {
@@ -128,10 +96,60 @@ class Site {
var error = err as PlatformException;
this.status = error.details['status'];
this.connected = error.details['connected'];
- _change.addError(error.message);
+ _change.addError(error.message ?? 'An unexpected error occurred');
});
}
+ factory Site.fromJson(Map json) {
+ Map rawHostmap = json['staticHostmap'];
+ Map staticHostmap = {};
+ rawHostmap.forEach((key, val) {
+ staticHostmap[key] = StaticHost.fromJson(val);
+ });
+
+ List rawUnsafeRoutes = json['unsafeRoutes'];
+ List unsafeRoutes = [];
+ rawUnsafeRoutes.forEach((val) {
+ unsafeRoutes.add(UnsafeRoute.fromJson(val));
+ });
+
+ List rawCA = json['ca'];
+ List ca = [];
+ rawCA.forEach((val) {
+ ca.add(CertificateInfo.fromJson(val));
+ });
+
+ CertificateInfo? certInfo;
+ if (json['cert'] != null) {
+ certInfo = CertificateInfo.fromJson(json['cert']);
+ }
+
+ List rawErrors = json["errors"];
+ List errors = [];
+ rawErrors.forEach((error) {
+ errors.add(error);
+ });
+
+ return Site(
+ name: json['name'],
+ id: json['id'],
+ staticHostmap: staticHostmap,
+ ca: ca,
+ certInfo: certInfo,
+ lhDuration: json['lhDuration'],
+ port: json['port'],
+ cipher: json['cipher'],
+ sortKey: json['sortKey'],
+ mtu: json['mtu'],
+ connected: json['connected'] ?? false,
+ status: json['status'] ?? "",
+ logFile: json['logFile'],
+ logVerbosity: json['logVerbosity'],
+ errors: errors,
+ unsafeRoutes: unsafeRoutes,
+ );
+ }
+
Stream onChange() {
return _change.stream;
}
@@ -142,10 +160,9 @@ class Site {
'id': id,
'staticHostmap': staticHostmap,
'unsafeRoutes': unsafeRoutes,
- 'ca': ca?.map((cert) {
- return cert.rawCert;
- })?.join('\n') ??
- "",
+ 'ca': ca.map((cert) {
+ return cert.rawCert;
+ }).join('\n'),
'cert': certInfo?.rawCert,
'key': key,
'lhDuration': lhDuration,
@@ -260,7 +277,7 @@ class Site {
_change.close();
}
- Future getHostInfo(String vpnIp, bool pending) async {
+ Future getHostInfo(String vpnIp, bool pending) async {
try {
var ret = await platform
.invokeMethod("active.getHostInfo", {"id": id, "vpnIp": vpnIp, "pending": pending});
@@ -277,7 +294,7 @@ class Site {
}
}
- Future setRemoteForTunnel(String vpnIp, String addr) async {
+ Future setRemoteForTunnel(String vpnIp, String addr) async {
try {
var ret = await platform
.invokeMethod("active.setRemoteForTunnel", {"id": id, "vpnIp": vpnIp, "addr": addr});
diff --git a/lib/models/StaticHosts.dart b/lib/models/StaticHosts.dart
index 1e30d89..1402f5c 100644
--- a/lib/models/StaticHosts.dart
+++ b/lib/models/StaticHosts.dart
@@ -4,11 +4,9 @@ class StaticHost {
bool lighthouse;
List destinations;
- StaticHost({this.lighthouse, this.destinations});
-
- StaticHost.fromJson(Map json) {
- lighthouse = json['lighthouse'];
+ StaticHost({required this.lighthouse, required this.destinations});
+ factory StaticHost.fromJson(Map json) {
var list = json['destinations'] as List;
var result = [];
@@ -16,7 +14,10 @@ class StaticHost {
result.add(IPAndPort.fromString(item));
});
- destinations = result;
+ return StaticHost(
+ lighthouse: json['lighthouse'],
+ destinations: result,
+ );
}
Map toJson() {
diff --git a/lib/models/UnsafeRoute.dart b/lib/models/UnsafeRoute.dart
index 684fed8..0486290 100644
--- a/lib/models/UnsafeRoute.dart
+++ b/lib/models/UnsafeRoute.dart
@@ -1,12 +1,14 @@
class UnsafeRoute {
- String route;
- String via;
+ String? route;
+ String? via;
UnsafeRoute({this.route, this.via});
- UnsafeRoute.fromJson(Map json) {
- route = json['route'];
- via = json['via'];
+ factory UnsafeRoute.fromJson(Map json) {
+ return UnsafeRoute(
+ route: json['route'],
+ via: json['via'],
+ );
}
Map toJson() {
diff --git a/lib/screens/AboutScreen.dart b/lib/screens/AboutScreen.dart
index 45de7d0..31879cc 100644
--- a/lib/screens/AboutScreen.dart
+++ b/lib/screens/AboutScreen.dart
@@ -9,7 +9,7 @@ import 'package:mobile_nebula/services/utils.dart';
import 'package:package_info/package_info.dart';
class AboutScreen extends StatefulWidget {
- const AboutScreen({Key key}) : super(key: key);
+ const AboutScreen({Key? key}) : super(key: key);
@override
_AboutScreenState createState() => _AboutScreenState();
@@ -17,7 +17,7 @@ class AboutScreen extends StatefulWidget {
class _AboutScreenState extends State {
bool ready = false;
- PackageInfo packageInfo;
+ PackageInfo? packageInfo;
@override
void initState() {
@@ -33,6 +33,7 @@ class _AboutScreenState extends State {
@override
Widget build(BuildContext context) {
+ // packageInfo is null until ready is true
if (!ready) {
return Center(
child: PlatformCircularProgressIndicator(cupertino: (_, __) {
@@ -48,13 +49,17 @@ class _AboutScreenState extends State {
ConfigItem(
label: Text('App version'),
labelWidth: 150,
- content: _buildText('${packageInfo.version}-${packageInfo.buildNumber} (sha: $gitSha)')),
+ content: _buildText('${packageInfo!.version}-${packageInfo!.buildNumber} (sha: $gitSha)')),
ConfigItem(
label: Text('Nebula version'), labelWidth: 150, content: _buildText('$nebulaVersion ($goVersion)')),
ConfigItem(
- label: Text('Flutter version'), labelWidth: 150, content: _buildText(flutterVersion['frameworkVersion'])),
+ label: Text('Flutter version'),
+ labelWidth: 150,
+ content: _buildText(flutterVersion['frameworkVersion'] ?? 'Unknown')),
ConfigItem(
- label: Text('Dart version'), labelWidth: 150, content: _buildText(flutterVersion['dartSdkVersion'])),
+ label: Text('Dart version'),
+ labelWidth: 150,
+ content: _buildText(flutterVersion['dartSdkVersion'] ?? 'Unknown')),
]),
ConfigSection(children: [
//TODO: wire up these other pages
diff --git a/lib/screens/HostInfoScreen.dart b/lib/screens/HostInfoScreen.dart
index cc5a9c9..e8cd601 100644
--- a/lib/screens/HostInfoScreen.dart
+++ b/lib/screens/HostInfoScreen.dart
@@ -14,13 +14,19 @@ import 'package:mobile_nebula/services/utils.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart';
class HostInfoScreen extends StatefulWidget {
- const HostInfoScreen({Key key, this.hostInfo, this.isLighthouse, this.pending, this.onChanged, this.site})
- : super(key: key);
+ const HostInfoScreen({
+ Key? key,
+ required this.hostInfo,
+ required this.isLighthouse,
+ required this.pending,
+ this.onChanged,
+ required this.site,
+ }) : super(key: key);
final bool isLighthouse;
final bool pending;
final HostInfo hostInfo;
- final Function onChanged;
+ final Function? onChanged;
final Site site;
@override
@@ -30,7 +36,7 @@ class HostInfoScreen extends StatefulWidget {
//TODO: have a config option to refresh hostmaps on a cadence (applies to 3 screens so far)
class _HostInfoScreenState extends State {
- HostInfo hostInfo;
+ late HostInfo hostInfo;
RefreshController refreshController = RefreshController(initialRefresh: false);
@override
@@ -64,9 +70,9 @@ class _HostInfoScreenState extends State {
? ConfigPageItem(
label: Text('Certificate'),
labelWidth: 150,
- content: Text(hostInfo.cert.details.name),
+ content: Text(hostInfo.cert!.details.name),
onPressed: () => Utils.openPage(
- context, (context) => CertificateDetailsScreen(certInfo: CertificateInfo(cert: hostInfo.cert))))
+ context, (context) => CertificateDetailsScreen(certInfo: CertificateInfo(cert: hostInfo.cert!))))
: Container(),
]);
}
@@ -116,7 +122,7 @@ class _HostInfoScreenState extends State {
_setHostInfo(h);
}
} catch (err) {
- Utils.popError(context, 'Error while changing the remote', err);
+ Utils.popError(context, 'Error while changing the remote', err.toString());
}
},
));
@@ -156,11 +162,11 @@ class _HostInfoScreenState extends State {
try {
await widget.site.closeTunnel(hostInfo.vpnIp);
if (widget.onChanged != null) {
- widget.onChanged();
+ widget.onChanged!();
}
Navigator.pop(context);
} catch (err) {
- Utils.popError(context, 'Error while trying to close the tunnel', err);
+ Utils.popError(context, 'Error while trying to close the tunnel', err.toString());
}
}, deleteLabel: 'Close'))));
}
@@ -174,7 +180,7 @@ class _HostInfoScreenState extends State {
_setHostInfo(h);
} catch (err) {
- Utils.popError(context, 'Failed to refresh host info', err);
+ Utils.popError(context, 'Failed to refresh host info', err.toString());
}
}
diff --git a/lib/screens/MainScreen.dart b/lib/screens/MainScreen.dart
index a93ad01..3e77819 100644
--- a/lib/screens/MainScreen.dart
+++ b/lib/screens/MainScreen.dart
@@ -23,7 +23,7 @@ import 'package:uuid/uuid.dart';
//TODO: add refresh
class MainScreen extends StatefulWidget {
- const MainScreen({Key key}) : super(key: key);
+ const MainScreen({Key? key}) : super(key: key);
@override
_MainScreenState createState() => _MainScreenState();
@@ -31,9 +31,9 @@ class MainScreen extends StatefulWidget {
class _MainScreenState extends State {
bool ready = false;
- List sites;
+ List? sites;
// A set of widgets to display in a column that represents an error blocking us from moving forward entirely
- List error;
+ List? error;
static const platform = MethodChannel('net.defined.mobileNebula/NebulaVpnService');
@@ -72,11 +72,14 @@ class _MainScreenState extends State {
Widget _buildBody() {
if (error != null) {
- return Center(child: Padding(child: Column(
- mainAxisAlignment: MainAxisAlignment.center,
- crossAxisAlignment: CrossAxisAlignment.center,
- children: error,
- ), padding: EdgeInsets.symmetric(vertical: 0, horizontal: 10)));
+ return Center(
+ child: Padding(
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: error!,
+ ),
+ padding: EdgeInsets.symmetric(vertical: 0, horizontal: 10)));
}
if (!ready) {
@@ -87,10 +90,6 @@ class _MainScreenState extends State {
);
}
- if (sites == null || sites.length == 0) {
- return _buildNoSites();
- }
-
return _buildSites();
}
@@ -112,8 +111,12 @@ class _MainScreenState extends State {
}
Widget _buildSites() {
+ if (sites == null || sites!.length == 0) {
+ return _buildNoSites();
+ }
+
List items = [];
- sites.forEach((site) {
+ sites!.forEach((site) {
items.add(SiteItem(
key: Key(site.id),
site: site,
@@ -134,17 +137,17 @@ class _MainScreenState extends State {
}
setState(() {
- final Site moved = sites.removeAt(oldI);
- sites.insert(newI, moved);
+ final Site moved = sites!.removeAt(oldI);
+ sites!.insert(newI, moved);
});
for (var i = min(oldI, newI); i <= max(oldI, newI); i++) {
- sites[i].sortKey = i;
+ sites![i].sortKey = i;
try {
- await sites[i].save();
+ await sites![i].save();
} catch (err) {
//TODO: display error at the end
- print('ERR ${sites[i].name} - $err');
+ print('ERR ${sites![i].name} - $err');
}
}
@@ -209,31 +212,27 @@ rmXnR1yvDZi1VPVmnNVY8NMsQpEpbbYlq7rul+ByQvg=
if (Platform.isAndroid) {
try {
await platform.invokeMethod("android.requestPermissions");
-
} on PlatformException catch (err) {
if (err.code == "PERMISSIONS") {
setState(() {
error = [
- Text("Permissions Required",
- style: TextStyle(fontWeight: FontWeight.bold)),
+ Text("Permissions Required", style: TextStyle(fontWeight: FontWeight.bold)),
Text(
- "VPN permissions are required for nebula to run, click the button below request and accept the appropriate permissions.",
- textAlign: TextAlign.center
- ),
+ "VPN permissions are required for nebula to run, click the button below request and accept the appropriate permissions.",
+ textAlign: TextAlign.center),
ElevatedButton(
- onPressed: () {
- error = null;
- _loadSites();
- },
- child: Text("Request Permissions")
- ),
+ onPressed: () {
+ error = null;
+ _loadSites();
+ },
+ child: Text("Request Permissions")),
];
});
} else {
setState(() {
error = [
Text("Unknown Error", style: TextStyle(fontWeight: FontWeight.bold)),
- Text(err.message, textAlign: TextAlign.center)
+ Text(err.message ?? 'An unknown error occurred', textAlign: TextAlign.center)
];
});
}
@@ -241,7 +240,7 @@ rmXnR1yvDZi1VPVmnNVY8NMsQpEpbbYlq7rul+ByQvg=
setState(() {
error = [
Text("Unknown Error", style: TextStyle(fontWeight: FontWeight.bold)),
- Text(err.message, textAlign: TextAlign.center)
+ Text(err.toString(), textAlign: TextAlign.center)
];
});
}
@@ -264,12 +263,12 @@ rmXnR1yvDZi1VPVmnNVY8NMsQpEpbbYlq7rul+ByQvg=
setState(() {});
}, onError: (err) {
setState(() {});
- if (ModalRoute.of(context).isCurrent) {
+ if (ModalRoute.of(context)!.isCurrent) {
Utils.popError(context, "${site.name} Error", err);
}
});
- sites.add(site);
+ sites!.add(site);
} catch (err) {
//TODO: handle error
print("$err site config: $rawSite");
@@ -288,7 +287,7 @@ rmXnR1yvDZi1VPVmnNVY8NMsQpEpbbYlq7rul+ByQvg=
"1 or more sites have errors and need your attention, problem sites have a red border.");
}
- sites.sort((a, b) {
+ sites!.sort((a, b) {
return a.sortKey - b.sortKey;
});
diff --git a/lib/screens/SiteDetailScreen.dart b/lib/screens/SiteDetailScreen.dart
index e2f8d93..9ed698d 100644
--- a/lib/screens/SiteDetailScreen.dart
+++ b/lib/screens/SiteDetailScreen.dart
@@ -20,24 +20,24 @@ import 'package:pull_to_refresh/pull_to_refresh.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, this.site, this.onChanged}) : super(key: key);
+ const SiteDetailScreen({Key? key, required this.site, this.onChanged}) : super(key: key);
final Site site;
- final Function onChanged;
+ final Function? onChanged;
@override
_SiteDetailScreenState createState() => _SiteDetailScreenState();
}
class _SiteDetailScreenState extends State {
- Site site;
- StreamSubscription onChange;
+ late Site site;
+ late StreamSubscription onChange;
static const platform = MethodChannel('net.defined.mobileNebula/NebulaVpnService');
bool changed = false;
- List activeHosts;
- List pendingHosts;
+ List? activeHosts;
+ List? pendingHosts;
RefreshController refreshController = RefreshController(initialRefresh: false);
- bool lastState;
+ late bool lastState;
@override
void initState() {
@@ -80,7 +80,7 @@ class _SiteDetailScreenState extends State {
title: site.name,
leadingAction: Utils.leadingBackWidget(context, onPressed: () {
if (changed && widget.onChanged != null) {
- widget.onChanged();
+ widget.onChanged!();
}
Navigator.pop(context);
}),
@@ -162,13 +162,13 @@ class _SiteDetailScreenState extends State {
if (activeHosts == null) {
active = SizedBox(height: 20, width: 20, child: PlatformCircularProgressIndicator());
} else {
- active = Text(Utils.itemCountFormat(activeHosts.length, singleSuffix: "tunnel", multiSuffix: "tunnels"));
+ active = Text(Utils.itemCountFormat(activeHosts!.length, singleSuffix: "tunnel", multiSuffix: "tunnels"));
}
if (pendingHosts == null) {
pending = SizedBox(height: 20, width: 20, child: PlatformCircularProgressIndicator());
} else {
- pending = Text(Utils.itemCountFormat(pendingHosts.length, singleSuffix: "tunnel", multiSuffix: "tunnels"));
+ pending = Text(Utils.itemCountFormat(pendingHosts!.length, singleSuffix: "tunnel", multiSuffix: "tunnels"));
}
return ConfigSection(
@@ -176,11 +176,13 @@ class _SiteDetailScreenState extends State {
children: [
ConfigPageItem(
onPressed: () {
+ if (activeHosts == null) return;
+
Utils.openPage(
context,
(context) => SiteTunnelsScreen(
pending: false,
- tunnels: activeHosts,
+ tunnels: activeHosts!,
site: site,
onChanged: (hosts) {
setState(() {
@@ -192,11 +194,13 @@ class _SiteDetailScreenState extends State {
content: Container(alignment: Alignment.centerRight, child: active)),
ConfigPageItem(
onPressed: () {
+ if (pendingHosts == null) return;
+
Utils.openPage(
context,
(context) => SiteTunnelsScreen(
pending: true,
- tunnels: pendingHosts,
+ tunnels: pendingHosts!,
site: site,
onChanged: (hosts) {
setState(() {
@@ -250,7 +254,7 @@ class _SiteDetailScreenState extends State {
pendingHosts = maps["pending"];
setState(() {});
} catch (err) {
- Utils.popError(context, 'Error while fetching hostmaps', err);
+ Utils.popError(context, 'Error while fetching hostmaps', err.toString());
}
}
@@ -267,7 +271,7 @@ class _SiteDetailScreenState extends State {
}
if (widget.onChanged != null) {
- widget.onChanged();
+ widget.onChanged!();
}
return true;
}
diff --git a/lib/screens/SiteLogsScreen.dart b/lib/screens/SiteLogsScreen.dart
index 648de1a..e7b8944 100644
--- a/lib/screens/SiteLogsScreen.dart
+++ b/lib/screens/SiteLogsScreen.dart
@@ -11,7 +11,7 @@ import 'package:mobile_nebula/services/utils.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart';
class SiteLogsScreen extends StatefulWidget {
- const SiteLogsScreen({Key key, this.site}) : super(key: key);
+ const SiteLogsScreen({Key? key, required this.site}) : super(key: key);
final Site site;
diff --git a/lib/screens/SiteTunnelsScreen.dart b/lib/screens/SiteTunnelsScreen.dart
index 02cf1f7..c587e9c 100644
--- a/lib/screens/SiteTunnelsScreen.dart
+++ b/lib/screens/SiteTunnelsScreen.dart
@@ -10,26 +10,28 @@ import 'package:mobile_nebula/services/utils.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart';
class SiteTunnelsScreen extends StatefulWidget {
- const SiteTunnelsScreen({Key key, this.site, this.tunnels, this.pending, this.onChanged}) : super(key: key);
+ const SiteTunnelsScreen(
+ {Key? key, required this.site, required this.tunnels, required this.pending, required this.onChanged})
+ : super(key: key);
final Site site;
final List tunnels;
final bool pending;
- final Function(List) onChanged;
+ final Function(List)? onChanged;
@override
_SiteTunnelsScreenState createState() => _SiteTunnelsScreenState();
}
class _SiteTunnelsScreenState extends State {
- Site site;
- List tunnels;
+ late Site site;
+ late List tunnels;
RefreshController refreshController = RefreshController(initialRefresh: false);
@override
void initState() {
site = widget.site;
- tunnels = widget.tunnels ?? [];
+ tunnels = widget.tunnels;
_sortTunnels();
super.initState();
}
@@ -67,7 +69,7 @@ class _SiteTunnelsScreenState extends State {
})),
label: Row(children: [Padding(child: icon, padding: EdgeInsets.only(right: 10)), Text(hostInfo.vpnIp)]),
labelWidth: ipWidth,
- content: Container(alignment: Alignment.centerRight, child: Text(hostInfo.cert?.details?.name ?? "")),
+ content: Container(alignment: Alignment.centerRight, child: Text(hostInfo.cert?.details.name ?? "")),
));
});
@@ -121,11 +123,11 @@ class _SiteTunnelsScreenState extends State {
_sortTunnels();
if (widget.onChanged != null) {
- widget.onChanged(tunnels);
+ widget.onChanged!(tunnels);
}
setState(() {});
} catch (err) {
- Utils.popError(context, 'Error while fetching hostmap', err);
+ Utils.popError(context, 'Error while fetching hostmap', err.toString());
}
}
}
diff --git a/lib/screens/siteConfig/AddCertificateScreen.dart b/lib/screens/siteConfig/AddCertificateScreen.dart
index 0e028ea..38270a0 100644
--- a/lib/screens/siteConfig/AddCertificateScreen.dart
+++ b/lib/screens/siteConfig/AddCertificateScreen.dart
@@ -1,9 +1,9 @@
import 'dart:convert';
-import 'package:barcode_scan/barcode_scan.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
+import 'package:flutter_barcode_scanner/flutter_barcode_scanner.dart';
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
import 'package:mobile_nebula/components/SimplePage.dart';
import 'package:mobile_nebula/components/config/ConfigButtonItem.dart';
@@ -20,16 +20,24 @@ class CertificateResult {
CertificateInfo certInfo;
String key;
- CertificateResult({this.certInfo, this.key});
+ CertificateResult({required this.certInfo, required this.key});
}
class AddCertificateScreen extends StatefulWidget {
- const AddCertificateScreen({Key key, this.onSave, this.onReplace, this.pubKey, this.privKey}) : super(key: key);
+ const AddCertificateScreen({
+ Key? key,
+ this.onSave,
+ this.onReplace,
+ required this.pubKey,
+ required this.privKey,
+ }) : super(key: key);
- // onSave will pop a new CertificateDetailsScreen
- final ValueChanged onSave;
- // onReplace will return the CertificateResult, assuming the previous screen is a CertificateDetailsScreen
- final ValueChanged onReplace;
+ // onSave will pop a new CertificateDetailsScreen.
+ // If onSave is null, onReplace must be set.
+ final ValueChanged? onSave;
+ // onReplace will return the CertificateResult, assuming the previous screen is a CertificateDetailsScreen.
+ // If onReplace is null, onSave must be set.
+ final ValueChanged? onReplace;
final String pubKey;
final String privKey;
@@ -39,7 +47,7 @@ class AddCertificateScreen extends StatefulWidget {
}
class _AddCertificateScreenState extends State {
- String pubKey;
+ late String pubKey;
bool showKey = false;
String inputType = 'paste';
@@ -98,9 +106,11 @@ class _AddCertificateScreenState extends State {
child: CupertinoSlidingSegmentedControl(
groupValue: inputType,
onValueChanged: (v) {
- setState(() {
- inputType = v;
- });
+ if (v != null) {
+ setState(() {
+ inputType = v;
+ });
+ }
},
children: {
'paste': Text('Copy/Paste'),
@@ -131,19 +141,16 @@ class _AddCertificateScreenState extends State {
child: Text('Show/Import Private Key'),
color: CupertinoColors.secondaryLabel.resolveFrom(context),
onPressed: () => Utils.confirmDelete(context, 'Show/Import Private Key?', () {
- setState(() {
- showKey = true;
- });
- }, deleteLabel: 'Yes'))));
+ setState(() {
+ showKey = true;
+ });
+ }, deleteLabel: 'Yes'))));
}
return ConfigSection(
label: 'Import a private key generated on another device',
children: [
- ConfigTextItem(
- controller: keyController,
- style: TextStyle(fontFamily: 'RobotoMono', fontSize: 14)
- ),
+ ConfigTextItem(controller: keyController, style: TextStyle(fontFamily: 'RobotoMono', fontSize: 14)),
],
);
}
@@ -196,13 +203,13 @@ class _AddCertificateScreenState extends State {
ConfigButtonItem(
content: Text('Scan a QR code'),
onPressed: () async {
- var options = ScanOptions(
- restrictFormat: [BarcodeFormat.qr],
- );
-
- var result = await BarcodeScanner.scan(options: options);
- if (result.rawContent != "") {
- _addCertEntry(result.rawContent);
+ try {
+ var result = await FlutterBarcodeScanner.scanBarcode('#ff6666', 'Cancel', true, ScanMode.QR);
+ if (result != "") {
+ _addCertEntry(result);
+ }
+ } catch (err) {
+ return Utils.popError(context, 'Error scanning QR code', err.toString());
}
}),
],
@@ -225,36 +232,34 @@ class _AddCertificateScreenState extends State {
if (tryCertInfo.cert.details.isCa) {
return Utils.popError(context, 'Error loading certificate content',
'A certificate authority is not appropriate for a client certificate.');
- } else if (!tryCertInfo.validity.valid) {
- return Utils.popError(context, 'Certificate was invalid', tryCertInfo.validity.reason);
+ } else if (!tryCertInfo.validity!.valid) {
+ return Utils.popError(context, 'Certificate was invalid', tryCertInfo.validity!.reason);
}
- var certMatch = await platform.invokeMethod(
- "nebula.verifyCertAndKey",
- {"cert": rawCert, "key": keyController.text}
- );
+ var certMatch = await platform
+ .invokeMethod("nebula.verifyCertAndKey", {"cert": rawCert, "key": keyController.text});
if (!certMatch) {
// The method above will throw if there is a mismatch, this is just here in case we introduce a bug in the future
return Utils.popError(context, 'Error loading certificate content',
'The provided certificates public key is not compatible with the private key.');
}
- // If we are replacing we just return the results now
if (widget.onReplace != null) {
+ // If we are replacing we just return the results now
Navigator.pop(context);
- widget.onReplace(CertificateResult(certInfo: tryCertInfo, key: keyController.text));
+ widget.onReplace!(CertificateResult(certInfo: tryCertInfo, key: keyController.text));
return;
+ } else if (widget.onSave != null) {
+ // We have a cert, pop the details screen where they can hit save
+ Utils.openPage(context, (context) {
+ return CertificateDetailsScreen(
+ certInfo: tryCertInfo,
+ onSave: () {
+ Navigator.pop(context);
+ widget.onSave!(CertificateResult(certInfo: tryCertInfo, key: keyController.text));
+ });
+ });
}
-
- // We have a cert, pop the details screen where they can hit save
- Utils.openPage(context, (context) {
- return CertificateDetailsScreen(
- certInfo: tryCertInfo,
- onSave: () {
- Navigator.pop(context);
- widget.onSave(CertificateResult(certInfo: tryCertInfo, key: keyController.text));
- });
- });
}
} on PlatformException catch (err) {
return Utils.popError(context, 'Error loading certificate content', err.details ?? err.message);
diff --git a/lib/screens/siteConfig/AdvancedScreen.dart b/lib/screens/siteConfig/AdvancedScreen.dart
index 7bb07ab..7b301d8 100644
--- a/lib/screens/siteConfig/AdvancedScreen.dart
+++ b/lib/screens/siteConfig/AdvancedScreen.dart
@@ -28,10 +28,23 @@ class Advanced {
String verbosity;
List unsafeRoutes;
int mtu;
+
+ Advanced({
+ required this.lhDuration,
+ required this.port,
+ required this.cipher,
+ required this.verbosity,
+ required this.unsafeRoutes,
+ required this.mtu,
+ });
}
class AdvancedScreen extends StatefulWidget {
- const AdvancedScreen({Key key, this.site, @required this.onSave}) : super(key: key);
+ const AdvancedScreen({
+ Key? key,
+ required this.site,
+ required this.onSave,
+ }) : super(key: key);
final Site site;
final ValueChanged onSave;
@@ -41,17 +54,19 @@ class AdvancedScreen extends StatefulWidget {
}
class _AdvancedScreenState extends State {
- var settings = Advanced();
+ late Advanced settings;
var changed = false;
@override
void initState() {
- settings.lhDuration = widget.site.lhDuration;
- settings.port = widget.site.port;
- settings.cipher = widget.site.cipher;
- settings.verbosity = widget.site.logVerbosity;
- settings.unsafeRoutes = widget.site.unsafeRoutes;
- settings.mtu = widget.site.mtu;
+ settings = Advanced(
+ lhDuration: widget.site.lhDuration,
+ port: widget.site.port,
+ cipher: widget.site.cipher,
+ verbosity: widget.site.logVerbosity,
+ unsafeRoutes: widget.site.unsafeRoutes,
+ mtu: widget.site.mtu,
+ );
super.initState();
}
@@ -80,7 +95,9 @@ class _AdvancedScreenState extends State {
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
onSaved: (val) {
setState(() {
- settings.lhDuration = int.parse(val);
+ if (val != null) {
+ settings.lhDuration = int.parse(val!);
+ }
});
},
)),
@@ -96,7 +113,9 @@ class _AdvancedScreenState extends State {
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
onSaved: (val) {
setState(() {
- settings.port = int.parse(val);
+ if (val != null) {
+ settings.port = int.parse(val!);
+ }
});
},
)),
@@ -111,7 +130,9 @@ class _AdvancedScreenState extends State {
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
onSaved: (val) {
setState(() {
- settings.mtu = int.parse(val);
+ if (val != null) {
+ settings.mtu = int.parse(val!);
+ }
});
},
)),
@@ -177,7 +198,7 @@ class _AdvancedScreenState extends State {
return RenderedConfigScreen(config: config, name: widget.site.name);
});
} catch (err) {
- Utils.popError(context, 'Failed to render the site config', err);
+ Utils.popError(context, 'Failed to render the site config', err.toString());
}
},
)
diff --git a/lib/screens/siteConfig/CAListScreen.dart b/lib/screens/siteConfig/CAListScreen.dart
index 75b32d4..41053aa 100644
--- a/lib/screens/siteConfig/CAListScreen.dart
+++ b/lib/screens/siteConfig/CAListScreen.dart
@@ -1,8 +1,8 @@
import 'dart:convert';
-import 'package:barcode_scan/barcode_scan.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart';
+import 'package:flutter_barcode_scanner/flutter_barcode_scanner.dart';
import 'package:mobile_nebula/components/FormPage.dart';
import 'package:mobile_nebula/components/config/ConfigButtonItem.dart';
import 'package:mobile_nebula/components/config/ConfigPageItem.dart';
@@ -17,10 +17,14 @@ 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, this.cas, @required this.onSave}) : super(key: key);
+ const CAListScreen({
+ Key? key,
+ required this.cas,
+ this.onSave,
+ }) : super(key: key);
final List cas;
- final ValueChanged> onSave;
+ final ValueChanged>? onSave;
@override
_CAListScreenState createState() => _CAListScreenState();
@@ -59,7 +63,7 @@ class _CAListScreenState extends State {
onSave: () {
if (widget.onSave != null) {
Navigator.pop(context);
- widget.onSave(cas.values.map((ca) {
+ widget.onSave!(cas.values.map((ca) {
return ca;
}).toList());
}
@@ -90,8 +94,8 @@ class _CAListScreenState extends State {
return items;
}
- _addCAEntry(String ca, ValueChanged callback) async {
- String error;
+ _addCAEntry(String ca, ValueChanged callback) async {
+ String? error;
//TODO: show an error popup
try {
@@ -118,9 +122,7 @@ class _CAListScreenState extends State {
error = err.details ?? err.message;
}
- if (callback != null) {
- callback(error);
- }
+ callback(error);
}
List _addCA() {
@@ -130,9 +132,11 @@ class _CAListScreenState extends State {
child: CupertinoSlidingSegmentedControl(
groupValue: inputType,
onValueChanged: (v) {
- setState(() {
- inputType = v;
- });
+ if (v != null) {
+ setState(() {
+ inputType = v;
+ });
+ }
},
children: {
'paste': Text('Copy/Paste'),
@@ -215,19 +219,19 @@ class _CAListScreenState extends State {
ConfigButtonItem(
content: Text('Scan a QR code'),
onPressed: () async {
- var options = ScanOptions(
- restrictFormat: [BarcodeFormat.qr],
- );
-
- var result = await BarcodeScanner.scan(options: options);
- if (result.rawContent != "") {
- _addCAEntry(result.rawContent, (err) {
- if (err != null) {
- Utils.popError(context, 'Error loading CA content', err);
- } else {
- setState(() {});
- }
- });
+ try {
+ var result = await FlutterBarcodeScanner.scanBarcode('#ff6666', 'Cancel', true, ScanMode.QR);
+ if (result != "") {
+ _addCAEntry(result, (err) {
+ if (err != null) {
+ Utils.popError(context, 'Error loading CA content', err);
+ } else {
+ setState(() {});
+ }
+ });
+ }
+ } catch (err) {
+ return Utils.popError(context, 'Error scanning QR code', err.toString());
}
})
],
diff --git a/lib/screens/siteConfig/CertificateDetailsScreen.dart b/lib/screens/siteConfig/CertificateDetailsScreen.dart
index 979ca86..1d3402c 100644
--- a/lib/screens/siteConfig/CertificateDetailsScreen.dart
+++ b/lib/screens/siteConfig/CertificateDetailsScreen.dart
@@ -10,22 +10,30 @@ 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, this.certInfo, this.onDelete, this.onSave, this.onReplace, this.pubKey, this.privKey})
- : super(key: key);
+ const CertificateDetailsScreen({
+ Key? key,
+ required this.certInfo,
+ this.onDelete,
+ this.onSave,
+ this.onReplace,
+ this.pubKey,
+ this.privKey,
+ }) : super(key: key);
final CertificateInfo certInfo;
// onDelete is used to remove a CA cert
- final Function onDelete;
+ final Function? onDelete;
// onSave is used to install a new certificate
- final Function onSave;
+ final Function? onSave;
// onReplace is used to install a new certificate over top of the old one
- final ValueChanged onReplace;
+ final ValueChanged? onReplace;
- final String pubKey;
- final String privKey;
+ // pubKey and privKey should be set if onReplace is not null.
+ final String? pubKey;
+ final String? privKey;
@override
_CertificateDetailsScreenState createState() => _CertificateDetailsScreenState();
@@ -33,8 +41,8 @@ class CertificateDetailsScreen extends StatefulWidget {
class _CertificateDetailsScreenState extends State {
bool changed = false;
- CertificateResult certResult;
- CertificateInfo certInfo;
+ CertificateResult? certResult;
+ late CertificateInfo certInfo;
ScrollController controller = ScrollController();
@override
@@ -58,10 +66,10 @@ class _CertificateDetailsScreenState extends State {
onSave: () {
if (widget.onSave != null) {
Navigator.pop(context);
- widget.onSave();
+ widget.onSave!();
} else if (widget.onReplace != null) {
Navigator.pop(context);
- widget.onReplace(certResult);
+ widget.onReplace!(certResult!);
}
},
hideSave: widget.onSave == null && widget.onReplace == null,
@@ -86,8 +94,8 @@ class _CertificateDetailsScreenState extends State {
Widget _buildValid() {
var valid = Text('yes');
- if (certInfo.validity != null && !certInfo.validity.valid) {
- valid = Text(certInfo.validity.valid ? 'yes' : certInfo.validity.reason,
+ if (certInfo.validity != null && !certInfo.validity!.valid) {
+ valid = Text(certInfo.validity!.valid ? 'yes' : certInfo.validity!.reason,
style: TextStyle(color: CupertinoColors.systemRed.resolveFrom(context)));
}
return ConfigSection(
@@ -137,7 +145,7 @@ class _CertificateDetailsScreenState extends State {
certInfo.rawCert != null
? ConfigItem(
label: Text('PEM Format'),
- content: SelectableText(certInfo.rawCert, style: TextStyle(fontFamily: 'RobotoMono', fontSize: 14)),
+ content: SelectableText(certInfo.rawCert!, style: TextStyle(fontFamily: 'RobotoMono', fontSize: 14)),
crossAxisAlignment: CrossAxisAlignment.start)
: Container(),
],
@@ -145,7 +153,7 @@ class _CertificateDetailsScreenState extends State {
}
Widget _buildReplace() {
- if (widget.onReplace == null) {
+ if (widget.onReplace == null || widget.pubKey == null || widget.privKey == null) {
return Container();
}
@@ -158,16 +166,19 @@ class _CertificateDetailsScreenState extends State {
color: CupertinoColors.systemRed.resolveFrom(context),
onPressed: () {
Utils.openPage(context, (context) {
- return AddCertificateScreen(onReplace: (result) {
- setState(() {
- changed = true;
- certResult = result;
- certInfo = certResult.certInfo;
- });
- // Slam the page back to the top
- controller.animateTo(0,
- duration: const Duration(milliseconds: 10), curve: Curves.linearToEaseOut);
- }, pubKey: widget.pubKey, privKey: widget.privKey, );
+ return AddCertificateScreen(
+ onReplace: (result) {
+ setState(() {
+ changed = true;
+ certResult = result;
+ certInfo = result.certInfo;
+ });
+ // Slam the page back to the top
+ controller.animateTo(0,
+ duration: const Duration(milliseconds: 10), curve: Curves.linearToEaseOut);
+ },
+ pubKey: widget.pubKey!,
+ privKey: widget.privKey!);
});
})));
}
@@ -188,7 +199,7 @@ class _CertificateDetailsScreenState extends State {
color: CupertinoColors.systemRed.resolveFrom(context),
onPressed: () => Utils.confirmDelete(context, title, () async {
Navigator.pop(context);
- widget.onDelete();
+ widget.onDelete!();
}))));
}
}
diff --git a/lib/screens/siteConfig/CipherScreen.dart b/lib/screens/siteConfig/CipherScreen.dart
index 5ac9b84..db8bfcd 100644
--- a/lib/screens/siteConfig/CipherScreen.dart
+++ b/lib/screens/siteConfig/CipherScreen.dart
@@ -6,7 +6,11 @@ import 'package:mobile_nebula/components/config/ConfigCheckboxItem.dart';
import 'package:mobile_nebula/components/config/ConfigSection.dart';
class CipherScreen extends StatefulWidget {
- const CipherScreen({Key key, this.cipher, @required this.onSave}) : super(key: key);
+ const CipherScreen({
+ Key? key,
+ required this.cipher,
+ required this.onSave,
+ }) : super(key: key);
final String cipher;
final ValueChanged onSave;
@@ -16,7 +20,7 @@ class CipherScreen extends StatefulWidget {
}
class _CipherScreenState extends State {
- String cipher;
+ late String cipher;
bool changed = false;
@override
@@ -32,9 +36,7 @@ class _CipherScreenState extends State {
changed: changed,
onSave: () {
Navigator.pop(context);
- if (widget.onSave != null) {
- widget.onSave(cipher);
- }
+ widget.onSave(cipher);
},
child: Column(
children: [
diff --git a/lib/screens/siteConfig/LogVerbosityScreen.dart b/lib/screens/siteConfig/LogVerbosityScreen.dart
index aed6374..a855183 100644
--- a/lib/screens/siteConfig/LogVerbosityScreen.dart
+++ b/lib/screens/siteConfig/LogVerbosityScreen.dart
@@ -6,7 +6,11 @@ import 'package:mobile_nebula/components/config/ConfigCheckboxItem.dart';
import 'package:mobile_nebula/components/config/ConfigSection.dart';
class LogVerbosityScreen extends StatefulWidget {
- const LogVerbosityScreen({Key key, this.verbosity, @required this.onSave}) : super(key: key);
+ const LogVerbosityScreen({
+ Key? key,
+ required this.verbosity,
+ required this.onSave,
+ }) : super(key: key);
final String verbosity;
final ValueChanged onSave;
@@ -16,7 +20,7 @@ class LogVerbosityScreen extends StatefulWidget {
}
class _LogVerbosityScreenState extends State {
- String verbosity;
+ late String verbosity;
bool changed = false;
@override
@@ -32,9 +36,7 @@ class _LogVerbosityScreenState extends State {
changed: changed,
onSave: () {
Navigator.pop(context);
- if (widget.onSave != null) {
- widget.onSave(verbosity);
- }
+ widget.onSave(verbosity);
},
child: Column(
children: [
diff --git a/lib/screens/siteConfig/RenderedConfigScreen.dart b/lib/screens/siteConfig/RenderedConfigScreen.dart
index 0abb06d..a6c9042 100644
--- a/lib/screens/siteConfig/RenderedConfigScreen.dart
+++ b/lib/screens/siteConfig/RenderedConfigScreen.dart
@@ -7,7 +7,11 @@ class RenderedConfigScreen extends StatelessWidget {
final String config;
final String name;
- RenderedConfigScreen({Key key, this.config, this.name}) : super(key: key);
+ RenderedConfigScreen({
+ Key? key,
+ required this.config,
+ required this.name,
+ }) : super(key: key);
@override
Widget build(BuildContext context) {
diff --git a/lib/screens/siteConfig/SiteConfigScreen.dart b/lib/screens/siteConfig/SiteConfigScreen.dart
index ba47fce..9d6a6b7 100644
--- a/lib/screens/siteConfig/SiteConfigScreen.dart
+++ b/lib/screens/siteConfig/SiteConfigScreen.dart
@@ -22,9 +22,13 @@ import 'package:mobile_nebula/services/utils.dart';
//TODO: Enforce a name
class SiteConfigScreen extends StatefulWidget {
- const SiteConfigScreen({Key key, this.site, this.onSave}) : super(key: key);
+ const SiteConfigScreen({
+ Key? key,
+ this.site,
+ required this.onSave,
+ }) : super(key: key);
- final Site site;
+ final Site? site;
// This is called after the target OS has saved the configuration
final ValueChanged onSave;
@@ -37,9 +41,9 @@ class _SiteConfigScreenState extends State {
bool changed = false;
bool newSite = false;
bool debug = false;
- Site site;
- String pubKey;
- String privKey;
+ late Site site;
+ String? pubKey;
+ String? privKey;
static const platform = MethodChannel('net.defined.mobileNebula/NebulaVpnService');
final nameController = TextEditingController();
@@ -52,7 +56,7 @@ class _SiteConfigScreenState extends State {
newSite = true;
site = Site();
} else {
- site = widget.site;
+ site = widget.site!;
nameController.text = site.name;
}
@@ -61,7 +65,7 @@ class _SiteConfigScreenState extends State {
@override
Widget build(BuildContext context) {
- if (pubKey == null) {
+ if (pubKey == null || privKey == null) {
return Center(
child: fpw.PlatformCircularProgressIndicator(cupertino: (_, __) {
return fpw.CupertinoProgressIndicatorData(radius: 50);
@@ -81,9 +85,7 @@ class _SiteConfigScreenState extends State {
}
Navigator.pop(context);
- if (widget.onSave != null) {
- widget.onSave(site);
- }
+ widget.onSave(site);
},
child: Column(
children: [
@@ -126,17 +128,17 @@ class _SiteConfigScreenState extends State {
}
Widget _keys() {
- final certError = site.certInfo == null || !site.certInfo.validity.valid;
+ final certError = site.certInfo == null || site.certInfo!.validity == null || !site.certInfo!.validity!.valid;
var caError = site.ca.length == 0;
if (!caError) {
site.ca.forEach((ca) {
- if (!ca.validity.valid) {
+ if (ca.validity == null || !ca.validity!.valid) {
caError = true;
}
});
}
- return ConfigSection(
+ return ConfigSection(
label: "IDENTITY",
children: [
ConfigPageItem(
@@ -147,13 +149,13 @@ class _SiteConfigScreenState extends State {
child: Icon(Icons.error, color: CupertinoColors.systemRed.resolveFrom(context), size: 20),
padding: EdgeInsets.only(right: 5))
: Container(),
- certError ? Text('Needs attention') : Text(site.certInfo.cert.details.name)
+ certError ? Text('Needs attention') : Text(site.certInfo?.cert.details.name ?? 'Unknown certificate')
]),
onPressed: () {
Utils.openPage(context, (context) {
if (site.certInfo != null) {
return CertificateDetailsScreen(
- certInfo: site.certInfo,
+ certInfo: site.certInfo!,
pubKey: pubKey,
privKey: privKey,
onReplace: (result) {
@@ -165,13 +167,16 @@ class _SiteConfigScreenState extends State {
});
}
- return AddCertificateScreen(pubKey: pubKey, privKey: privKey, onSave: (result) {
- setState(() {
- changed = true;
- site.certInfo = result.certInfo;
- site.key = result.key;
- });
- });
+ return AddCertificateScreen(
+ pubKey: pubKey!,
+ privKey: privKey!,
+ onSave: (result) {
+ setState(() {
+ changed = true;
+ site.certInfo = result.certInfo;
+ site.key = result.key;
+ });
+ });
});
},
),
diff --git a/lib/screens/siteConfig/StaticHostmapScreen.dart b/lib/screens/siteConfig/StaticHostmapScreen.dart
index 43399db..e6b9a36 100644
--- a/lib/screens/siteConfig/StaticHostmapScreen.dart
+++ b/lib/screens/siteConfig/StaticHostmapScreen.dart
@@ -15,35 +15,42 @@ class _IPAndPort {
final FocusNode focusNode;
IPAndPort destination;
- _IPAndPort({this.focusNode, this.destination});
+ _IPAndPort({required this.focusNode, required this.destination});
}
class StaticHostmapScreen extends StatefulWidget {
- const StaticHostmapScreen(
- {Key key, this.nebulaIp, this.destinations, this.lighthouse = false, this.onDelete, @required this.onSave})
- : super(key: key);
+ StaticHostmapScreen({
+ Key? key,
+ this.nebulaIp = '',
+ destinations,
+ this.lighthouse = false,
+ this.onDelete,
+ required this.onSave,
+ }) : this.destinations = destinations ?? [],
+ super(key: key);
final List destinations;
final String nebulaIp;
final bool lighthouse;
final ValueChanged onSave;
- final Function onDelete;
+ final Function? onDelete;
@override
_StaticHostmapScreenState createState() => _StaticHostmapScreenState();
}
class _StaticHostmapScreenState extends State {
- Map _destinations = {};
- String _nebulaIp;
- bool _lighthouse;
+ late Map _destinations;
+ late String _nebulaIp;
+ late bool _lighthouse;
bool changed = false;
@override
void initState() {
_nebulaIp = widget.nebulaIp;
_lighthouse = widget.lighthouse;
- widget.destinations?.forEach((dest) {
+ _destinations = {};
+ widget.destinations.forEach((dest) {
_destinations[UniqueKey()] = _IPAndPort(focusNode: FocusNode(), destination: dest);
});
@@ -75,7 +82,9 @@ class _StaticHostmapScreenState extends State {
crossAxisAlignment: CrossAxisAlignment.end,
textInputAction: TextInputAction.next,
onSaved: (v) {
- _nebulaIp = v;
+ if (v != null) {
+ _nebulaIp = v;
+ }
})),
ConfigItem(
label: Text('Lighthouse'),
@@ -107,7 +116,7 @@ class _StaticHostmapScreenState extends State {
color: CupertinoColors.systemRed.resolveFrom(context),
onPressed: () => Utils.confirmDelete(context, 'Delete host map?', () {
Navigator.of(context).pop();
- widget.onDelete();
+ widget.onDelete!();
}),
)))
: Container()
@@ -116,15 +125,13 @@ class _StaticHostmapScreenState extends State {
_onSave() {
Navigator.pop(context);
- if (widget.onSave != null) {
- var map = Hostmap(nebulaIp: _nebulaIp, destinations: [], lighthouse: _lighthouse);
+ var map = Hostmap(nebulaIp: _nebulaIp, destinations: [], lighthouse: _lighthouse);
- _destinations.forEach((_, dest) {
- map.destinations.add(dest.destination);
- });
+ _destinations.forEach((_, dest) {
+ map.destinations.add(dest.destination);
+ });
- widget.onSave(map);
- }
+ widget.onSave(map);
}
List _buildHosts() {
@@ -152,7 +159,9 @@ class _StaticHostmapScreenState extends State {
noBorder: true,
initialValue: dest.destination,
onSaved: (v) {
- dest.destination = v;
+ if (v != null) {
+ dest.destination = v;
+ }
},
)),
]),
diff --git a/lib/screens/siteConfig/StaticHostsScreen.dart b/lib/screens/siteConfig/StaticHostsScreen.dart
index 76c7daf..6746948 100644
--- a/lib/screens/siteConfig/StaticHostsScreen.dart
+++ b/lib/screens/siteConfig/StaticHostsScreen.dart
@@ -18,12 +18,20 @@ class _Hostmap {
List destinations;
bool lighthouse;
- _Hostmap({this.focusNode, this.nebulaIp, destinations, this.lighthouse})
- : destinations = destinations ?? [];
+ _Hostmap({
+ required this.focusNode,
+ required this.nebulaIp,
+ required this.destinations,
+ required this.lighthouse,
+ });
}
class StaticHostsScreen extends StatefulWidget {
- const StaticHostsScreen({Key key, @required this.hostmap, @required this.onSave}) : super(key: key);
+ const StaticHostsScreen({
+ Key? key,
+ required this.hostmap,
+ required this.onSave,
+ }) : super(key: key);
final Map hostmap;
final ValueChanged