Upgrade to flutter 3 (#70)

Co-authored-by: John Maguire <contact@johnmaguire.me>
This commit is contained in:
Nate Brown 2022-09-21 15:27:35 -05:00 committed by GitHub
parent e3780bda1e
commit dbe67c2f81
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
71 changed files with 953 additions and 818 deletions

View File

@ -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

1
android/.gitignore vendored
View File

@ -6,3 +6,4 @@ gradle-wrapper.jar
/local.properties
GeneratedPluginRegistrant.java
/build/build-attribution/
/mobileNebula/mobileNebula.aar

View File

@ -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')
}

View File

@ -9,11 +9,12 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-feature android:name="android.hardware.camera" android:required="false" />
<application
android:name="io.flutter.app.FlutterApplication"
android:name="${applicationName}"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher">
<service android:name=".NebulaVpnService"
android:permission="android.permission.BIND_VPN_SERVICE"
android:exported="false"
android:process=":nebulaVpnBg">
<intent-filter>
<action android:name="android.net.VpnService"/>
@ -21,6 +22,7 @@
</service>
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"

View File

@ -33,7 +33,7 @@ class Share {
} catch (err: Exception) {
Log.println(Log.ERROR, "", "Share: Error")
result.error(err.message, null, null)
result.error(err.message ?: "Unknown error", null, null)
}
}
@ -67,7 +67,7 @@ class Share {
} catch (err: Exception) {
Log.println(Log.ERROR, "", "Share: Error")
result.error(err.message, null, null)
result.error(err.message ?: "Unknown error", null, null)
}
}
@ -107,7 +107,7 @@ class Share {
} catch (err: Exception) {
Log.println(Log.ERROR, "", "Share: Error")
return result.error(err.message, null, null)
return result.error(err.message ?: "Unknown error", null, null)
}
result.success(true)

View File

@ -1,12 +1,12 @@
buildscript {
ext.kotlin_version = '1.3.61'
ext.kotlin_version = '1.6.10'
repositories {
google()
jcenter()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.0.0'
classpath 'com.android.tools.build:gradle:7.1.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
@ -14,7 +14,7 @@ buildscript {
allprojects {
repositories {
google()
jcenter()
mavenCentral()
}
}

View File

@ -1,4 +1,3 @@
org.gradle.jvmargs=-Xmx1536M
android.enableR8=true
android.useAndroidX=true
android.enableJetifier=true

View File

@ -1,6 +1,5 @@
#Fri Jun 05 14:55:48 CDT 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip

View File

@ -0,0 +1,6 @@
configurations.maybeCreate("default")
exec {
workingDir '../../'
commandLine './gen-artifacts.sh', 'android'
}
artifacts.add("default", file('mobileNebula.aar'))

View File

@ -1,15 +1,11 @@
include ':app'
include ':app', ':mobileNebula'
def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
def properties = new Properties()
def plugins = new Properties()
def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
if (pluginsFile.exists()) {
pluginsFile.withReader('UTF-8') { reader -> 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"

View File

@ -1 +0,0 @@
include ':app'

View File

@ -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"

View File

@ -21,6 +21,6 @@
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>9.0</string>
<string>11.0</string>
</dict>
</plist>

View File

@ -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

View File

@ -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 = (

View File

@ -47,5 +47,7 @@
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
</dict>
</plist>

View File

@ -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<CIDR> onChanged;
final TextInputAction textInputAction;
final TextEditingController ipController;
final TextEditingController bitsController;
final FocusNode? focusNode;
final FocusNode? nextFocusNode;
final ValueChanged<CIDR>? onChanged;
final TextInputAction? textInputAction;
final TextEditingController? ipController;
final TextEditingController? bitsController;
@override
_CIDRFieldState createState() => _CIDRFieldState();
@ -44,7 +44,7 @@ class _CIDRFieldState extends State<CIDRField> {
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<CIDRField> {
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<CIDRField> {
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],

View File

@ -6,15 +6,15 @@ import 'package:mobile_nebula/validators/ipValidator.dart';
class CIDRFormField extends FormField<CIDR> {
//TODO: onSaved, validator, auto-validate, enabled?
CIDRFormField({
Key key,
Key? key,
autoFocus = false,
enableIPV6 = false,
focusNode,
nextFocusNode,
ValueChanged<CIDR> onChanged,
FormFieldSetter<CIDR> onSaved,
ValueChanged<CIDR>? onChanged,
FormFieldSetter<CIDR>? onSaved,
textInputAction,
CIDR initialValue,
CIDR? initialValue,
this.ipController,
this.bitsController,
}) : super(
@ -30,14 +30,14 @@ class CIDRFormField extends FormField<CIDR> {
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<CIDR> 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<CIDR> {
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<CIDR> {
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<CIDR> {
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<CIDR> {
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<CIDR> {
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<CIDR> {
// 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));
}
}

View File

@ -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<FormPage> {
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();
},
)

View File

@ -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<IPAndPort> 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();

View File

@ -8,17 +8,17 @@ import 'IPAndPortField.dart';
class IPAndPortFormField extends FormField<IPAndPort> {
//TODO: onSaved, validator, auto-validate, enabled?
IPAndPortFormField({
Key key,
Key? key,
ipOnly = false,
enableIPV6 = false,
ipHelp = "ip address",
autoFocus = false,
focusNode,
nextFocusNode,
ValueChanged<IPAndPort> onChanged,
FormFieldSetter<IPAndPort> onSaved,
ValueChanged<IPAndPort>? onChanged,
FormFieldSetter<IPAndPort>? onSaved,
textInputAction,
IPAndPort initialValue,
IPAndPort? initialValue,
noBorder,
ipTextAlign = TextAlign.center,
this.ipController,
@ -36,14 +36,14 @@ class IPAndPortFormField extends FormField<IPAndPort> {
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<IPAndPort> 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<IPAndPort> {
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<IPAndPort> {
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<IPAndPort> {
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<IPAndPort> {
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<IPAndPort> {
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<IPAndPort> {
// 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));
}
}

View File

@ -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<String> onChanged;
final FocusNode? focusNode;
final FocusNode? nextFocusNode;
final ValueChanged<String>? 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<String>((Match match) => match.group(0))
.map<String>((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 {

View File

@ -9,15 +9,15 @@ import 'IPField.dart';
class IPFormField extends FormField<String> {
//TODO: validator, auto-validate, enabled?
IPFormField({
Key key,
Key? key,
ipOnly = false,
enableIPV6 = false,
help = "ip address",
autoFocus = false,
focusNode,
nextFocusNode,
ValueChanged<String> onChanged,
FormFieldSetter<String> onSaved,
ValueChanged<String>? onChanged,
FormFieldSetter<String>? onSaved,
textPadding = const EdgeInsets.all(6.0),
textInputAction,
initialValue,
@ -41,7 +41,7 @@ class IPFormField extends FormField<String> {
return null;
},
builder: (FormFieldState<String> 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<String> {
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<String> {
]);
});
final TextEditingController controller;
final TextEditingController? controller;
@override
_IPFormField createState() => _IPFormField();
}
class _IPFormField extends FormFieldState<String> {
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<String> {
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<String> {
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<String> {
void reset() {
super.reset();
setState(() {
_effectiveController.text = widget.initialValue;
_effectiveController.text = widget.initialValue ?? "";
});
}

View File

@ -6,14 +6,14 @@ import 'package:mobile_nebula/components/SpecialTextField.dart';
class PlatformTextFormField extends FormField<String> {
//TODO: auto-validate, enabled?
PlatformTextFormField(
{Key key,
{Key? key,
widgetKey,
this.controller,
focusNode,
nextFocusNode,
TextInputType keyboardType,
TextInputType? keyboardType,
textInputAction,
List<TextInputFormatter> inputFormatters,
List<TextInputFormatter>? inputFormatters,
textAlign,
autofocus,
maxLines = 1,
@ -25,10 +25,10 @@ class PlatformTextFormField extends FormField<String> {
expands,
suffix,
textAlignVertical,
String initialValue,
String placeholder,
FormFieldValidator<String> validator,
ValueChanged<String> onSaved})
String? initialValue,
String? placeholder,
FormFieldValidator<String>? validator,
ValueChanged<String?>? onSaved})
: super(
key: key,
initialValue: controller != null ? controller.text : (initialValue ?? ''),
@ -41,7 +41,7 @@ class PlatformTextFormField extends FormField<String> {
return null;
},
builder: (FormFieldState<String> 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<String> {
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<String> {
]);
});
final TextEditingController controller;
final TextEditingController? controller;
@override
_PlatformTextFormFieldState createState() => _PlatformTextFormFieldState();
}
class _PlatformTextFormFieldState extends FormFieldState<String> {
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<String> {
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<String> {
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<String> {
void reset() {
super.reset();
setState(() {
_effectiveController.text = widget.initialValue;
_effectiveController.text = widget.initialValue ?? "";
});
}

View File

@ -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<Widget> 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!,
]);
}

View File

@ -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(

View File

@ -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<SpecialButton> 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<SpecialButton> with SingleTickerProvider
static const Duration kFadeInDuration = Duration(milliseconds: 100);
final Tween<double> _opacityTween = Tween<double>(begin: 1.0);
AnimationController _animationController;
Animation<double> _opacityAnimation;
AnimationController? _animationController;
Animation<double>? _opacityAnimation;
@override
void initState() {
@ -82,7 +82,7 @@ class _SpecialButtonState extends State<SpecialButton> 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<SpecialButton> with SingleTickerProvider
@override
void dispose() {
_animationController.dispose();
_animationController = null;
_animationController?.dispose();
super.dispose();
}
@ -127,14 +126,14 @@ class _SpecialButtonState extends State<SpecialButton> 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>((void value) {
if (mounted && wasHeldDown != _buttonHeldDown) {

View File

@ -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<String> onChanged;
final bool enabled;
final List<TextInputFormatter> inputFormatters;
final bool expands;
final bool? autofocus;
final ValueChanged<String>? onChanged;
final bool? enabled;
final List<TextInputFormatter>? inputFormatters;
final bool? expands;
@override
_SpecialTextFieldState createState() => _SpecialTextFieldState();
}
class _SpecialTextFieldState extends State<SpecialTextField> {
List<TextInputFormatter> formatters;
List<TextInputFormatter> 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();

View File

@ -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

View File

@ -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!();
}
},
);

View File

@ -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) {

View File

@ -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;

View File

@ -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);

View File

@ -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<Widget> 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)),

View File

@ -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

View File

@ -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]),
);
}
}

View File

@ -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<CertificateInfo> fromJsonList(List<dynamic> 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<String, dynamic> 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<String>.from(json['groups']),
ips = List<String>.from(json['ips']),

View File

@ -6,31 +6,48 @@ class HostInfo {
int remoteIndex;
List<UDPAddress> remoteAddresses;
int cachedPackets;
Certificate cert;
UDPAddress currentRemote;
Certificate? cert;
UDPAddress? currentRemote;
int messageCounter;
HostInfo.fromJson(Map<String, dynamic> 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<String, dynamic> json) {
UDPAddress? currentRemote;
if (json['currentRemote'] != null) {
currentRemote = UDPAddress.fromJson(json['currentRemote']);
}
Certificate? cert;
if (json['cert'] != null) {
cert = Certificate.fromJson(json['cert']);
}
List<dynamic> addrs = json['remoteAddrs'];
remoteAddresses = [];
addrs?.forEach((val) {
List<UDPAddress> 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,
);
}
}

View File

@ -5,5 +5,5 @@ class Hostmap {
List<IPAndPort> destinations;
bool lighthouse;
Hostmap({this.nebulaIp, this.destinations, this.lighthouse});
Hostmap({required this.nebulaIp, required this.destinations, required this.lighthouse});
}

View File

@ -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,
);
}
}

View File

@ -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<String, StaticHost> staticHostmap;
List<UnsafeRoute> unsafeRoutes;
late Map<String, StaticHost> staticHostmap;
late List<UnsafeRoute> unsafeRoutes;
// pki fields
List<CertificateInfo> ca;
CertificateInfo certInfo;
String key;
late List<CertificateInfo> 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<String> errors;
late List<String> 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<String, dynamic> json) {
name = json['name'];
id = json['id'];
Map<String, dynamic> rawHostmap = json['staticHostmap'];
staticHostmap = {};
rawHostmap.forEach((key, val) {
staticHostmap[key] = StaticHost.fromJson(val);
});
List<dynamic> rawUnsafeRoutes = json['unsafeRoutes'];
unsafeRoutes = [];
if (rawUnsafeRoutes != null) {
rawUnsafeRoutes.forEach((val) {
unsafeRoutes.add(UnsafeRoute.fromJson(val));
});
}
List<dynamic> 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<dynamic> rawErrors = json["errors"];
rawErrors.forEach((error) {
errors.add(error);
});
Site({
String name = '',
String? id,
Map<String, StaticHost>? staticHostmap,
List<CertificateInfo>? ca,
CertificateInfo? certInfo,
int lhDuration = 0,
int port = 0,
String cipher = "aes",
int sortKey = 0,
int mtu = 1300,
bool connected = false,
String status = '',
String logFile = '',
String logVerbosity = 'info',
List<String>? errors,
List<UnsafeRoute>? 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<String, dynamic> json) {
Map<String, dynamic> rawHostmap = json['staticHostmap'];
Map<String, StaticHost> staticHostmap = {};
rawHostmap.forEach((key, val) {
staticHostmap[key] = StaticHost.fromJson(val);
});
List<dynamic> rawUnsafeRoutes = json['unsafeRoutes'];
List<UnsafeRoute> unsafeRoutes = [];
rawUnsafeRoutes.forEach((val) {
unsafeRoutes.add(UnsafeRoute.fromJson(val));
});
List<dynamic> rawCA = json['ca'];
List<CertificateInfo> ca = [];
rawCA.forEach((val) {
ca.add(CertificateInfo.fromJson(val));
});
CertificateInfo? certInfo;
if (json['cert'] != null) {
certInfo = CertificateInfo.fromJson(json['cert']);
}
List<dynamic> rawErrors = json["errors"];
List<String> 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<HostInfo> getHostInfo(String vpnIp, bool pending) async {
Future<HostInfo?> getHostInfo(String vpnIp, bool pending) async {
try {
var ret = await platform
.invokeMethod("active.getHostInfo", <String, dynamic>{"id": id, "vpnIp": vpnIp, "pending": pending});
@ -277,7 +294,7 @@ class Site {
}
}
Future<HostInfo> setRemoteForTunnel(String vpnIp, String addr) async {
Future<HostInfo?> setRemoteForTunnel(String vpnIp, String addr) async {
try {
var ret = await platform
.invokeMethod("active.setRemoteForTunnel", <String, dynamic>{"id": id, "vpnIp": vpnIp, "addr": addr});

View File

@ -4,11 +4,9 @@ class StaticHost {
bool lighthouse;
List<IPAndPort> destinations;
StaticHost({this.lighthouse, this.destinations});
StaticHost.fromJson(Map<String, dynamic> json) {
lighthouse = json['lighthouse'];
StaticHost({required this.lighthouse, required this.destinations});
factory StaticHost.fromJson(Map<String, dynamic> json) {
var list = json['destinations'] as List<dynamic>;
var result = <IPAndPort>[];
@ -16,7 +14,10 @@ class StaticHost {
result.add(IPAndPort.fromString(item));
});
destinations = result;
return StaticHost(
lighthouse: json['lighthouse'],
destinations: result,
);
}
Map<String, dynamic> toJson() {

View File

@ -1,12 +1,14 @@
class UnsafeRoute {
String route;
String via;
String? route;
String? via;
UnsafeRoute({this.route, this.via});
UnsafeRoute.fromJson(Map<String, dynamic> json) {
route = json['route'];
via = json['via'];
factory UnsafeRoute.fromJson(Map<String, dynamic> json) {
return UnsafeRoute(
route: json['route'],
via: json['via'],
);
}
Map<String, dynamic> toJson() {

View File

@ -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<AboutScreen> {
bool ready = false;
PackageInfo packageInfo;
PackageInfo? packageInfo;
@override
void initState() {
@ -33,6 +33,7 @@ class _AboutScreenState extends State<AboutScreen> {
@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<AboutScreen> {
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: <Widget>[
//TODO: wire up these other pages

View File

@ -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<HostInfoScreen> {
HostInfo hostInfo;
late HostInfo hostInfo;
RefreshController refreshController = RefreshController(initialRefresh: false);
@override
@ -64,9 +70,9 @@ class _HostInfoScreenState extends State<HostInfoScreen> {
? 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<HostInfoScreen> {
_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<HostInfoScreen> {
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<HostInfoScreen> {
_setHostInfo(h);
} catch (err) {
Utils.popError(context, 'Failed to refresh host info', err);
Utils.popError(context, 'Failed to refresh host info', err.toString());
}
}

View File

@ -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<MainScreen> {
bool ready = false;
List<Site> sites;
List<Site>? sites;
// A set of widgets to display in a column that represents an error blocking us from moving forward entirely
List<Widget> error;
List<Widget>? error;
static const platform = MethodChannel('net.defined.mobileNebula/NebulaVpnService');
@ -72,11 +72,14 @@ class _MainScreenState extends State<MainScreen> {
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<MainScreen> {
);
}
if (sites == null || sites.length == 0) {
return _buildNoSites();
}
return _buildSites();
}
@ -112,8 +111,12 @@ class _MainScreenState extends State<MainScreen> {
}
Widget _buildSites() {
if (sites == null || sites!.length == 0) {
return _buildNoSites();
}
List<Widget> items = [];
sites.forEach((site) {
sites!.forEach((site) {
items.add(SiteItem(
key: Key(site.id),
site: site,
@ -134,17 +137,17 @@ class _MainScreenState extends State<MainScreen> {
}
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;
});

View File

@ -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<SiteDetailScreen> {
Site site;
StreamSubscription onChange;
late Site site;
late StreamSubscription onChange;
static const platform = MethodChannel('net.defined.mobileNebula/NebulaVpnService');
bool changed = false;
List<HostInfo> activeHosts;
List<HostInfo> pendingHosts;
List<HostInfo>? activeHosts;
List<HostInfo>? pendingHosts;
RefreshController refreshController = RefreshController(initialRefresh: false);
bool lastState;
late bool lastState;
@override
void initState() {
@ -80,7 +80,7 @@ class _SiteDetailScreenState extends State<SiteDetailScreen> {
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<SiteDetailScreen> {
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<SiteDetailScreen> {
children: <Widget>[
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<SiteDetailScreen> {
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<SiteDetailScreen> {
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<SiteDetailScreen> {
}
if (widget.onChanged != null) {
widget.onChanged();
widget.onChanged!();
}
return true;
}

View File

@ -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;

View File

@ -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<HostInfo> tunnels;
final bool pending;
final Function(List<HostInfo>) onChanged;
final Function(List<HostInfo>)? onChanged;
@override
_SiteTunnelsScreenState createState() => _SiteTunnelsScreenState();
}
class _SiteTunnelsScreenState extends State<SiteTunnelsScreen> {
Site site;
List<HostInfo> tunnels;
late Site site;
late List<HostInfo> 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<SiteTunnelsScreen> {
})),
label: Row(children: <Widget>[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<SiteTunnelsScreen> {
_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());
}
}
}

View File

@ -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<CertificateResult> onSave;
// onReplace will return the CertificateResult, assuming the previous screen is a CertificateDetailsScreen
final ValueChanged<CertificateResult> onReplace;
// onSave will pop a new CertificateDetailsScreen.
// If onSave is null, onReplace must be set.
final ValueChanged<CertificateResult>? onSave;
// onReplace will return the CertificateResult, assuming the previous screen is a CertificateDetailsScreen.
// If onReplace is null, onSave must be set.
final ValueChanged<CertificateResult>? onReplace;
final String pubKey;
final String privKey;
@ -39,7 +47,7 @@ class AddCertificateScreen extends StatefulWidget {
}
class _AddCertificateScreenState extends State<AddCertificateScreen> {
String pubKey;
late String pubKey;
bool showKey = false;
String inputType = 'paste';
@ -98,9 +106,11 @@ class _AddCertificateScreenState extends State<AddCertificateScreen> {
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<AddCertificateScreen> {
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<AddCertificateScreen> {
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<AddCertificateScreen> {
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",
<String, String>{"cert": rawCert, "key": keyController.text}
);
var certMatch = await platform
.invokeMethod("nebula.verifyCertAndKey", <String, String>{"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);

View File

@ -28,10 +28,23 @@ class Advanced {
String verbosity;
List<UnsafeRoute> 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<Advanced> onSave;
@ -41,17 +54,19 @@ class AdvancedScreen extends StatefulWidget {
}
class _AdvancedScreenState extends State<AdvancedScreen> {
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<AdvancedScreen> {
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<AdvancedScreen> {
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<AdvancedScreen> {
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<AdvancedScreen> {
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());
}
},
)

View File

@ -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<CertificateInfo> cas;
final ValueChanged<List<CertificateInfo>> onSave;
final ValueChanged<List<CertificateInfo>>? onSave;
@override
_CAListScreenState createState() => _CAListScreenState();
@ -59,7 +63,7 @@ class _CAListScreenState extends State<CAListScreen> {
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<CAListScreen> {
return items;
}
_addCAEntry(String ca, ValueChanged<String> callback) async {
String error;
_addCAEntry(String ca, ValueChanged<String?> callback) async {
String? error;
//TODO: show an error popup
try {
@ -118,9 +122,7 @@ class _CAListScreenState extends State<CAListScreen> {
error = err.details ?? err.message;
}
if (callback != null) {
callback(error);
}
callback(error);
}
List<Widget> _addCA() {
@ -130,9 +132,11 @@ class _CAListScreenState extends State<CAListScreen> {
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<CAListScreen> {
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());
}
})
],

View File

@ -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<CertificateResult> onReplace;
final ValueChanged<CertificateResult>? 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<CertificateDetailsScreen> {
bool changed = false;
CertificateResult certResult;
CertificateInfo certInfo;
CertificateResult? certResult;
late CertificateInfo certInfo;
ScrollController controller = ScrollController();
@override
@ -58,10 +66,10 @@ class _CertificateDetailsScreenState extends State<CertificateDetailsScreen> {
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<CertificateDetailsScreen> {
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<CertificateDetailsScreen> {
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<CertificateDetailsScreen> {
}
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<CertificateDetailsScreen> {
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<CertificateDetailsScreen> {
color: CupertinoColors.systemRed.resolveFrom(context),
onPressed: () => Utils.confirmDelete(context, title, () async {
Navigator.pop(context);
widget.onDelete();
widget.onDelete!();
}))));
}
}

View File

@ -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<String> onSave;
@ -16,7 +20,7 @@ class CipherScreen extends StatefulWidget {
}
class _CipherScreenState extends State<CipherScreen> {
String cipher;
late String cipher;
bool changed = false;
@override
@ -32,9 +36,7 @@ class _CipherScreenState extends State<CipherScreen> {
changed: changed,
onSave: () {
Navigator.pop(context);
if (widget.onSave != null) {
widget.onSave(cipher);
}
widget.onSave(cipher);
},
child: Column(
children: <Widget>[

View File

@ -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<String> onSave;
@ -16,7 +20,7 @@ class LogVerbosityScreen extends StatefulWidget {
}
class _LogVerbosityScreenState extends State<LogVerbosityScreen> {
String verbosity;
late String verbosity;
bool changed = false;
@override
@ -32,9 +36,7 @@ class _LogVerbosityScreenState extends State<LogVerbosityScreen> {
changed: changed,
onSave: () {
Navigator.pop(context);
if (widget.onSave != null) {
widget.onSave(verbosity);
}
widget.onSave(verbosity);
},
child: Column(
children: <Widget>[

View File

@ -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) {

View File

@ -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<Site> onSave;
@ -37,9 +41,9 @@ class _SiteConfigScreenState extends State<SiteConfigScreen> {
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<SiteConfigScreen> {
newSite = true;
site = Site();
} else {
site = widget.site;
site = widget.site!;
nameController.text = site.name;
}
@ -61,7 +65,7 @@ class _SiteConfigScreenState extends State<SiteConfigScreen> {
@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<SiteConfigScreen> {
}
Navigator.pop(context);
if (widget.onSave != null) {
widget.onSave(site);
}
widget.onSave(site);
},
child: Column(
children: <Widget>[
@ -126,11 +128,11 @@ class _SiteConfigScreenState extends State<SiteConfigScreen> {
}
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;
}
});
@ -147,13 +149,13 @@ class _SiteConfigScreenState extends State<SiteConfigScreen> {
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<SiteConfigScreen> {
});
}
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;
});
});
});
},
),

View File

@ -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<IPAndPort> destinations;
final String nebulaIp;
final bool lighthouse;
final ValueChanged<Hostmap> onSave;
final Function onDelete;
final Function? onDelete;
@override
_StaticHostmapScreenState createState() => _StaticHostmapScreenState();
}
class _StaticHostmapScreenState extends State<StaticHostmapScreen> {
Map<Key, _IPAndPort> _destinations = {};
String _nebulaIp;
bool _lighthouse;
late Map<Key, _IPAndPort> _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<StaticHostmapScreen> {
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<StaticHostmapScreen> {
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<StaticHostmapScreen> {
_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<Widget> _buildHosts() {
@ -152,7 +159,9 @@ class _StaticHostmapScreenState extends State<StaticHostmapScreen> {
noBorder: true,
initialValue: dest.destination,
onSaved: (v) {
dest.destination = v;
if (v != null) {
dest.destination = v;
}
},
)),
]),

View File

@ -18,12 +18,20 @@ class _Hostmap {
List<IPAndPort> destinations;
bool lighthouse;
_Hostmap({this.focusNode, this.nebulaIp, destinations, this.lighthouse})
: destinations = destinations ?? <IPAndPort>[];
_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<String, StaticHost> hostmap;
final ValueChanged<Map<String, StaticHost>> onSave;
@ -38,7 +46,7 @@ class _StaticHostsScreenState extends State<StaticHostsScreen> {
@override
void initState() {
widget.hostmap?.forEach((key, map) {
widget.hostmap.forEach((key, map) {
_hostmap[UniqueKey()] =
_Hostmap(focusNode: FocusNode(), nebulaIp: key, destinations: map.destinations, lighthouse: map.lighthouse);
});
@ -59,14 +67,12 @@ class _StaticHostsScreenState extends State<StaticHostsScreen> {
_onSave() {
Navigator.pop(context);
if (widget.onSave != null) {
Map<String, StaticHost> map = {};
_hostmap.forEach((_, host) {
map[host.nebulaIp] = StaticHost(destinations: host.destinations, lighthouse: host.lighthouse);
});
Map<String, StaticHost> map = {};
_hostmap.forEach((_, host) {
map[host.nebulaIp] = StaticHost(destinations: host.destinations, lighthouse: host.lighthouse);
});
widget.onSave(map);
}
widget.onSave(map);
}
List<Widget> _buildHosts() {

View File

@ -10,18 +10,23 @@ import 'package:mobile_nebula/models/UnsafeRoute.dart';
import 'package:mobile_nebula/services/utils.dart';
class UnsafeRouteScreen extends StatefulWidget {
const UnsafeRouteScreen({Key key, this.route, this.onDelete, @required this.onSave}) : super(key: key);
const UnsafeRouteScreen({
Key? key,
required this.route,
required this.onSave,
this.onDelete,
}) : super(key: key);
final UnsafeRoute route;
final ValueChanged<UnsafeRoute> onSave;
final Function onDelete;
final Function? onDelete;
@override
_UnsafeRouteScreenState createState() => _UnsafeRouteScreenState();
}
class _UnsafeRouteScreenState extends State<UnsafeRouteScreen> {
UnsafeRoute route;
late UnsafeRoute route;
bool changed = false;
FocusNode routeFocus = FocusNode();
@ -36,7 +41,7 @@ class _UnsafeRouteScreenState extends State<UnsafeRouteScreen> {
@override
Widget build(BuildContext context) {
var routeCIDR = route?.route == null ? CIDR() : CIDR.fromString(route?.route);
var routeCIDR = route.route == null ? CIDR() : CIDR.fromString(route.route!);
return FormPage(
title: widget.onDelete == null ? 'New Unsafe Route' : 'Edit Unsafe Route',
@ -57,7 +62,7 @@ class _UnsafeRouteScreenState extends State<UnsafeRouteScreen> {
ConfigItem(
label: Text('Via'),
content: IPFormField(
initialValue: route?.via ?? "",
initialValue: route.via ?? '',
ipOnly: true,
help: 'nebula ip',
textAlign: TextAlign.end,
@ -66,7 +71,9 @@ class _UnsafeRouteScreenState extends State<UnsafeRouteScreen> {
focusNode: viaFocus,
nextFocusNode: mtuFocus,
onSaved: (v) {
route.via = v;
if (v != null) {
route.via = v;
}
})),
//TODO: Android doesn't appear to support route based MTU, figure this out
// ConfigItem(
@ -94,7 +101,7 @@ class _UnsafeRouteScreenState extends State<UnsafeRouteScreen> {
color: CupertinoColors.systemRed.resolveFrom(context),
onPressed: () => Utils.confirmDelete(context, 'Delete unsafe route?', () {
Navigator.of(context).pop();
widget.onDelete();
widget.onDelete!();
}),
)))
: Container()
@ -103,8 +110,6 @@ class _UnsafeRouteScreenState extends State<UnsafeRouteScreen> {
_onSave() {
Navigator.pop(context);
if (widget.onSave != null) {
widget.onSave(route);
}
widget.onSave(route);
}
}

View File

@ -8,7 +8,11 @@ import 'package:mobile_nebula/screens/siteConfig/UnsafeRouteScreen.dart';
import 'package:mobile_nebula/services/utils.dart';
class UnsafeRoutesScreen extends StatefulWidget {
const UnsafeRoutesScreen({Key key, @required this.unsafeRoutes, @required this.onSave}) : super(key: key);
const UnsafeRoutesScreen({
Key? key,
required this.unsafeRoutes,
required this.onSave,
}) : super(key: key);
final List<UnsafeRoute> unsafeRoutes;
final ValueChanged<List<UnsafeRoute>> onSave;
@ -18,11 +22,12 @@ class UnsafeRoutesScreen extends StatefulWidget {
}
class _UnsafeRoutesScreenState extends State<UnsafeRoutesScreen> {
Map<Key, UnsafeRoute> unsafeRoutes = {};
late Map<Key, UnsafeRoute> unsafeRoutes;
bool changed = false;
@override
void initState() {
unsafeRoutes = {};
widget.unsafeRoutes.forEach((route) {
unsafeRoutes[UniqueKey()] = route;
});
@ -43,9 +48,7 @@ class _UnsafeRoutesScreenState extends State<UnsafeRoutesScreen> {
_onSave() {
Navigator.pop(context);
if (widget.onSave != null) {
widget.onSave(unsafeRoutes.values.toList());
}
widget.onSave(unsafeRoutes.values.toList());
}
List<Widget> _buildRoutes() {
@ -53,7 +56,7 @@ class _UnsafeRoutesScreenState extends State<UnsafeRoutesScreen> {
List<Widget> items = [];
unsafeRoutes.forEach((key, route) {
items.add(ConfigPageItem(
label: Text(route.route),
label: Text(route.route ?? ''),
labelWidth: ipWidth,
content: Text('via ${route.via}', textAlign: TextAlign.end),
onPressed: () {

View File

@ -7,7 +7,6 @@ import 'package:mobile_nebula/services/storage.dart';
class Settings {
final _storage = Storage();
StreamController _change = StreamController.broadcast();
var _ready = Completer<Settings>();
var _settings = Map<String, dynamic>();
bool get useSystemColors {
@ -78,14 +77,11 @@ class Settings {
}
Settings._internal() {
_ready = Completer<Settings>();
_storage.readFile("config.json").then((rawConfig) {
if (rawConfig != null) {
_settings = jsonDecode(rawConfig);
}
_ready.complete();
_change.add(null);
});
}

View File

@ -12,13 +12,17 @@ class Share {
/// - title: Title of message or subject if sending an email
/// - text: The text to share
/// - filename: The filename to use if sending over airdrop for example
static Future<bool> share({@required String title, @required String text, @required String filename}) async {
assert(title != null && title.isNotEmpty);
assert(text != null && text.isNotEmpty);
assert(filename != null && filename.isNotEmpty);
static Future<bool> share({
required String title,
required String text,
required String filename,
}) async {
assert(title.isNotEmpty);
assert(text.isNotEmpty);
assert(filename.isNotEmpty);
if (title == null || title.isEmpty) {
throw FlutterError('Title cannot be null');
if (title.isEmpty) {
throw FlutterError('Title cannot be empty');
}
final bool success = await _channel.invokeMethod('share', <String, dynamic>{
@ -34,14 +38,18 @@ class Share {
/// - title: Title of message or subject if sending an email
/// - filePath: Path to the file to share
/// - filename: An optional filename to override the existing file
static Future<bool> shareFile({@required String title, @required String filePath, String filename}) async {
assert(title != null && title.isNotEmpty);
assert(filePath != null && filePath.isNotEmpty);
static Future<bool> shareFile({
required String title,
required String filePath,
String? filename,
}) async {
assert(title.isNotEmpty);
assert(filePath.isNotEmpty);
if (title == null || title.isEmpty) {
throw FlutterError('Title cannot be null');
} else if (filePath == null || filePath.isEmpty) {
throw FlutterError('FilePath cannot be null');
if (title.isEmpty) {
throw FlutterError('Title cannot be empty');
} else if (filePath.isEmpty) {
throw FlutterError('FilePath cannot be empty');
}
final bool success = await _channel.invokeMethod('shareFile', <String, dynamic>{

View File

@ -34,7 +34,7 @@ class Storage {
return directory.path;
}
Future<String> readFile(String path) async {
Future<String?> readFile(String path) async {
try {
final parent = await localPath;
final file = File(p.join(parent, path));

View File

@ -52,7 +52,7 @@ class Utils {
/// Builds a simple leading widget that pops the current screen.
/// Provide your own onPressed to override that behavior, just remember you have to pop
static Widget leadingBackWidget(BuildContext context, {label = 'Back', Function onPressed}) {
static Widget leadingBackWidget(BuildContext context, {label = 'Back', Function? onPressed}) {
if (Platform.isIOS) {
return CupertinoButton(
child: Row(children: <Widget>[Icon(context.platformIcons.back), Text(label)]),
@ -83,11 +83,11 @@ class Utils {
static Widget trailingSaveWidget(BuildContext context, Function onPressed) {
return CupertinoButton(
child: Text('Save', style: TextStyle(
fontWeight: FontWeight.bold,
//TODO: For some reason on android if inherit is the default of true the text color here turns to the background color
inherit: Platform.isIOS ? true : false
)),
child: Text('Save',
style: TextStyle(
fontWeight: FontWeight.bold,
//TODO: For some reason on android if inherit is the default of true the text color here turns to the background color
inherit: Platform.isIOS ? true : false)),
padding: Platform.isAndroid ? null : EdgeInsets.zero,
onPressed: () => onPressed());
}
@ -122,7 +122,7 @@ class Utils {
});
}
static popError(BuildContext context, String title, String error, {StackTrace stack}) {
static popError(BuildContext context, String title, String error, {StackTrace? stack}) {
if (stack != null) {
error += '\n${stack.toString()}';
}
@ -170,14 +170,14 @@ class Utils {
return int.parse(parts[3]) | int.parse(parts[2]) << 8 | int.parse(parts[1]) << 16 | int.parse(parts[0]) << 24;
}
static Future<String> pickFile(BuildContext context) async {
static Future<String?> pickFile(BuildContext context) async {
await FilePicker.platform.clearTemporaryFiles();
final result = await FilePicker.platform.pickFiles(allowMultiple: false);
if (result == null) {
return null;
}
final file = File(result.files.first.path);
final file = File(result!.files.first.path!);
return file.readAsString();
}
}

View File

@ -1,6 +1,10 @@
import 'dart:io';
bool ipValidator(String str, bool enableIPV6) {
bool ipValidator(String? str, bool enableIPV6) {
if (str == null) {
return false;
}
final ia = InternetAddress.tryParse(str);
if (ia == null) {
return false;

View File

@ -6,7 +6,7 @@ go 1.18
require (
github.com/sirupsen/logrus v1.8.1
github.com/slackhq/nebula v1.6.0
github.com/slackhq/nebula v1.6.1-0.20220919174748-4c0ae3df5ef7
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29
gopkg.in/yaml.v2 v2.4.0
)
@ -33,7 +33,6 @@ require (
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 // indirect
github.com/vishvananda/netlink v1.1.0 // indirect
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
golang.org/x/mobile v0.0.0-20220722155234-aaac322e2105 // indirect
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
golang.org/x/net v0.0.0-20220403103023-749bd193bc2b // indirect
golang.org/x/sys v0.0.0-20220406155245-289d7a0edf71 // indirect

View File

@ -210,8 +210,8 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/slackhq/nebula v1.6.0 h1:1M2txSJq5Jef/A68Kw6SwdLS0PMtjhx4X509ZBHtG54=
github.com/slackhq/nebula v1.6.0/go.mod h1:UmkqnXe4O53QwToSl/gG7sM4BroQwAB7dd4hUaT6MlI=
github.com/slackhq/nebula v1.6.1-0.20220919174748-4c0ae3df5ef7 h1:u06zUX/HdLxZnyewES34xIjRELl5xDXCLseiiBn6Dyo=
github.com/slackhq/nebula v1.6.1-0.20220919174748-4c0ae3df5ef7/go.mod h1:UmkqnXe4O53QwToSl/gG7sM4BroQwAB7dd4hUaT6MlI=
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 h1:TG/diQgUe0pntT/2D9tmUCz4VNwm9MfrtPr0SU2qSX8=
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@ -231,7 +231,6 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
@ -249,7 +248,6 @@ golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
@ -270,10 +268,7 @@ golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRu
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mobile v0.0.0-20220722155234-aaac322e2105 h1:3vUV5x5+3LfQbgk7paCM6INOaJG9xXQbn79xoNkwfIk=
golang.org/x/mobile v0.0.0-20220722155234-aaac322e2105/go.mod h1:pe2sM7Uk+2Su1y7u/6Z8KJ24D7lepUjFZbhFOrmDfuQ=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
@ -316,7 +311,6 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220403103023-749bd193bc2b h1:vI32FkLJNAWtGD4BwkThwEy6XS7ZLLMHkSkYfF8M0W0=
@ -383,7 +377,6 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220406155245-289d7a0edf71 h1:PRD0hj6tTuUnCFD08vkvjkYFbQg/9lV8KIxe1y4/cvU=
@ -444,7 +437,6 @@ golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.8-0.20211022200916-316ba0b74098/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20=
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@ -7,14 +7,7 @@ packages:
name: async
url: "https://pub.dartlang.org"
source: hosted
version: "2.8.2"
barcode_scan:
dependency: "direct main"
description:
name: barcode_scan
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.1"
version: "2.9.0"
boolean_selector:
dependency: transitive
description:
@ -28,28 +21,21 @@ packages:
name: characters
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
charcode:
dependency: transitive
description:
name: charcode
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.1"
version: "1.2.1"
clock:
dependency: transitive
description:
name: clock
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
version: "1.1.1"
collection:
dependency: transitive
description:
name: collection
url: "https://pub.dartlang.org"
source: hosted
version: "1.15.0"
version: "1.16.0"
crypto:
dependency: transitive
description:
@ -70,14 +56,14 @@ packages:
name: fake_async
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
version: "1.3.1"
ffi:
dependency: transitive
description:
name: ffi
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.2"
version: "2.0.1"
file:
dependency: transitive
description:
@ -91,33 +77,33 @@ packages:
name: file_picker
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.4"
fixnum:
dependency: transitive
description:
name: fixnum
url: "https://pub.dartlang.org"
source: hosted
version: "0.10.11"
version: "5.0.1"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_barcode_scanner:
dependency: "direct main"
description:
name: flutter_barcode_scanner
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
flutter_platform_widgets:
dependency: "direct main"
description:
name: flutter_platform_widgets
url: "https://pub.dartlang.org"
source: hosted
version: "1.12.1"
version: "2.0.0"
flutter_plugin_android_lifecycle:
dependency: transitive
description:
name: flutter_plugin_android_lifecycle
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.5"
version: "2.0.7"
flutter_test:
dependency: "direct dev"
description: flutter
@ -134,21 +120,28 @@ packages:
name: js
url: "https://pub.dartlang.org"
source: hosted
version: "0.6.3"
version: "0.6.4"
matcher:
dependency: transitive
description:
name: matcher
url: "https://pub.dartlang.org"
source: hosted
version: "0.12.11"
version: "0.12.12"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.5"
meta:
dependency: transitive
description:
name: meta
url: "https://pub.dartlang.org"
source: hosted
version: "1.7.0"
version: "1.8.0"
package_info:
dependency: "direct main"
description:
@ -162,14 +155,14 @@ packages:
name: path
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.0"
version: "1.8.2"
path_provider:
dependency: "direct main"
description:
name: path_provider
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.8"
version: "2.0.11"
path_provider_android:
dependency: transitive
description:
@ -190,7 +183,7 @@ packages:
name: path_provider_linux
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.4"
version: "2.1.7"
path_provider_macos:
dependency: transitive
description:
@ -211,7 +204,7 @@ packages:
name: path_provider_windows
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.4"
version: "2.1.3"
platform:
dependency: transitive
description:
@ -233,13 +226,6 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "4.2.4"
protobuf:
dependency: transitive
description:
name: protobuf
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.4"
pull_to_refresh:
dependency: "direct main"
description:
@ -258,7 +244,7 @@ packages:
name: source_span
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.1"
version: "1.9.0"
stack_trace:
dependency: transitive
description:
@ -279,21 +265,21 @@ packages:
name: string_scanner
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
version: "1.1.1"
term_glyph:
dependency: transitive
description:
name: term_glyph
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
version: "1.2.1"
test_api:
dependency: transitive
description:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.3"
version: "0.4.12"
typed_data:
dependency: transitive
description:
@ -370,14 +356,14 @@ packages:
name: vector_math
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.1"
version: "2.1.2"
win32:
dependency: transitive
description:
name: win32
url: "https://pub.dartlang.org"
source: hosted
version: "2.3.3"
version: "2.7.0"
xdg_directories:
dependency: transitive
description:
@ -386,5 +372,5 @@ packages:
source: hosted
version: "0.2.0"
sdks:
dart: ">=2.14.0 <3.0.0"
flutter: ">=2.5.0"
dart: ">=2.18.1 <3.0.0"
flutter: ">=3.0.0"

View File

@ -11,10 +11,10 @@ description: Mobile Nebula Client
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 0.1.0+52
version: 0.1.0+54
environment:
sdk: ">=2.1.0 <3.0.0"
sdk: '>=2.18.1 <3.0.0'
dependencies:
flutter:
@ -23,14 +23,14 @@ dependencies:
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2
flutter_platform_widgets: ^1.2.0
path_provider: ^2.0.1
file_picker: ^3.0.2+2
barcode_scan: ^3.0.1
flutter_platform_widgets: ^2.0.0
path_provider: ^2.0.11
file_picker: ^5.0.1
uuid: ^3.0.4
package_info: ^2.0.0
url_launcher: ^6.0.6
pull_to_refresh: ^2.0.0
flutter_barcode_scanner: ^2.0.0
dev_dependencies:
flutter_test: