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) Install all of the following things:
- [`gomobile`](https://godoc.org/golang.org/x/mobile/cmd/gomobile)
- [`xcode`](https://apps.apple.com/us/app/xcode/)
- [`android-studio`](https://developer.android.com/studio) - [`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 # Formatting
@ -21,7 +35,6 @@ Use:
flutter format lib/ test/ -l 120 flutter format lib/ test/ -l 120
``` ```
# Release # Release
Update `version` in `pubspec.yaml` to reflect this release, then 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 /local.properties
GeneratedPluginRegistrant.java GeneratedPluginRegistrant.java
/build/build-attribution/ /build/build-attribution/
/mobileNebula/mobileNebula.aar

View File

@ -32,23 +32,28 @@ if (keystorePropertiesFile.exists()) {
} }
android { 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 { sourceSets {
main.java.srcDirs += 'src/main/kotlin' main.java.srcDirs += 'src/main/kotlin'
} }
lintOptions {
disable 'InvalidPackage'
}
defaultConfig { defaultConfig {
applicationId "net.defined.mobile_nebula" applicationId "net.defined.mobile_nebula"
minSdkVersion 29 minSdkVersion 29 //flutter.minSdkVersion
targetSdkVersion 30 targetSdkVersion 30 //flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
} }
signingConfigs { signingConfigs {
@ -62,17 +67,18 @@ android {
buildTypes { buildTypes {
release { release {
signingConfig signingConfigs.release // signingConfig signingConfigs.release
//
// We are disabling minification and proguard because it wrecks the crypto for storing keys // // 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 // // Ideally we would turn these on. We had issues with gson as well but resolved those with proguardFiles
shrinkResources false // shrinkResources false
minifyEnabled false // minifyEnabled false
useProguard false // useProguard false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' // proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
//
resValue 'string', 'app_name', '"Nebula"' resValue 'string', 'app_name', '"Nebula"'
} }
debug { debug {
resValue 'string', 'app_name', '"Nebula-DEBUG"' resValue 'string', 'app_name', '"Nebula-DEBUG"'
applicationIdSuffix '.debug' applicationIdSuffix '.debug'
@ -84,26 +90,9 @@ flutter {
source '../..' source '../..'
} }
repositories {
flatDir {
dirs 'src/main/libs'
}
}
dependencies { dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 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' implementation 'com.google.code.gson:gson:2.8.6'
implementation project(':mobileNebula')
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'
}
}
} }

View File

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

View File

@ -33,7 +33,7 @@ class Share {
} catch (err: Exception) { } catch (err: Exception) {
Log.println(Log.ERROR, "", "Share: Error") 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) { } catch (err: Exception) {
Log.println(Log.ERROR, "", "Share: Error") 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) { } catch (err: Exception) {
Log.println(Log.ERROR, "", "Share: Error") 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) result.success(true)

View File

@ -1,12 +1,12 @@
buildscript { buildscript {
ext.kotlin_version = '1.3.61' ext.kotlin_version = '1.6.10'
repositories { repositories {
google() google()
jcenter() mavenCentral()
} }
dependencies { 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" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
} }
} }
@ -14,7 +14,7 @@ buildscript {
allprojects { allprojects {
repositories { repositories {
google() google()
jcenter() mavenCentral()
} }
} }

View File

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

View File

@ -1,6 +1,5 @@
#Fri Jun 05 14:55:48 CDT 2020
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists 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() assert localPropertiesFile.exists()
def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
if (pluginsFile.exists()) {
pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
}
plugins.each { name, path -> def flutterSdkPath = properties.getProperty("flutter.sdk")
def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
include ":$name" apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
project(":$name").projectDir = pluginDirectory
}

View File

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

View File

@ -16,9 +16,9 @@ if [ "$1" = "ios" ]; then
elif [ "$1" = "android" ]; then elif [ "$1" = "android" ]; then
# Build nebula for android # Build nebula for android
make mobileNebula.aar make mobileNebula.aar
mkdir -p ../android/app/src/main/libs mkdir -p ../android/mobileNebula
rm -rf ../android/app/src/main/libs/mobileNebula.aar rm -rf ../android/mobileNebula/mobileNebula.aar
cp mobileNebula.aar ../android/app/src/main/libs/mobileNebula.aar cp mobileNebula.aar ../android/mobileNebula/mobileNebula.aar
else else
echo "Error: unsupported target os $1" echo "Error: unsupported target os $1"

View File

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

View File

@ -1,64 +1,57 @@
PODS: PODS:
- barcode_scan (0.0.1): - DKImagePickerController/Core (4.3.4):
- Flutter
- MTBBarcodeScanner
- SwiftProtobuf
- DKImagePickerController/Core (4.3.0):
- DKImagePickerController/ImageDataManager - DKImagePickerController/ImageDataManager
- DKImagePickerController/Resource - DKImagePickerController/Resource
- DKImagePickerController/ImageDataManager (4.3.0) - DKImagePickerController/ImageDataManager (4.3.4)
- DKImagePickerController/PhotoGallery (4.3.0): - DKImagePickerController/PhotoGallery (4.3.4):
- DKImagePickerController/Core - DKImagePickerController/Core
- DKPhotoGallery - DKPhotoGallery
- DKImagePickerController/Resource (4.3.0) - DKImagePickerController/Resource (4.3.4)
- DKPhotoGallery (0.0.15): - DKPhotoGallery (0.0.17):
- DKPhotoGallery/Core (= 0.0.15) - DKPhotoGallery/Core (= 0.0.17)
- DKPhotoGallery/Model (= 0.0.15) - DKPhotoGallery/Model (= 0.0.17)
- DKPhotoGallery/Preview (= 0.0.15) - DKPhotoGallery/Preview (= 0.0.17)
- DKPhotoGallery/Resource (= 0.0.15) - DKPhotoGallery/Resource (= 0.0.17)
- SDWebImage - SDWebImage
- SDWebImageFLPlugin - SwiftyGif
- DKPhotoGallery/Core (0.0.15): - DKPhotoGallery/Core (0.0.17):
- DKPhotoGallery/Model - DKPhotoGallery/Model
- DKPhotoGallery/Preview - DKPhotoGallery/Preview
- SDWebImage - SDWebImage
- SDWebImageFLPlugin - SwiftyGif
- DKPhotoGallery/Model (0.0.15): - DKPhotoGallery/Model (0.0.17):
- SDWebImage - SDWebImage
- SDWebImageFLPlugin - SwiftyGif
- DKPhotoGallery/Preview (0.0.15): - DKPhotoGallery/Preview (0.0.17):
- DKPhotoGallery/Model - DKPhotoGallery/Model
- DKPhotoGallery/Resource - DKPhotoGallery/Resource
- SDWebImage - SDWebImage
- SDWebImageFLPlugin - SwiftyGif
- DKPhotoGallery/Resource (0.0.15): - DKPhotoGallery/Resource (0.0.17):
- SDWebImage - SDWebImage
- SDWebImageFLPlugin - SwiftyGif
- file_picker (0.0.1): - file_picker (0.0.1):
- DKImagePickerController/PhotoGallery - DKImagePickerController/PhotoGallery
- Flutter - Flutter
- FLAnimatedImage (1.0.12)
- Flutter (1.0.0) - Flutter (1.0.0)
- MTBBarcodeScanner (5.0.11) - flutter_barcode_scanner (2.0.0):
- Flutter
- package_info (0.0.1): - package_info (0.0.1):
- Flutter - Flutter
- path_provider_ios (0.0.1): - path_provider_ios (0.0.1):
- Flutter - Flutter
- SDWebImage (5.8.0): - SDWebImage (5.13.3):
- SDWebImage/Core (= 5.8.0) - SDWebImage/Core (= 5.13.3)
- SDWebImage/Core (5.8.0) - SDWebImage/Core (5.13.3)
- SDWebImageFLPlugin (0.4.0): - SwiftyGif (5.4.3)
- FLAnimatedImage (>= 1.0.11)
- SDWebImage/Core (~> 5.6)
- SwiftProtobuf (1.9.0)
- SwiftyJSON (5.0.1) - SwiftyJSON (5.0.1)
- url_launcher_ios (0.0.1): - url_launcher_ios (0.0.1):
- Flutter - Flutter
DEPENDENCIES: DEPENDENCIES:
- barcode_scan (from `.symlinks/plugins/barcode_scan/ios`)
- file_picker (from `.symlinks/plugins/file_picker/ios`) - file_picker (from `.symlinks/plugins/file_picker/ios`)
- Flutter (from `Flutter`) - Flutter (from `Flutter`)
- flutter_barcode_scanner (from `.symlinks/plugins/flutter_barcode_scanner/ios`)
- package_info (from `.symlinks/plugins/package_info/ios`) - package_info (from `.symlinks/plugins/package_info/ios`)
- path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`) - path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`)
- SwiftyJSON (~> 5.0) - SwiftyJSON (~> 5.0)
@ -68,20 +61,17 @@ SPEC REPOS:
trunk: trunk:
- DKImagePickerController - DKImagePickerController
- DKPhotoGallery - DKPhotoGallery
- FLAnimatedImage
- MTBBarcodeScanner
- SDWebImage - SDWebImage
- SDWebImageFLPlugin - SwiftyGif
- SwiftProtobuf
- SwiftyJSON - SwiftyJSON
EXTERNAL SOURCES: EXTERNAL SOURCES:
barcode_scan:
:path: ".symlinks/plugins/barcode_scan/ios"
file_picker: file_picker:
:path: ".symlinks/plugins/file_picker/ios" :path: ".symlinks/plugins/file_picker/ios"
Flutter: Flutter:
:path: Flutter :path: Flutter
flutter_barcode_scanner:
:path: ".symlinks/plugins/flutter_barcode_scanner/ios"
package_info: package_info:
:path: ".symlinks/plugins/package_info/ios" :path: ".symlinks/plugins/package_info/ios"
path_provider_ios: path_provider_ios:
@ -90,21 +80,18 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/url_launcher_ios/ios" :path: ".symlinks/plugins/url_launcher_ios/ios"
SPEC CHECKSUMS: SPEC CHECKSUMS:
barcode_scan: a5c27959edfafaa0c771905bad0b29d6d39e4479 DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac
DKImagePickerController: 397702a3590d4958fad336e9a77079935c500ddb DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179
DKPhotoGallery: e880aef16c108333240e1e7327896f2ea380f4f0 file_picker: 817ab1d8cd2da9d2da412a417162deee3500fc95
file_picker: 3e6c3790de664ccf9b882732d9db5eaf6b8d4eb1 Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
FLAnimatedImage: 4a0b56255d9b05f18b6dd7ee06871be5d3b89e31 flutter_barcode_scanner: 7a1144744c28dc0c57a8de7218ffe5ec59a9e4bf
Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a
MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb
package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62 package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62
path_provider_ios: 7d7ce634493af4477d156294792024ec3485acd5 path_provider_ios: 7d7ce634493af4477d156294792024ec3485acd5
SDWebImage: 84000f962cbfa70c07f19d2234cbfcf5d779b5dc SDWebImage: af5bbffef2cde09f148d826f9733dcde1a9414cd
SDWebImageFLPlugin: 6c2295fb1242d44467c6c87dc5db6b0a13228fd8 SwiftyGif: 6c3eafd0ce693cad58bb63d2b2fb9bacb8552780
SwiftProtobuf: ecbec1be9036d15655f6b3443a1c4ea693c97932
SwiftyJSON: 2f33a42c6fbc52764d96f13368585094bfd8aa5e SwiftyJSON: 2f33a42c6fbc52764d96f13368585094bfd8aa5e
url_launcher_ios: 02f1989d4e14e998335b02b67a7590fa34f971af url_launcher_ios: 02f1989d4e14e998335b02b67a7590fa34f971af
PODFILE CHECKSUM: 92e176614f91c6517d4254a0edec8b66f076c77e 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", "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh",
"${BUILT_PRODUCTS_DIR}/DKImagePickerController/DKImagePickerController.framework", "${BUILT_PRODUCTS_DIR}/DKImagePickerController/DKImagePickerController.framework",
"${BUILT_PRODUCTS_DIR}/DKPhotoGallery/DKPhotoGallery.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}/SDWebImage/SDWebImage.framework",
"${BUILT_PRODUCTS_DIR}/SDWebImageFLPlugin/SDWebImageFLPlugin.framework", "${BUILT_PRODUCTS_DIR}/SwiftyGif/SwiftyGif.framework",
"${BUILT_PRODUCTS_DIR}/SwiftProtobuf/SwiftProtobuf.framework",
"${BUILT_PRODUCTS_DIR}/SwiftyJSON/SwiftyJSON.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}/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}/package_info/package_info.framework",
"${BUILT_PRODUCTS_DIR}/path_provider_ios/path_provider_ios.framework", "${BUILT_PRODUCTS_DIR}/path_provider_ios/path_provider_ios.framework",
"${BUILT_PRODUCTS_DIR}/url_launcher_ios/url_launcher_ios.framework", "${BUILT_PRODUCTS_DIR}/url_launcher_ios/url_launcher_ios.framework",
@ -355,14 +352,11 @@
outputPaths = ( outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DKImagePickerController.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DKImagePickerController.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DKPhotoGallery.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}/SDWebImage.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImageFLPlugin.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftyGif.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftProtobuf.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftyJSON.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}/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}/package_info.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider_ios.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider_ios.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/url_launcher_ios.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/url_launcher_ios.framework",
@ -558,7 +552,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 4;
DEVELOPMENT_TEAM = 576H3XS7FP; DEVELOPMENT_TEAM = 576H3XS7FP;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
@ -596,7 +590,7 @@
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = NebulaNetworkExtension/NebulaNetworkExtension.entitlements; CODE_SIGN_ENTITLEMENTS = NebulaNetworkExtension/NebulaNetworkExtension.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 4;
DEVELOPMENT_TEAM = 576H3XS7FP; DEVELOPMENT_TEAM = 576H3XS7FP;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
@ -637,7 +631,7 @@
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = NebulaNetworkExtension/NebulaNetworkExtension.entitlements; CODE_SIGN_ENTITLEMENTS = NebulaNetworkExtension/NebulaNetworkExtension.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 4;
DEVELOPMENT_TEAM = 576H3XS7FP; DEVELOPMENT_TEAM = 576H3XS7FP;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
@ -675,7 +669,7 @@
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = NebulaNetworkExtension/NebulaNetworkExtension.entitlements; CODE_SIGN_ENTITLEMENTS = NebulaNetworkExtension/NebulaNetworkExtension.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 4;
DEVELOPMENT_TEAM = 576H3XS7FP; DEVELOPMENT_TEAM = 576H3XS7FP;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
@ -816,7 +810,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 4;
DEVELOPMENT_TEAM = 576H3XS7FP; DEVELOPMENT_TEAM = 576H3XS7FP;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
@ -852,7 +846,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 4;
DEVELOPMENT_TEAM = 576H3XS7FP; DEVELOPMENT_TEAM = 576H3XS7FP;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (

View File

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

View File

@ -8,7 +8,7 @@ import 'IPField.dart';
//TODO: Support initialValue //TODO: Support initialValue
class CIDRField extends StatefulWidget { class CIDRField extends StatefulWidget {
const CIDRField({ const CIDRField({
Key key, Key? key,
this.ipHelp = "ip address", this.ipHelp = "ip address",
this.autoFocus = false, this.autoFocus = false,
this.focusNode, this.focusNode,
@ -21,12 +21,12 @@ class CIDRField extends StatefulWidget {
final String ipHelp; final String ipHelp;
final bool autoFocus; final bool autoFocus;
final FocusNode focusNode; final FocusNode? focusNode;
final FocusNode nextFocusNode; final FocusNode? nextFocusNode;
final ValueChanged<CIDR> onChanged; final ValueChanged<CIDR>? onChanged;
final TextInputAction textInputAction; final TextInputAction? textInputAction;
final TextEditingController ipController; final TextEditingController? ipController;
final TextEditingController bitsController; final TextEditingController? bitsController;
@override @override
_CIDRFieldState createState() => _CIDRFieldState(); _CIDRFieldState createState() => _CIDRFieldState();
@ -44,7 +44,7 @@ class _CIDRFieldState extends State<CIDRField> {
void initState() { void initState() {
//TODO: this won't track external controller changes appropriately //TODO: this won't track external controller changes appropriately
cidr.ip = widget.ipController?.text ?? ""; cidr.ip = widget.ipController?.text ?? "";
cidr.bits = int.tryParse(widget.bitsController?.text ?? ""); cidr.bits = int.tryParse(widget.bitsController?.text ?? "") ?? 0;
super.initState(); super.initState();
} }
@ -66,8 +66,12 @@ class _CIDRFieldState extends State<CIDRField> {
focusNode: widget.focusNode, focusNode: widget.focusNode,
nextFocusNode: bitsFocus, nextFocusNode: bitsFocus,
onChanged: (val) { onChanged: (val) {
if (widget.onChanged == null) {
return;
}
cidr.ip = val; cidr.ip = val;
widget.onChanged(cidr); widget.onChanged!(cidr);
}, },
controller: widget.ipController, controller: widget.ipController,
))), ))),
@ -81,8 +85,12 @@ class _CIDRFieldState extends State<CIDRField> {
nextFocusNode: widget.nextFocusNode, nextFocusNode: widget.nextFocusNode,
controller: widget.bitsController, controller: widget.bitsController,
onChanged: (val) { onChanged: (val) {
cidr.bits = int.tryParse(val ?? ""); if (widget.onChanged == null) {
widget.onChanged(cidr); return;
}
cidr.bits = int.tryParse(val) ?? 0;
widget.onChanged!(cidr);
}, },
maxLength: 2, maxLength: 2,
inputFormatters: [FilteringTextInputFormatter.digitsOnly], inputFormatters: [FilteringTextInputFormatter.digitsOnly],

View File

@ -6,15 +6,15 @@ import 'package:mobile_nebula/validators/ipValidator.dart';
class CIDRFormField extends FormField<CIDR> { class CIDRFormField extends FormField<CIDR> {
//TODO: onSaved, validator, auto-validate, enabled? //TODO: onSaved, validator, auto-validate, enabled?
CIDRFormField({ CIDRFormField({
Key key, Key? key,
autoFocus = false, autoFocus = false,
enableIPV6 = false, enableIPV6 = false,
focusNode, focusNode,
nextFocusNode, nextFocusNode,
ValueChanged<CIDR> onChanged, ValueChanged<CIDR>? onChanged,
FormFieldSetter<CIDR> onSaved, FormFieldSetter<CIDR>? onSaved,
textInputAction, textInputAction,
CIDR initialValue, CIDR? initialValue,
this.ipController, this.ipController,
this.bitsController, this.bitsController,
}) : super( }) : super(
@ -30,14 +30,14 @@ class CIDRFormField extends FormField<CIDR> {
return 'Please enter a valid ip address'; 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 "Please enter a valid number of bits";
} }
return null; return null;
}, },
builder: (FormFieldState<CIDR> field) { builder: (FormFieldState<CIDR> field) {
final _CIDRFormField state = field; final _CIDRFormField state = field as _CIDRFormField;
void onChangedHandler(CIDR value) { void onChangedHandler(CIDR value) {
if (onChanged != null) { if (onChanged != null) {
@ -57,50 +57,50 @@ class CIDRFormField extends FormField<CIDR> {
bitsController: state._effectiveBitsController, bitsController: state._effectiveBitsController,
), ),
field.hasError field.hasError
? Text(field.errorText, ? Text(field.errorText ?? "Unknown error",
style: TextStyle(color: CupertinoColors.systemRed.resolveFrom(field.context), fontSize: 13), style: TextStyle(color: CupertinoColors.systemRed.resolveFrom(field.context), fontSize: 13),
textAlign: TextAlign.end) textAlign: TextAlign.end)
: Container(height: 0) : Container(height: 0)
]); ]);
}); });
final TextEditingController ipController; final TextEditingController? ipController;
final TextEditingController bitsController; final TextEditingController? bitsController;
@override @override
_CIDRFormField createState() => _CIDRFormField(); _CIDRFormField createState() => _CIDRFormField();
} }
class _CIDRFormField extends FormFieldState<CIDR> { class _CIDRFormField extends FormFieldState<CIDR> {
TextEditingController _ipController; TextEditingController? _ipController = TextEditingController();
TextEditingController _bitsController; TextEditingController? _bitsController = TextEditingController();
TextEditingController get _effectiveIPController => widget.ipController ?? _ipController; TextEditingController get _effectiveIPController => widget.ipController ?? _ipController!;
TextEditingController get _effectiveBitsController => widget.bitsController ?? _bitsController; TextEditingController get _effectiveBitsController => widget.bitsController ?? _bitsController!;
@override @override
CIDRFormField get widget => super.widget; CIDRFormField get widget => super.widget as CIDRFormField;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
if (widget.ipController == null) { if (widget.ipController == null) {
_ipController = TextEditingController(text: widget.initialValue.ip); _ipController = TextEditingController(text: widget.initialValue?.ip);
} else { } else {
widget.ipController.addListener(_handleControllerChanged); widget.ipController!.addListener(_handleControllerChanged);
} }
if (widget.bitsController == null) { if (widget.bitsController == null) {
_bitsController = TextEditingController(text: widget.initialValue?.bits?.toString() ?? ""); _bitsController = TextEditingController(text: widget.initialValue?.bits?.toString() ?? "");
} else { } else {
widget.bitsController.addListener(_handleControllerChanged); widget.bitsController!.addListener(_handleControllerChanged);
} }
} }
@override @override
void didUpdateWidget(CIDRFormField oldWidget) { void didUpdateWidget(CIDRFormField oldWidget) {
super.didUpdateWidget(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; bool shouldUpdate = false;
if (widget.ipController != oldWidget.ipController) { if (widget.ipController != oldWidget.ipController) {
@ -108,12 +108,12 @@ class _CIDRFormField extends FormFieldState<CIDR> {
widget.ipController?.addListener(_handleControllerChanged); widget.ipController?.addListener(_handleControllerChanged);
if (oldWidget.ipController != null && widget.ipController == null) { if (oldWidget.ipController != null && widget.ipController == null) {
_ipController = TextEditingController.fromValue(oldWidget.ipController.value); _ipController = TextEditingController.fromValue(oldWidget.ipController!.value);
} }
if (widget.ipController != null) { if (widget.ipController != null) {
shouldUpdate = true; shouldUpdate = true;
update.ip = widget.ipController.text; update.ip = widget.ipController!.text;
if (oldWidget.ipController == null) _ipController = null; if (oldWidget.ipController == null) _ipController = null;
} }
} }
@ -123,12 +123,12 @@ class _CIDRFormField extends FormFieldState<CIDR> {
widget.bitsController?.addListener(_handleControllerChanged); widget.bitsController?.addListener(_handleControllerChanged);
if (oldWidget.bitsController != null && widget.bitsController == null) { if (oldWidget.bitsController != null && widget.bitsController == null) {
_bitsController = TextEditingController.fromValue(oldWidget.bitsController.value); _bitsController = TextEditingController.fromValue(oldWidget.bitsController!.value);
} }
if (widget.bitsController != null) { if (widget.bitsController != null) {
shouldUpdate = true; shouldUpdate = true;
update.bits = int.parse(widget.bitsController.text); update.bits = int.parse(widget.bitsController!.text);
if (oldWidget.bitsController == null) _bitsController = null; if (oldWidget.bitsController == null) _bitsController = null;
} }
} }
@ -149,8 +149,8 @@ class _CIDRFormField extends FormFieldState<CIDR> {
void reset() { void reset() {
super.reset(); super.reset();
setState(() { setState(() {
_effectiveIPController.text = widget.initialValue.ip; _effectiveIPController.text = widget.initialValue?.ip ?? "";
_effectiveBitsController.text = widget.initialValue.bits.toString(); _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 // example, the reset() method. In such cases, the FormField value will
// already have been set. // already have been set.
final effectiveBits = int.parse(_effectiveBitsController.text); 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)); 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 /// SimplePage with a form and built in validation and confirmation to discard changes if any are made
class FormPage extends StatefulWidget { class FormPage extends StatefulWidget {
const FormPage( const FormPage(
{Key key, {Key? key,
this.title, required this.title,
@required this.child, required this.child,
@required this.onSave, required this.onSave,
@required this.changed, required this.changed,
this.hideSave = false, this.hideSave = false,
this.scrollController}) this.scrollController})
: super(key: key); : super(key: key);
@ -20,7 +20,7 @@ class FormPage extends StatefulWidget {
final String title; final String title;
final Function onSave; final Function onSave;
final Widget child; 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 /// If you need the page to progress to a certain point before saving, control it here
final bool hideSave; final bool hideSave;
@ -90,11 +90,15 @@ class _FormPageState extends State<FormPage> {
Utils.trailingSaveWidget( Utils.trailingSaveWidget(
context, context,
() { () {
if (!_formKey.currentState.validate()) { if (_formKey.currentState == null) {
return; return;
} }
_formKey.currentState.save(); if (!_formKey.currentState!.validate()) {
return;
}
_formKey.currentState!.save();
widget.onSave(); widget.onSave();
}, },
) )

View File

@ -8,13 +8,13 @@ import 'IPField.dart';
//TODO: Support initialValue //TODO: Support initialValue
class IPAndPortField extends StatefulWidget { class IPAndPortField extends StatefulWidget {
const IPAndPortField({ const IPAndPortField({
Key key, Key? key,
this.ipOnly = false, this.ipOnly = false,
this.ipHelp = "ip address", this.ipHelp = "ip address",
this.autoFocus = false, this.autoFocus = false,
this.focusNode, this.focusNode,
this.nextFocusNode, this.nextFocusNode,
this.onChanged, required this.onChanged,
this.textInputAction, this.textInputAction,
this.noBorder = false, this.noBorder = false,
this.ipTextAlign, this.ipTextAlign,
@ -25,14 +25,14 @@ class IPAndPortField extends StatefulWidget {
final String ipHelp; final String ipHelp;
final bool ipOnly; final bool ipOnly;
final bool autoFocus; final bool autoFocus;
final FocusNode focusNode; final FocusNode? focusNode;
final FocusNode nextFocusNode; final FocusNode? nextFocusNode;
final ValueChanged<IPAndPort> onChanged; final ValueChanged<IPAndPort> onChanged;
final TextInputAction textInputAction; final TextInputAction? textInputAction;
final bool noBorder; final bool noBorder;
final TextAlign ipTextAlign; final TextAlign? ipTextAlign;
final TextEditingController ipController; final TextEditingController? ipController;
final TextEditingController portController; final TextEditingController? portController;
@override @override
_IPAndPortFieldState createState() => _IPAndPortFieldState(); _IPAndPortFieldState createState() => _IPAndPortFieldState();

View File

@ -8,17 +8,17 @@ import 'IPAndPortField.dart';
class IPAndPortFormField extends FormField<IPAndPort> { class IPAndPortFormField extends FormField<IPAndPort> {
//TODO: onSaved, validator, auto-validate, enabled? //TODO: onSaved, validator, auto-validate, enabled?
IPAndPortFormField({ IPAndPortFormField({
Key key, Key? key,
ipOnly = false, ipOnly = false,
enableIPV6 = false, enableIPV6 = false,
ipHelp = "ip address", ipHelp = "ip address",
autoFocus = false, autoFocus = false,
focusNode, focusNode,
nextFocusNode, nextFocusNode,
ValueChanged<IPAndPort> onChanged, ValueChanged<IPAndPort>? onChanged,
FormFieldSetter<IPAndPort> onSaved, FormFieldSetter<IPAndPort>? onSaved,
textInputAction, textInputAction,
IPAndPort initialValue, IPAndPort? initialValue,
noBorder, noBorder,
ipTextAlign = TextAlign.center, ipTextAlign = TextAlign.center,
this.ipController, 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'; 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 "Please enter a valid port";
} }
return null; return null;
}, },
builder: (FormFieldState<IPAndPort> field) { builder: (FormFieldState<IPAndPort> field) {
final _IPAndPortFormField state = field; final _IPAndPortFormField state = field as _IPAndPortFormField;
void onChangedHandler(IPAndPort value) { void onChangedHandler(IPAndPort value) {
if (onChanged != null) { if (onChanged != null) {
@ -67,42 +67,42 @@ class IPAndPortFormField extends FormField<IPAndPort> {
ipTextAlign: ipTextAlign, ipTextAlign: ipTextAlign,
), ),
field.hasError field.hasError
? Text(field.errorText, ? Text(field.errorText!,
style: TextStyle(color: CupertinoColors.systemRed.resolveFrom(field.context), fontSize: 13)) style: TextStyle(color: CupertinoColors.systemRed.resolveFrom(field.context), fontSize: 13))
: Container(height: 0) : Container(height: 0)
]); ]);
}); });
final TextEditingController ipController; final TextEditingController? ipController;
final TextEditingController portController; final TextEditingController? portController;
@override @override
_IPAndPortFormField createState() => _IPAndPortFormField(); _IPAndPortFormField createState() => _IPAndPortFormField();
} }
class _IPAndPortFormField extends FormFieldState<IPAndPort> { class _IPAndPortFormField extends FormFieldState<IPAndPort> {
TextEditingController _ipController; TextEditingController? _ipController;
TextEditingController _portController; TextEditingController? _portController;
TextEditingController get _effectiveIPController => widget.ipController ?? _ipController; TextEditingController get _effectiveIPController => widget.ipController ?? _ipController!;
TextEditingController get _effectivePortController => widget.portController ?? _portController; TextEditingController get _effectivePortController => widget.portController ?? _portController!;
@override @override
IPAndPortFormField get widget => super.widget; IPAndPortFormField get widget => super.widget as IPAndPortFormField;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
if (widget.ipController == null) { if (widget.ipController == null) {
_ipController = TextEditingController(text: widget.initialValue.ip); _ipController = TextEditingController(text: widget.initialValue?.ip ?? "");
} else { } else {
widget.ipController.addListener(_handleControllerChanged); widget.ipController!.addListener(_handleControllerChanged);
} }
if (widget.portController == null) { if (widget.portController == null) {
_portController = TextEditingController(text: widget.initialValue?.port?.toString() ?? ""); _portController = TextEditingController(text: widget.initialValue?.port?.toString() ?? "");
} else { } else {
widget.portController.addListener(_handleControllerChanged); widget.portController!.addListener(_handleControllerChanged);
} }
} }
@ -118,12 +118,12 @@ class _IPAndPortFormField extends FormFieldState<IPAndPort> {
widget.ipController?.addListener(_handleControllerChanged); widget.ipController?.addListener(_handleControllerChanged);
if (oldWidget.ipController != null && widget.ipController == null) { if (oldWidget.ipController != null && widget.ipController == null) {
_ipController = TextEditingController.fromValue(oldWidget.ipController.value); _ipController = TextEditingController.fromValue(oldWidget.ipController!.value);
} }
if (widget.ipController != null) { if (widget.ipController != null) {
shouldUpdate = true; shouldUpdate = true;
update.ip = widget.ipController.text; update.ip = widget.ipController!.text;
if (oldWidget.ipController == null) _ipController = null; if (oldWidget.ipController == null) _ipController = null;
} }
} }
@ -133,12 +133,12 @@ class _IPAndPortFormField extends FormFieldState<IPAndPort> {
widget.portController?.addListener(_handleControllerChanged); widget.portController?.addListener(_handleControllerChanged);
if (oldWidget.portController != null && widget.portController == null) { if (oldWidget.portController != null && widget.portController == null) {
_portController = TextEditingController.fromValue(oldWidget.portController.value); _portController = TextEditingController.fromValue(oldWidget.portController!.value);
} }
if (widget.portController != null) { if (widget.portController != null) {
shouldUpdate = true; shouldUpdate = true;
update.port = int.parse(widget.portController.text); update.port = int.parse(widget.portController!.text);
if (oldWidget.portController == null) _portController = null; if (oldWidget.portController == null) _portController = null;
} }
} }
@ -159,8 +159,8 @@ class _IPAndPortFormField extends FormFieldState<IPAndPort> {
void reset() { void reset() {
super.reset(); super.reset();
setState(() { setState(() {
_effectiveIPController.text = widget.initialValue.ip; _effectiveIPController.text = widget.initialValue?.ip ?? "";
_effectivePortController.text = widget.initialValue.port.toString(); _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 // example, the reset() method. In such cases, the FormField value will
// already have been set. // already have been set.
final effectivePort = int.parse(_effectivePortController.text); 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)); didChange(IPAndPort(ip: _effectiveIPController.text, port: effectivePort));
} }
} }

View File

@ -8,16 +8,16 @@ class IPField extends StatelessWidget {
final String help; final String help;
final bool ipOnly; final bool ipOnly;
final bool autoFocus; final bool autoFocus;
final FocusNode focusNode; final FocusNode? focusNode;
final FocusNode nextFocusNode; final FocusNode? nextFocusNode;
final ValueChanged<String> onChanged; final ValueChanged<String>? onChanged;
final EdgeInsetsGeometry textPadding; final EdgeInsetsGeometry textPadding;
final TextInputAction textInputAction; final TextInputAction? textInputAction;
final controller; final controller;
final textAlign; final textAlign;
const IPField( const IPField(
{Key key, {Key? key,
this.ipOnly = false, this.ipOnly = false,
this.help = "ip address", this.help = "ip address",
this.autoFocus = false, this.autoFocus = false,
@ -33,7 +33,7 @@ class IPField extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var textStyle = CupertinoTheme.of(context).textTheme.textStyle; 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( return SizedBox(
width: ipWidth, width: ipWidth,
@ -64,7 +64,7 @@ class IPTextInputFormatter extends TextInputFormatter {
(String substring) { (String substring) {
return whitelistedPattern return whitelistedPattern
.allMatches(substring) .allMatches(substring)
.map<String>((Match match) => match.group(0)) .map<String>((Match match) => match.group(0)!)
.join() .join()
.replaceAll(RegExp(r','), '.'); .replaceAll(RegExp(r','), '.');
}, },
@ -79,7 +79,7 @@ TextEditingValue _selectionAwareTextManipulation(
final int selectionStartIndex = value.selection.start; final int selectionStartIndex = value.selection.start;
final int selectionEndIndex = value.selection.end; final int selectionEndIndex = value.selection.end;
String manipulatedText; String manipulatedText;
TextSelection manipulatedSelection; TextSelection? manipulatedSelection;
if (selectionStartIndex < 0 || selectionEndIndex < 0) { if (selectionStartIndex < 0 || selectionEndIndex < 0) {
manipulatedText = substringManipulation(value.text); manipulatedText = substringManipulation(value.text);
} else { } else {

View File

@ -9,15 +9,15 @@ import 'IPField.dart';
class IPFormField extends FormField<String> { class IPFormField extends FormField<String> {
//TODO: validator, auto-validate, enabled? //TODO: validator, auto-validate, enabled?
IPFormField({ IPFormField({
Key key, Key? key,
ipOnly = false, ipOnly = false,
enableIPV6 = false, enableIPV6 = false,
help = "ip address", help = "ip address",
autoFocus = false, autoFocus = false,
focusNode, focusNode,
nextFocusNode, nextFocusNode,
ValueChanged<String> onChanged, ValueChanged<String>? onChanged,
FormFieldSetter<String> onSaved, FormFieldSetter<String>? onSaved,
textPadding = const EdgeInsets.all(6.0), textPadding = const EdgeInsets.all(6.0),
textInputAction, textInputAction,
initialValue, initialValue,
@ -41,7 +41,7 @@ class IPFormField extends FormField<String> {
return null; return null;
}, },
builder: (FormFieldState<String> field) { builder: (FormFieldState<String> field) {
final _IPFormField state = field; final _IPFormField state = field as _IPFormField;
void onChangedHandler(String value) { void onChangedHandler(String value) {
if (onChanged != null) { if (onChanged != null) {
@ -64,7 +64,7 @@ class IPFormField extends FormField<String> {
textAlign: textAlign), textAlign: textAlign),
field.hasError field.hasError
? Text( ? Text(
field.errorText, field.errorText!,
style: TextStyle(color: CupertinoColors.systemRed.resolveFrom(field.context), fontSize: 13), style: TextStyle(color: CupertinoColors.systemRed.resolveFrom(field.context), fontSize: 13),
textAlign: textAlign, textAlign: textAlign,
) )
@ -72,19 +72,19 @@ class IPFormField extends FormField<String> {
]); ]);
}); });
final TextEditingController controller; final TextEditingController? controller;
@override @override
_IPFormField createState() => _IPFormField(); _IPFormField createState() => _IPFormField();
} }
class _IPFormField extends FormFieldState<String> { class _IPFormField extends FormFieldState<String> {
TextEditingController _controller; TextEditingController? _controller;
TextEditingController get _effectiveController => widget.controller ?? _controller; TextEditingController get _effectiveController => widget.controller ?? _controller!;
@override @override
IPFormField get widget => super.widget; IPFormField get widget => super.widget as IPFormField;
@override @override
void initState() { void initState() {
@ -92,7 +92,7 @@ class _IPFormField extends FormFieldState<String> {
if (widget.controller == null) { if (widget.controller == null) {
_controller = TextEditingController(text: widget.initialValue); _controller = TextEditingController(text: widget.initialValue);
} else { } else {
widget.controller.addListener(_handleControllerChanged); widget.controller!.addListener(_handleControllerChanged);
} }
} }
@ -104,9 +104,9 @@ class _IPFormField extends FormFieldState<String> {
widget.controller?.addListener(_handleControllerChanged); widget.controller?.addListener(_handleControllerChanged);
if (oldWidget.controller != null && widget.controller == null) if (oldWidget.controller != null && widget.controller == null)
_controller = TextEditingController.fromValue(oldWidget.controller.value); _controller = TextEditingController.fromValue(oldWidget.controller!.value);
if (widget.controller != null) { if (widget.controller != null) {
setValue(widget.controller.text); setValue(widget.controller!.text);
if (oldWidget.controller == null) _controller = null; if (oldWidget.controller == null) _controller = null;
} }
} }
@ -122,7 +122,7 @@ class _IPFormField extends FormFieldState<String> {
void reset() { void reset() {
super.reset(); super.reset();
setState(() { 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> { class PlatformTextFormField extends FormField<String> {
//TODO: auto-validate, enabled? //TODO: auto-validate, enabled?
PlatformTextFormField( PlatformTextFormField(
{Key key, {Key? key,
widgetKey, widgetKey,
this.controller, this.controller,
focusNode, focusNode,
nextFocusNode, nextFocusNode,
TextInputType keyboardType, TextInputType? keyboardType,
textInputAction, textInputAction,
List<TextInputFormatter> inputFormatters, List<TextInputFormatter>? inputFormatters,
textAlign, textAlign,
autofocus, autofocus,
maxLines = 1, maxLines = 1,
@ -25,10 +25,10 @@ class PlatformTextFormField extends FormField<String> {
expands, expands,
suffix, suffix,
textAlignVertical, textAlignVertical,
String initialValue, String? initialValue,
String placeholder, String? placeholder,
FormFieldValidator<String> validator, FormFieldValidator<String>? validator,
ValueChanged<String> onSaved}) ValueChanged<String?>? onSaved})
: super( : super(
key: key, key: key,
initialValue: controller != null ? controller.text : (initialValue ?? ''), initialValue: controller != null ? controller.text : (initialValue ?? ''),
@ -41,7 +41,7 @@ class PlatformTextFormField extends FormField<String> {
return null; return null;
}, },
builder: (FormFieldState<String> field) { builder: (FormFieldState<String> field) {
final _PlatformTextFormFieldState state = field; final _PlatformTextFormFieldState state = field as _PlatformTextFormFieldState;
void onChangedHandler(String value) { void onChangedHandler(String value) {
if (onChanged != null) { if (onChanged != null) {
@ -73,7 +73,7 @@ class PlatformTextFormField extends FormField<String> {
suffix: suffix), suffix: suffix),
field.hasError field.hasError
? Text( ? Text(
field.errorText, field.errorText!,
style: TextStyle(color: CupertinoColors.systemRed.resolveFrom(field.context), fontSize: 13), style: TextStyle(color: CupertinoColors.systemRed.resolveFrom(field.context), fontSize: 13),
textAlign: textAlign, textAlign: textAlign,
) )
@ -81,19 +81,19 @@ class PlatformTextFormField extends FormField<String> {
]); ]);
}); });
final TextEditingController controller; final TextEditingController? controller;
@override @override
_PlatformTextFormFieldState createState() => _PlatformTextFormFieldState(); _PlatformTextFormFieldState createState() => _PlatformTextFormFieldState();
} }
class _PlatformTextFormFieldState extends FormFieldState<String> { class _PlatformTextFormFieldState extends FormFieldState<String> {
TextEditingController _controller; TextEditingController? _controller;
TextEditingController get _effectiveController => widget.controller ?? _controller; TextEditingController get _effectiveController => widget.controller ?? _controller!;
@override @override
PlatformTextFormField get widget => super.widget; PlatformTextFormField get widget => super.widget as PlatformTextFormField;
@override @override
void initState() { void initState() {
@ -101,7 +101,7 @@ class _PlatformTextFormFieldState extends FormFieldState<String> {
if (widget.controller == null) { if (widget.controller == null) {
_controller = TextEditingController(text: widget.initialValue); _controller = TextEditingController(text: widget.initialValue);
} else { } else {
widget.controller.addListener(_handleControllerChanged); widget.controller!.addListener(_handleControllerChanged);
} }
} }
@ -113,9 +113,9 @@ class _PlatformTextFormFieldState extends FormFieldState<String> {
widget.controller?.addListener(_handleControllerChanged); widget.controller?.addListener(_handleControllerChanged);
if (oldWidget.controller != null && widget.controller == null) if (oldWidget.controller != null && widget.controller == null)
_controller = TextEditingController.fromValue(oldWidget.controller.value); _controller = TextEditingController.fromValue(oldWidget.controller!.value);
if (widget.controller != null) { if (widget.controller != null) {
setValue(widget.controller.text); setValue(widget.controller!.text);
if (oldWidget.controller == null) _controller = null; if (oldWidget.controller == null) _controller = null;
} }
} }
@ -131,7 +131,7 @@ class _PlatformTextFormFieldState extends FormFieldState<String> {
void reset() { void reset() {
super.reset(); super.reset();
setState(() { setState(() {
_effectiveController.text = widget.initialValue; _effectiveController.text = widget.initialValue ?? "";
}); });
} }

View File

@ -13,9 +13,9 @@ enum SimpleScrollable {
class SimplePage extends StatelessWidget { class SimplePage extends StatelessWidget {
const SimplePage( const SimplePage(
{Key key, {Key? key,
this.title, required this.title,
@required this.child, required this.child,
this.leadingAction, this.leadingAction,
this.trailingActions = const [], this.trailingActions = const [],
this.scrollable = SimpleScrollable.vertical, this.scrollable = SimpleScrollable.vertical,
@ -30,20 +30,20 @@ class SimplePage extends StatelessWidget {
final String title; final String title;
final Widget child; final Widget child;
final SimpleScrollable scrollable; 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 /// 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 /// This is set to true if you have any scrollable other than none
final bool scrollbar; 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 /// 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 List<Widget> trailingActions;
final VoidCallback onRefresh; final VoidCallback? onRefresh;
final VoidCallback onLoading; final VoidCallback? onLoading;
final RefreshController refreshController; final RefreshController? refreshController;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -72,7 +72,7 @@ class SimplePage extends StatelessWidget {
scrollController: scrollController, scrollController: scrollController,
onRefresh: onRefresh, onRefresh: onRefresh,
onLoading: onLoading, onLoading: onLoading,
controller: refreshController, controller: refreshController!,
child: realChild, child: realChild,
enablePullUp: onLoading != null, enablePullUp: onLoading != null,
enablePullDown: onRefresh != null, enablePullDown: onRefresh != null,
@ -88,7 +88,7 @@ class SimplePage extends StatelessWidget {
if (bottomBar != null) { if (bottomBar != null) {
realChild = Column(children: [ realChild = Column(children: [
Expanded(child: realChild), 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'; import 'package:mobile_nebula/services/utils.dart';
class SiteItem extends StatelessWidget { 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 Site site;
final onPressed; final onPressed;
@ -27,8 +27,8 @@ class SiteItem extends StatelessWidget {
Widget _buildContent(BuildContext context) { Widget _buildContent(BuildContext context) {
final border = BorderSide(color: Utils.configSectionBorder(context)); final border = BorderSide(color: Utils.configSectionBorder(context));
var ip = "Error"; var ip = "Error";
if (site.certInfo != null && site.certInfo.cert.details.ips.length > 0) { if (site.certInfo != null && site.certInfo!.cert.details.ips.length > 0) {
ip = site.certInfo.cert.details.ips[0]; ip = site.certInfo!.cert.details.ips[0];
} }
return SpecialButton( 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 // 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 { 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); : super(key: key);
final Widget child; final Widget? child;
final Color color; final Color? color;
final bool useButtonTheme; final bool useButtonTheme;
final BoxDecoration decoration; final BoxDecoration? decoration;
final Function onPressed; final GestureTapCallback? onPressed;
@override @override
_SpecialButtonState createState() => _SpecialButtonState(); _SpecialButtonState createState() => _SpecialButtonState();
@ -59,7 +59,7 @@ class _SpecialButtonState extends State<SpecialButton> with SingleTickerProvider
child: Semantics( child: Semantics(
button: true, button: true,
child: FadeTransition( child: FadeTransition(
opacity: _opacityAnimation, opacity: _opacityAnimation!,
child: DefaultTextStyle(style: textStyle, child: Container(child: widget.child, color: widget.color)), 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); static const Duration kFadeInDuration = Duration(milliseconds: 100);
final Tween<double> _opacityTween = Tween<double>(begin: 1.0); final Tween<double> _opacityTween = Tween<double>(begin: 1.0);
AnimationController _animationController; AnimationController? _animationController;
Animation<double> _opacityAnimation; Animation<double>? _opacityAnimation;
@override @override
void initState() { void initState() {
@ -82,7 +82,7 @@ class _SpecialButtonState extends State<SpecialButton> with SingleTickerProvider
value: 0.0, value: 0.0,
vsync: this, vsync: this,
); );
_opacityAnimation = _animationController.drive(CurveTween(curve: Curves.decelerate)).drive(_opacityTween); _opacityAnimation = _animationController!.drive(CurveTween(curve: Curves.decelerate)).drive(_opacityTween);
_setTween(); _setTween();
} }
@ -98,8 +98,7 @@ class _SpecialButtonState extends State<SpecialButton> with SingleTickerProvider
@override @override
void dispose() { void dispose() {
_animationController.dispose(); _animationController?.dispose();
_animationController = null;
super.dispose(); super.dispose();
} }
@ -127,14 +126,14 @@ class _SpecialButtonState extends State<SpecialButton> with SingleTickerProvider
} }
void _animate() { void _animate() {
if (_animationController.isAnimating) { if (_animationController == null || _animationController!.isAnimating) {
return; return;
} }
final bool wasHeldDown = _buttonHeldDown; final bool wasHeldDown = _buttonHeldDown;
final TickerFuture ticker = _buttonHeldDown final TickerFuture ticker = _buttonHeldDown
? _animationController.animateTo(1.0, duration: kFadeOutDuration) ? _animationController!.animateTo(1.0, duration: kFadeOutDuration)
: _animationController.animateTo(0.0, duration: kFadeInDuration); : _animationController!.animateTo(0.0, duration: kFadeInDuration);
ticker.then<void>((void value) { ticker.then<void>((void value) {
if (mounted && wasHeldDown != _buttonHeldDown) { 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 /// A normal TextField or CupertinoTextField that looks the same on all platforms
class SpecialTextField extends StatefulWidget { class SpecialTextField extends StatefulWidget {
const SpecialTextField( const SpecialTextField(
{Key key, {Key? key,
this.placeholder, this.placeholder,
this.suffix, this.suffix,
this.controller, this.controller,
@ -30,43 +30,44 @@ class SpecialTextField extends StatefulWidget {
this.inputFormatters}) this.inputFormatters})
: super(key: key); : super(key: key);
final String placeholder; final String? placeholder;
final TextEditingController controller; final TextEditingController? controller;
final FocusNode focusNode; final FocusNode? focusNode;
final FocusNode nextFocusNode; final FocusNode? nextFocusNode;
final bool autocorrect; final bool? autocorrect;
final int minLines; final int? minLines;
final int maxLines; final int? maxLines;
final int maxLength; final int? maxLength;
final MaxLengthEnforcement maxLengthEnforcement; final MaxLengthEnforcement? maxLengthEnforcement;
final Widget suffix; final Widget? suffix;
final TextStyle style; final TextStyle? style;
final TextInputType keyboardType; final TextInputType? keyboardType;
final Brightness keyboardAppearance; final Brightness? keyboardAppearance;
final TextInputAction textInputAction; final TextInputAction? textInputAction;
final TextCapitalization textCapitalization; final TextCapitalization? textCapitalization;
final TextAlign textAlign; final TextAlign? textAlign;
final TextAlignVertical textAlignVertical; final TextAlignVertical? textAlignVertical;
final bool autofocus; final bool? autofocus;
final ValueChanged<String> onChanged; final ValueChanged<String>? onChanged;
final bool enabled; final bool? enabled;
final List<TextInputFormatter> inputFormatters; final List<TextInputFormatter>? inputFormatters;
final bool expands; final bool? expands;
@override @override
_SpecialTextFieldState createState() => _SpecialTextFieldState(); _SpecialTextFieldState createState() => _SpecialTextFieldState();
} }
class _SpecialTextFieldState extends State<SpecialTextField> { class _SpecialTextFieldState extends State<SpecialTextField> {
List<TextInputFormatter> formatters; List<TextInputFormatter> formatters = [];
@override @override
void initState() { void initState() {
formatters = widget.inputFormatters; if (widget.inputFormatters == null || formatters.length == 0) {
if (formatters == null || formatters.length == 0) {
formatters = [FilteringTextInputFormatter.allow(RegExp(r'[^\t]'))]; formatters = [FilteringTextInputFormatter.allow(RegExp(r'[^\t]'))];
} else {
formatters = widget.inputFormatters!;
} }
super.initState(); 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 // A config item that detects tapping and calls back on a tap
class ConfigButtonItem extends StatelessWidget { 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; final onPressed;
@override @override

View File

@ -3,14 +3,15 @@ import 'package:mobile_nebula/components/SpecialButton.dart';
import 'package:mobile_nebula/services/utils.dart'; import 'package:mobile_nebula/services/utils.dart';
class ConfigCheckboxItem extends StatelessWidget { 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); : super(key: key);
final Widget label; final Widget? label;
final Widget content; final Widget? content;
final double labelWidth; final double labelWidth;
final bool checked; final bool checked;
final Function onChanged; final Function? onChanged;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -34,7 +35,7 @@ class ConfigCheckboxItem extends StatelessWidget {
child: item, child: item,
onPressed: () { onPressed: () {
if (onChanged != null) { if (onChanged != null) {
onChanged(); onChanged!();
} }
}, },
); );

View File

@ -4,15 +4,15 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
TextStyle basicTextStyle(BuildContext context) => 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; const double _headerFontSize = 13.0;
class ConfigHeader extends StatelessWidget { 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 String label;
final Color color; final Color? color;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {

View File

@ -4,10 +4,14 @@ import 'package:mobile_nebula/services/utils.dart';
class ConfigItem extends StatelessWidget { class ConfigItem extends StatelessWidget {
const ConfigItem( 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); : super(key: key);
final Widget label; final Widget? label;
final Widget content; final Widget content;
final double labelWidth; final double labelWidth;
final CrossAxisAlignment crossAxisAlignment; final CrossAxisAlignment crossAxisAlignment;

View File

@ -7,7 +7,7 @@ import 'package:mobile_nebula/services/utils.dart';
class ConfigPageItem extends StatelessWidget { class ConfigPageItem extends StatelessWidget {
const ConfigPageItem( const ConfigPageItem(
{Key key, {Key? key,
this.label, this.label,
this.content, this.content,
this.labelWidth = 100, this.labelWidth = 100,
@ -15,8 +15,8 @@ class ConfigPageItem extends StatelessWidget {
this.crossAxisAlignment = CrossAxisAlignment.center}) this.crossAxisAlignment = CrossAxisAlignment.center})
: super(key: key); : super(key: key);
final Widget label; final Widget? label;
final Widget content; final Widget? content;
final double labelWidth; final double labelWidth;
final CrossAxisAlignment crossAxisAlignment; final CrossAxisAlignment crossAxisAlignment;
final onPressed; final onPressed;
@ -28,8 +28,8 @@ class ConfigPageItem extends StatelessWidget {
if (Platform.isAndroid) { if (Platform.isAndroid) {
final origTheme = Theme.of(context); final origTheme = Theme.of(context);
theme = origTheme.copyWith( theme = origTheme.copyWith(
textTheme: textTheme: origTheme.textTheme
origTheme.textTheme.copyWith(button: origTheme.textTheme.button.copyWith(fontWeight: FontWeight.normal))); .copyWith(button: origTheme.textTheme.button!.copyWith(fontWeight: FontWeight.normal)));
return Theme(data: theme, child: _buildContent(context)); return Theme(data: theme, child: _buildContent(context));
} else { } else {
final origTheme = CupertinoTheme.of(context); final origTheme = CupertinoTheme.of(context);

View File

@ -4,12 +4,13 @@ import 'package:mobile_nebula/services/utils.dart';
import 'ConfigHeader.dart'; import 'ConfigHeader.dart';
class ConfigSection extends StatelessWidget { 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 List<Widget> children;
final String label; final String? label;
final Color borderColor; final Color? borderColor;
final Color labelColor; final Color? labelColor;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -32,7 +33,7 @@ class ConfigSection extends StatelessWidget {
} }
return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ 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( Container(
decoration: decoration:
BoxDecoration(border: Border(top: border, bottom: border), color: Utils.configItemBackground(context)), 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'; import 'package:mobile_nebula/components/SpecialTextField.dart';
class ConfigTextItem extends StatelessWidget { 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 String? placeholder;
final TextEditingController controller; final TextEditingController? controller;
final TextStyle style; final TextStyle style;
@override @override

View File

@ -1,5 +1,5 @@
class CIDR { class CIDR {
CIDR({this.ip, this.bits}); CIDR({this.ip = '', this.bits = 0});
String ip; String ip;
int bits; int bits;
@ -13,13 +13,15 @@ class CIDR {
return toString(); return toString();
} }
CIDR.fromString(String val) { factory CIDR.fromString(String val) {
final parts = val.split('/'); final parts = val.split('/');
if (parts.length != 2) { if (parts.length != 2) {
throw 'Invalid CIDR string'; throw 'Invalid CIDR string';
} }
ip = parts[0]; return CIDR(
bits = int.parse(parts[1]); ip: parts[0],
bits: int.parse(parts[1]),
);
} }
} }

View File

@ -1,7 +1,7 @@
class CertificateInfo { class CertificateInfo {
Certificate cert; Certificate cert;
String rawCert; String? rawCert;
CertificateValidity validity; CertificateValidity? validity;
CertificateInfo.debug({this.rawCert = ""}) CertificateInfo.debug({this.rawCert = ""})
: this.cert = Certificate.debug(), : this.cert = Certificate.debug(),
@ -12,10 +12,10 @@ class CertificateInfo {
rawCert = json['RawCert'], rawCert = json['RawCert'],
validity = CertificateValidity.fromJson(json['Validity']); 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) { 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) CertificateDetails.fromJson(Map<String, dynamic> json)
: name = json['name'], : name = json['name'],
notBefore = DateTime.tryParse(json['notBefore']), notBefore = DateTime.parse(json['notBefore']),
notAfter = DateTime.tryParse(json['notAfter']), notAfter = DateTime.parse(json['notAfter']),
publicKey = json['publicKey'], publicKey = json['publicKey'],
groups = List<String>.from(json['groups']), groups = List<String>.from(json['groups']),
ips = List<String>.from(json['ips']), ips = List<String>.from(json['ips']),

View File

@ -6,31 +6,48 @@ class HostInfo {
int remoteIndex; int remoteIndex;
List<UDPAddress> remoteAddresses; List<UDPAddress> remoteAddresses;
int cachedPackets; int cachedPackets;
Certificate cert; Certificate? cert;
UDPAddress currentRemote; UDPAddress? currentRemote;
int messageCounter; int messageCounter;
HostInfo.fromJson(Map<String, dynamic> json) { HostInfo({
vpnIp = json['vpnIp']; required this.vpnIp,
localIndex = json['localIndex']; required this.localIndex,
remoteIndex = json['remoteIndex']; required this.remoteIndex,
cachedPackets = json['cachedPackets']; 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) { if (json['currentRemote'] != null) {
currentRemote = UDPAddress.fromJson(json['currentRemote']); currentRemote = UDPAddress.fromJson(json['currentRemote']);
} }
Certificate? cert;
if (json['cert'] != null) { if (json['cert'] != null) {
cert = Certificate.fromJson(json['cert']); cert = Certificate.fromJson(json['cert']);
} }
List<dynamic> addrs = json['remoteAddrs']; List<dynamic> addrs = json['remoteAddrs'];
remoteAddresses = []; List<UDPAddress> remoteAddresses = [];
addrs?.forEach((val) { addrs.forEach((val) {
remoteAddresses.add(UDPAddress.fromJson(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; List<IPAndPort> destinations;
bool lighthouse; 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 { class IPAndPort {
String ip; String? ip;
int port; int? port;
IPAndPort({this.ip, this.port}); IPAndPort({this.ip, this.port});
@override @override
String toString() { String toString() {
if (ip.contains(':')) { if (ip != null && ip!.contains(':')) {
return '[$ip]:$port'; return '[$ip]:$port';
} }
@ -17,10 +17,13 @@ class IPAndPort {
return toString(); 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 //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"); 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 { class Site {
static const platform = MethodChannel('net.defined.mobileNebula/NebulaVpnService'); 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 /// Signals that something about this site has changed. onError is called with an error string if there was an error
StreamController _change = StreamController.broadcast(); StreamController _change = StreamController.broadcast();
// Identifiers // Identifiers
String name; late String name;
String id; late String id;
// static_host_map // static_host_map
Map<String, StaticHost> staticHostmap; late Map<String, StaticHost> staticHostmap;
List<UnsafeRoute> unsafeRoutes; late List<UnsafeRoute> unsafeRoutes;
// pki fields // pki fields
List<CertificateInfo> ca; late List<CertificateInfo> ca;
CertificateInfo certInfo; String? key;
String key; late CertificateInfo? certInfo;
// lighthouse options // lighthouse options
int lhDuration; // in seconds late int lhDuration; // in seconds
// listen settings // listen settings
int port; late int port;
int mtu; late int mtu;
String cipher; late String cipher;
int sortKey; late int sortKey;
bool connected; late bool connected;
String status; late String status;
String logFile; late String logFile;
String logVerbosity; late String logVerbosity;
// A list of errors encountered while loading the site // A list of errors encountered while loading the site
List<String> errors; late List<String> errors;
Site( Site({
{this.name, String name = '',
id, String? id,
staticHostmap, Map<String, StaticHost>? staticHostmap,
ca, List<CertificateInfo>? ca,
this.certInfo, CertificateInfo? certInfo,
this.lhDuration = 0, int lhDuration = 0,
this.port = 0, int port = 0,
this.cipher = "aes", String cipher = "aes",
this.sortKey, int sortKey = 0,
this.mtu = 1300, int mtu = 1300,
this.connected, bool connected = false,
this.status, String status = '',
this.logFile, String logFile = '',
this.logVerbosity = 'info', String logVerbosity = 'info',
errors, List<String>? errors,
unsafeRoutes}) List<UnsafeRoute>? unsafeRoutes,
: staticHostmap = staticHostmap ?? {}, }) {
unsafeRoutes = unsafeRoutes ?? [], this.name = name;
errors = errors ?? [], this.id = id ?? uuid.v4();
ca = ca ?? [], this.staticHostmap = staticHostmap ?? {};
id = id ?? uuid.v4(); this.ca = ca ?? [];
this.certInfo = certInfo;
Site.fromJson(Map<String, dynamic> json) { this.lhDuration = lhDuration;
name = json['name']; this.port = port;
id = json['id']; this.cipher = cipher;
this.sortKey = sortKey;
Map<String, dynamic> rawHostmap = json['staticHostmap']; this.mtu = mtu;
staticHostmap = {}; this.connected = connected;
rawHostmap.forEach((key, val) { this.status = status;
staticHostmap[key] = StaticHost.fromJson(val); this.logFile = logFile;
}); this.logVerbosity = logVerbosity;
this.errors = errors ?? [];
List<dynamic> rawUnsafeRoutes = json['unsafeRoutes']; this.unsafeRoutes = 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);
});
_updates = EventChannel('net.defined.nebula/$id'); _updates = EventChannel('net.defined.nebula/$id');
_updates.receiveBroadcastStream().listen((d) { _updates.receiveBroadcastStream().listen((d) {
@ -128,10 +96,60 @@ class Site {
var error = err as PlatformException; var error = err as PlatformException;
this.status = error.details['status']; this.status = error.details['status'];
this.connected = error.details['connected']; 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() { Stream onChange() {
return _change.stream; return _change.stream;
} }
@ -142,10 +160,9 @@ class Site {
'id': id, 'id': id,
'staticHostmap': staticHostmap, 'staticHostmap': staticHostmap,
'unsafeRoutes': unsafeRoutes, 'unsafeRoutes': unsafeRoutes,
'ca': ca?.map((cert) { 'ca': ca.map((cert) {
return cert.rawCert; return cert.rawCert;
})?.join('\n') ?? }).join('\n'),
"",
'cert': certInfo?.rawCert, 'cert': certInfo?.rawCert,
'key': key, 'key': key,
'lhDuration': lhDuration, 'lhDuration': lhDuration,
@ -260,7 +277,7 @@ class Site {
_change.close(); _change.close();
} }
Future<HostInfo> getHostInfo(String vpnIp, bool pending) async { Future<HostInfo?> getHostInfo(String vpnIp, bool pending) async {
try { try {
var ret = await platform var ret = await platform
.invokeMethod("active.getHostInfo", <String, dynamic>{"id": id, "vpnIp": vpnIp, "pending": pending}); .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 { try {
var ret = await platform var ret = await platform
.invokeMethod("active.setRemoteForTunnel", <String, dynamic>{"id": id, "vpnIp": vpnIp, "addr": addr}); .invokeMethod("active.setRemoteForTunnel", <String, dynamic>{"id": id, "vpnIp": vpnIp, "addr": addr});

View File

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

View File

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

View File

@ -9,7 +9,7 @@ import 'package:mobile_nebula/services/utils.dart';
import 'package:package_info/package_info.dart'; import 'package:package_info/package_info.dart';
class AboutScreen extends StatefulWidget { class AboutScreen extends StatefulWidget {
const AboutScreen({Key key}) : super(key: key); const AboutScreen({Key? key}) : super(key: key);
@override @override
_AboutScreenState createState() => _AboutScreenState(); _AboutScreenState createState() => _AboutScreenState();
@ -17,7 +17,7 @@ class AboutScreen extends StatefulWidget {
class _AboutScreenState extends State<AboutScreen> { class _AboutScreenState extends State<AboutScreen> {
bool ready = false; bool ready = false;
PackageInfo packageInfo; PackageInfo? packageInfo;
@override @override
void initState() { void initState() {
@ -33,6 +33,7 @@ class _AboutScreenState extends State<AboutScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// packageInfo is null until ready is true
if (!ready) { if (!ready) {
return Center( return Center(
child: PlatformCircularProgressIndicator(cupertino: (_, __) { child: PlatformCircularProgressIndicator(cupertino: (_, __) {
@ -48,13 +49,17 @@ class _AboutScreenState extends State<AboutScreen> {
ConfigItem( ConfigItem(
label: Text('App version'), label: Text('App version'),
labelWidth: 150, labelWidth: 150,
content: _buildText('${packageInfo.version}-${packageInfo.buildNumber} (sha: $gitSha)')), content: _buildText('${packageInfo!.version}-${packageInfo!.buildNumber} (sha: $gitSha)')),
ConfigItem( ConfigItem(
label: Text('Nebula version'), labelWidth: 150, content: _buildText('$nebulaVersion ($goVersion)')), label: Text('Nebula version'), labelWidth: 150, content: _buildText('$nebulaVersion ($goVersion)')),
ConfigItem( ConfigItem(
label: Text('Flutter version'), labelWidth: 150, content: _buildText(flutterVersion['frameworkVersion'])), label: Text('Flutter version'),
labelWidth: 150,
content: _buildText(flutterVersion['frameworkVersion'] ?? 'Unknown')),
ConfigItem( 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>[ ConfigSection(children: <Widget>[
//TODO: wire up these other pages //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'; import 'package:pull_to_refresh/pull_to_refresh.dart';
class HostInfoScreen extends StatefulWidget { class HostInfoScreen extends StatefulWidget {
const HostInfoScreen({Key key, this.hostInfo, this.isLighthouse, this.pending, this.onChanged, this.site}) const HostInfoScreen({
: super(key: key); Key? key,
required this.hostInfo,
required this.isLighthouse,
required this.pending,
this.onChanged,
required this.site,
}) : super(key: key);
final bool isLighthouse; final bool isLighthouse;
final bool pending; final bool pending;
final HostInfo hostInfo; final HostInfo hostInfo;
final Function onChanged; final Function? onChanged;
final Site site; final Site site;
@override @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) //TODO: have a config option to refresh hostmaps on a cadence (applies to 3 screens so far)
class _HostInfoScreenState extends State<HostInfoScreen> { class _HostInfoScreenState extends State<HostInfoScreen> {
HostInfo hostInfo; late HostInfo hostInfo;
RefreshController refreshController = RefreshController(initialRefresh: false); RefreshController refreshController = RefreshController(initialRefresh: false);
@override @override
@ -64,9 +70,9 @@ class _HostInfoScreenState extends State<HostInfoScreen> {
? ConfigPageItem( ? ConfigPageItem(
label: Text('Certificate'), label: Text('Certificate'),
labelWidth: 150, labelWidth: 150,
content: Text(hostInfo.cert.details.name), content: Text(hostInfo.cert!.details.name),
onPressed: () => Utils.openPage( onPressed: () => Utils.openPage(
context, (context) => CertificateDetailsScreen(certInfo: CertificateInfo(cert: hostInfo.cert)))) context, (context) => CertificateDetailsScreen(certInfo: CertificateInfo(cert: hostInfo.cert!))))
: Container(), : Container(),
]); ]);
} }
@ -116,7 +122,7 @@ class _HostInfoScreenState extends State<HostInfoScreen> {
_setHostInfo(h); _setHostInfo(h);
} }
} catch (err) { } 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 { try {
await widget.site.closeTunnel(hostInfo.vpnIp); await widget.site.closeTunnel(hostInfo.vpnIp);
if (widget.onChanged != null) { if (widget.onChanged != null) {
widget.onChanged(); widget.onChanged!();
} }
Navigator.pop(context); Navigator.pop(context);
} catch (err) { } 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')))); }, deleteLabel: 'Close'))));
} }
@ -174,7 +180,7 @@ class _HostInfoScreenState extends State<HostInfoScreen> {
_setHostInfo(h); _setHostInfo(h);
} catch (err) { } 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 //TODO: add refresh
class MainScreen extends StatefulWidget { class MainScreen extends StatefulWidget {
const MainScreen({Key key}) : super(key: key); const MainScreen({Key? key}) : super(key: key);
@override @override
_MainScreenState createState() => _MainScreenState(); _MainScreenState createState() => _MainScreenState();
@ -31,9 +31,9 @@ class MainScreen extends StatefulWidget {
class _MainScreenState extends State<MainScreen> { class _MainScreenState extends State<MainScreen> {
bool ready = false; 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 // 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'); static const platform = MethodChannel('net.defined.mobileNebula/NebulaVpnService');
@ -72,11 +72,14 @@ class _MainScreenState extends State<MainScreen> {
Widget _buildBody() { Widget _buildBody() {
if (error != null) { if (error != null) {
return Center(child: Padding(child: Column( return Center(
mainAxisAlignment: MainAxisAlignment.center, child: Padding(
crossAxisAlignment: CrossAxisAlignment.center, child: Column(
children: error, mainAxisAlignment: MainAxisAlignment.center,
), padding: EdgeInsets.symmetric(vertical: 0, horizontal: 10))); crossAxisAlignment: CrossAxisAlignment.center,
children: error!,
),
padding: EdgeInsets.symmetric(vertical: 0, horizontal: 10)));
} }
if (!ready) { if (!ready) {
@ -87,10 +90,6 @@ class _MainScreenState extends State<MainScreen> {
); );
} }
if (sites == null || sites.length == 0) {
return _buildNoSites();
}
return _buildSites(); return _buildSites();
} }
@ -112,8 +111,12 @@ class _MainScreenState extends State<MainScreen> {
} }
Widget _buildSites() { Widget _buildSites() {
if (sites == null || sites!.length == 0) {
return _buildNoSites();
}
List<Widget> items = []; List<Widget> items = [];
sites.forEach((site) { sites!.forEach((site) {
items.add(SiteItem( items.add(SiteItem(
key: Key(site.id), key: Key(site.id),
site: site, site: site,
@ -134,17 +137,17 @@ class _MainScreenState extends State<MainScreen> {
} }
setState(() { setState(() {
final Site moved = sites.removeAt(oldI); final Site moved = sites!.removeAt(oldI);
sites.insert(newI, moved); sites!.insert(newI, moved);
}); });
for (var i = min(oldI, newI); i <= max(oldI, newI); i++) { for (var i = min(oldI, newI); i <= max(oldI, newI); i++) {
sites[i].sortKey = i; sites![i].sortKey = i;
try { try {
await sites[i].save(); await sites![i].save();
} catch (err) { } catch (err) {
//TODO: display error at the end //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) { if (Platform.isAndroid) {
try { try {
await platform.invokeMethod("android.requestPermissions"); await platform.invokeMethod("android.requestPermissions");
} on PlatformException catch (err) { } on PlatformException catch (err) {
if (err.code == "PERMISSIONS") { if (err.code == "PERMISSIONS") {
setState(() { setState(() {
error = [ error = [
Text("Permissions Required", Text("Permissions Required", style: TextStyle(fontWeight: FontWeight.bold)),
style: TextStyle(fontWeight: FontWeight.bold)),
Text( Text(
"VPN permissions are required for nebula to run, click the button below request and accept the appropriate permissions.", "VPN permissions are required for nebula to run, click the button below request and accept the appropriate permissions.",
textAlign: TextAlign.center textAlign: TextAlign.center),
),
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () {
error = null; error = null;
_loadSites(); _loadSites();
}, },
child: Text("Request Permissions") child: Text("Request Permissions")),
),
]; ];
}); });
} else { } else {
setState(() { setState(() {
error = [ error = [
Text("Unknown Error", style: TextStyle(fontWeight: FontWeight.bold)), 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(() { setState(() {
error = [ error = [
Text("Unknown Error", style: TextStyle(fontWeight: FontWeight.bold)), 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(() {}); setState(() {});
}, onError: (err) { }, onError: (err) {
setState(() {}); setState(() {});
if (ModalRoute.of(context).isCurrent) { if (ModalRoute.of(context)!.isCurrent) {
Utils.popError(context, "${site.name} Error", err); Utils.popError(context, "${site.name} Error", err);
} }
}); });
sites.add(site); sites!.add(site);
} catch (err) { } catch (err) {
//TODO: handle error //TODO: handle error
print("$err site config: $rawSite"); 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."); "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; 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) //TODO: ios is now the problem with connecting screwing our ability to query the hostmap (its a race)
class SiteDetailScreen extends StatefulWidget { 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 Site site;
final Function onChanged; final Function? onChanged;
@override @override
_SiteDetailScreenState createState() => _SiteDetailScreenState(); _SiteDetailScreenState createState() => _SiteDetailScreenState();
} }
class _SiteDetailScreenState extends State<SiteDetailScreen> { class _SiteDetailScreenState extends State<SiteDetailScreen> {
Site site; late Site site;
StreamSubscription onChange; late StreamSubscription onChange;
static const platform = MethodChannel('net.defined.mobileNebula/NebulaVpnService'); static const platform = MethodChannel('net.defined.mobileNebula/NebulaVpnService');
bool changed = false; bool changed = false;
List<HostInfo> activeHosts; List<HostInfo>? activeHosts;
List<HostInfo> pendingHosts; List<HostInfo>? pendingHosts;
RefreshController refreshController = RefreshController(initialRefresh: false); RefreshController refreshController = RefreshController(initialRefresh: false);
bool lastState; late bool lastState;
@override @override
void initState() { void initState() {
@ -80,7 +80,7 @@ class _SiteDetailScreenState extends State<SiteDetailScreen> {
title: site.name, title: site.name,
leadingAction: Utils.leadingBackWidget(context, onPressed: () { leadingAction: Utils.leadingBackWidget(context, onPressed: () {
if (changed && widget.onChanged != null) { if (changed && widget.onChanged != null) {
widget.onChanged(); widget.onChanged!();
} }
Navigator.pop(context); Navigator.pop(context);
}), }),
@ -162,13 +162,13 @@ class _SiteDetailScreenState extends State<SiteDetailScreen> {
if (activeHosts == null) { if (activeHosts == null) {
active = SizedBox(height: 20, width: 20, child: PlatformCircularProgressIndicator()); active = SizedBox(height: 20, width: 20, child: PlatformCircularProgressIndicator());
} else { } else {
active = Text(Utils.itemCountFormat(activeHosts.length, singleSuffix: "tunnel", multiSuffix: "tunnels")); active = Text(Utils.itemCountFormat(activeHosts!.length, singleSuffix: "tunnel", multiSuffix: "tunnels"));
} }
if (pendingHosts == null) { if (pendingHosts == null) {
pending = SizedBox(height: 20, width: 20, child: PlatformCircularProgressIndicator()); pending = SizedBox(height: 20, width: 20, child: PlatformCircularProgressIndicator());
} else { } else {
pending = Text(Utils.itemCountFormat(pendingHosts.length, singleSuffix: "tunnel", multiSuffix: "tunnels")); pending = Text(Utils.itemCountFormat(pendingHosts!.length, singleSuffix: "tunnel", multiSuffix: "tunnels"));
} }
return ConfigSection( return ConfigSection(
@ -176,11 +176,13 @@ class _SiteDetailScreenState extends State<SiteDetailScreen> {
children: <Widget>[ children: <Widget>[
ConfigPageItem( ConfigPageItem(
onPressed: () { onPressed: () {
if (activeHosts == null) return;
Utils.openPage( Utils.openPage(
context, context,
(context) => SiteTunnelsScreen( (context) => SiteTunnelsScreen(
pending: false, pending: false,
tunnels: activeHosts, tunnels: activeHosts!,
site: site, site: site,
onChanged: (hosts) { onChanged: (hosts) {
setState(() { setState(() {
@ -192,11 +194,13 @@ class _SiteDetailScreenState extends State<SiteDetailScreen> {
content: Container(alignment: Alignment.centerRight, child: active)), content: Container(alignment: Alignment.centerRight, child: active)),
ConfigPageItem( ConfigPageItem(
onPressed: () { onPressed: () {
if (pendingHosts == null) return;
Utils.openPage( Utils.openPage(
context, context,
(context) => SiteTunnelsScreen( (context) => SiteTunnelsScreen(
pending: true, pending: true,
tunnels: pendingHosts, tunnels: pendingHosts!,
site: site, site: site,
onChanged: (hosts) { onChanged: (hosts) {
setState(() { setState(() {
@ -250,7 +254,7 @@ class _SiteDetailScreenState extends State<SiteDetailScreen> {
pendingHosts = maps["pending"]; pendingHosts = maps["pending"];
setState(() {}); setState(() {});
} catch (err) { } 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) { if (widget.onChanged != null) {
widget.onChanged(); widget.onChanged!();
} }
return true; return true;
} }

View File

@ -11,7 +11,7 @@ import 'package:mobile_nebula/services/utils.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart'; import 'package:pull_to_refresh/pull_to_refresh.dart';
class SiteLogsScreen extends StatefulWidget { 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; 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'; import 'package:pull_to_refresh/pull_to_refresh.dart';
class SiteTunnelsScreen extends StatefulWidget { 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 Site site;
final List<HostInfo> tunnels; final List<HostInfo> tunnels;
final bool pending; final bool pending;
final Function(List<HostInfo>) onChanged; final Function(List<HostInfo>)? onChanged;
@override @override
_SiteTunnelsScreenState createState() => _SiteTunnelsScreenState(); _SiteTunnelsScreenState createState() => _SiteTunnelsScreenState();
} }
class _SiteTunnelsScreenState extends State<SiteTunnelsScreen> { class _SiteTunnelsScreenState extends State<SiteTunnelsScreen> {
Site site; late Site site;
List<HostInfo> tunnels; late List<HostInfo> tunnels;
RefreshController refreshController = RefreshController(initialRefresh: false); RefreshController refreshController = RefreshController(initialRefresh: false);
@override @override
void initState() { void initState() {
site = widget.site; site = widget.site;
tunnels = widget.tunnels ?? []; tunnels = widget.tunnels;
_sortTunnels(); _sortTunnels();
super.initState(); 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)]), label: Row(children: <Widget>[Padding(child: icon, padding: EdgeInsets.only(right: 10)), Text(hostInfo.vpnIp)]),
labelWidth: ipWidth, 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(); _sortTunnels();
if (widget.onChanged != null) { if (widget.onChanged != null) {
widget.onChanged(tunnels); widget.onChanged!(tunnels);
} }
setState(() {}); setState(() {});
} catch (err) { } 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 'dart:convert';
import 'package:barcode_scan/barcode_scan.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.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:flutter_platform_widgets/flutter_platform_widgets.dart';
import 'package:mobile_nebula/components/SimplePage.dart'; import 'package:mobile_nebula/components/SimplePage.dart';
import 'package:mobile_nebula/components/config/ConfigButtonItem.dart'; import 'package:mobile_nebula/components/config/ConfigButtonItem.dart';
@ -20,16 +20,24 @@ class CertificateResult {
CertificateInfo certInfo; CertificateInfo certInfo;
String key; String key;
CertificateResult({this.certInfo, this.key}); CertificateResult({required this.certInfo, required this.key});
} }
class AddCertificateScreen extends StatefulWidget { 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 // onSave will pop a new CertificateDetailsScreen.
final ValueChanged<CertificateResult> onSave; // If onSave is null, onReplace must be set.
// onReplace will return the CertificateResult, assuming the previous screen is a CertificateDetailsScreen final ValueChanged<CertificateResult>? onSave;
final ValueChanged<CertificateResult> onReplace; // 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 pubKey;
final String privKey; final String privKey;
@ -39,7 +47,7 @@ class AddCertificateScreen extends StatefulWidget {
} }
class _AddCertificateScreenState extends State<AddCertificateScreen> { class _AddCertificateScreenState extends State<AddCertificateScreen> {
String pubKey; late String pubKey;
bool showKey = false; bool showKey = false;
String inputType = 'paste'; String inputType = 'paste';
@ -98,9 +106,11 @@ class _AddCertificateScreenState extends State<AddCertificateScreen> {
child: CupertinoSlidingSegmentedControl( child: CupertinoSlidingSegmentedControl(
groupValue: inputType, groupValue: inputType,
onValueChanged: (v) { onValueChanged: (v) {
setState(() { if (v != null) {
inputType = v; setState(() {
}); inputType = v;
});
}
}, },
children: { children: {
'paste': Text('Copy/Paste'), 'paste': Text('Copy/Paste'),
@ -131,19 +141,16 @@ class _AddCertificateScreenState extends State<AddCertificateScreen> {
child: Text('Show/Import Private Key'), child: Text('Show/Import Private Key'),
color: CupertinoColors.secondaryLabel.resolveFrom(context), color: CupertinoColors.secondaryLabel.resolveFrom(context),
onPressed: () => Utils.confirmDelete(context, 'Show/Import Private Key?', () { onPressed: () => Utils.confirmDelete(context, 'Show/Import Private Key?', () {
setState(() { setState(() {
showKey = true; showKey = true;
}); });
}, deleteLabel: 'Yes')))); }, deleteLabel: 'Yes'))));
} }
return ConfigSection( return ConfigSection(
label: 'Import a private key generated on another device', label: 'Import a private key generated on another device',
children: [ children: [
ConfigTextItem( ConfigTextItem(controller: keyController, style: TextStyle(fontFamily: 'RobotoMono', fontSize: 14)),
controller: keyController,
style: TextStyle(fontFamily: 'RobotoMono', fontSize: 14)
),
], ],
); );
} }
@ -196,13 +203,13 @@ class _AddCertificateScreenState extends State<AddCertificateScreen> {
ConfigButtonItem( ConfigButtonItem(
content: Text('Scan a QR code'), content: Text('Scan a QR code'),
onPressed: () async { onPressed: () async {
var options = ScanOptions( try {
restrictFormat: [BarcodeFormat.qr], var result = await FlutterBarcodeScanner.scanBarcode('#ff6666', 'Cancel', true, ScanMode.QR);
); if (result != "") {
_addCertEntry(result);
var result = await BarcodeScanner.scan(options: options); }
if (result.rawContent != "") { } catch (err) {
_addCertEntry(result.rawContent); return Utils.popError(context, 'Error scanning QR code', err.toString());
} }
}), }),
], ],
@ -225,36 +232,34 @@ class _AddCertificateScreenState extends State<AddCertificateScreen> {
if (tryCertInfo.cert.details.isCa) { if (tryCertInfo.cert.details.isCa) {
return Utils.popError(context, 'Error loading certificate content', return Utils.popError(context, 'Error loading certificate content',
'A certificate authority is not appropriate for a client certificate.'); 'A certificate authority is not appropriate for a client certificate.');
} else if (!tryCertInfo.validity.valid) { } else if (!tryCertInfo.validity!.valid) {
return Utils.popError(context, 'Certificate was invalid', tryCertInfo.validity.reason); return Utils.popError(context, 'Certificate was invalid', tryCertInfo.validity!.reason);
} }
var certMatch = await platform.invokeMethod( var certMatch = await platform
"nebula.verifyCertAndKey", .invokeMethod("nebula.verifyCertAndKey", <String, String>{"cert": rawCert, "key": keyController.text});
<String, String>{"cert": rawCert, "key": keyController.text}
);
if (!certMatch) { 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 // 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', return Utils.popError(context, 'Error loading certificate content',
'The provided certificates public key is not compatible with the private key.'); '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 (widget.onReplace != null) {
// If we are replacing we just return the results now
Navigator.pop(context); Navigator.pop(context);
widget.onReplace(CertificateResult(certInfo: tryCertInfo, key: keyController.text)); widget.onReplace!(CertificateResult(certInfo: tryCertInfo, key: keyController.text));
return; 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) { } on PlatformException catch (err) {
return Utils.popError(context, 'Error loading certificate content', err.details ?? err.message); return Utils.popError(context, 'Error loading certificate content', err.details ?? err.message);

View File

@ -28,10 +28,23 @@ class Advanced {
String verbosity; String verbosity;
List<UnsafeRoute> unsafeRoutes; List<UnsafeRoute> unsafeRoutes;
int mtu; 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 { 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 Site site;
final ValueChanged<Advanced> onSave; final ValueChanged<Advanced> onSave;
@ -41,17 +54,19 @@ class AdvancedScreen extends StatefulWidget {
} }
class _AdvancedScreenState extends State<AdvancedScreen> { class _AdvancedScreenState extends State<AdvancedScreen> {
var settings = Advanced(); late Advanced settings;
var changed = false; var changed = false;
@override @override
void initState() { void initState() {
settings.lhDuration = widget.site.lhDuration; settings = Advanced(
settings.port = widget.site.port; lhDuration: widget.site.lhDuration,
settings.cipher = widget.site.cipher; port: widget.site.port,
settings.verbosity = widget.site.logVerbosity; cipher: widget.site.cipher,
settings.unsafeRoutes = widget.site.unsafeRoutes; verbosity: widget.site.logVerbosity,
settings.mtu = widget.site.mtu; unsafeRoutes: widget.site.unsafeRoutes,
mtu: widget.site.mtu,
);
super.initState(); super.initState();
} }
@ -80,7 +95,9 @@ class _AdvancedScreenState extends State<AdvancedScreen> {
inputFormatters: [FilteringTextInputFormatter.digitsOnly], inputFormatters: [FilteringTextInputFormatter.digitsOnly],
onSaved: (val) { onSaved: (val) {
setState(() { 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], inputFormatters: [FilteringTextInputFormatter.digitsOnly],
onSaved: (val) { onSaved: (val) {
setState(() { 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], inputFormatters: [FilteringTextInputFormatter.digitsOnly],
onSaved: (val) { onSaved: (val) {
setState(() { 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); return RenderedConfigScreen(config: config, name: widget.site.name);
}); });
} catch (err) { } 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 'dart:convert';
import 'package:barcode_scan/barcode_scan.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/services.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/FormPage.dart';
import 'package:mobile_nebula/components/config/ConfigButtonItem.dart'; import 'package:mobile_nebula/components/config/ConfigButtonItem.dart';
import 'package:mobile_nebula/components/config/ConfigPageItem.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) //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 { 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 List<CertificateInfo> cas;
final ValueChanged<List<CertificateInfo>> onSave; final ValueChanged<List<CertificateInfo>>? onSave;
@override @override
_CAListScreenState createState() => _CAListScreenState(); _CAListScreenState createState() => _CAListScreenState();
@ -59,7 +63,7 @@ class _CAListScreenState extends State<CAListScreen> {
onSave: () { onSave: () {
if (widget.onSave != null) { if (widget.onSave != null) {
Navigator.pop(context); Navigator.pop(context);
widget.onSave(cas.values.map((ca) { widget.onSave!(cas.values.map((ca) {
return ca; return ca;
}).toList()); }).toList());
} }
@ -90,8 +94,8 @@ class _CAListScreenState extends State<CAListScreen> {
return items; return items;
} }
_addCAEntry(String ca, ValueChanged<String> callback) async { _addCAEntry(String ca, ValueChanged<String?> callback) async {
String error; String? error;
//TODO: show an error popup //TODO: show an error popup
try { try {
@ -118,9 +122,7 @@ class _CAListScreenState extends State<CAListScreen> {
error = err.details ?? err.message; error = err.details ?? err.message;
} }
if (callback != null) { callback(error);
callback(error);
}
} }
List<Widget> _addCA() { List<Widget> _addCA() {
@ -130,9 +132,11 @@ class _CAListScreenState extends State<CAListScreen> {
child: CupertinoSlidingSegmentedControl( child: CupertinoSlidingSegmentedControl(
groupValue: inputType, groupValue: inputType,
onValueChanged: (v) { onValueChanged: (v) {
setState(() { if (v != null) {
inputType = v; setState(() {
}); inputType = v;
});
}
}, },
children: { children: {
'paste': Text('Copy/Paste'), 'paste': Text('Copy/Paste'),
@ -215,19 +219,19 @@ class _CAListScreenState extends State<CAListScreen> {
ConfigButtonItem( ConfigButtonItem(
content: Text('Scan a QR code'), content: Text('Scan a QR code'),
onPressed: () async { onPressed: () async {
var options = ScanOptions( try {
restrictFormat: [BarcodeFormat.qr], var result = await FlutterBarcodeScanner.scanBarcode('#ff6666', 'Cancel', true, ScanMode.QR);
); if (result != "") {
_addCAEntry(result, (err) {
var result = await BarcodeScanner.scan(options: options); if (err != null) {
if (result.rawContent != "") { Utils.popError(context, 'Error loading CA content', err);
_addCAEntry(result.rawContent, (err) { } else {
if (err != null) { setState(() {});
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) /// Displays the details of a CertificateInfo object. Respects incomplete objects (missing validity or rawCert)
class CertificateDetailsScreen extends StatefulWidget { class CertificateDetailsScreen extends StatefulWidget {
const CertificateDetailsScreen({Key key, this.certInfo, this.onDelete, this.onSave, this.onReplace, this.pubKey, this.privKey}) const CertificateDetailsScreen({
: super(key: key); Key? key,
required this.certInfo,
this.onDelete,
this.onSave,
this.onReplace,
this.pubKey,
this.privKey,
}) : super(key: key);
final CertificateInfo certInfo; final CertificateInfo certInfo;
// onDelete is used to remove a CA cert // onDelete is used to remove a CA cert
final Function onDelete; final Function? onDelete;
// onSave is used to install a new certificate // 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 // 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; // pubKey and privKey should be set if onReplace is not null.
final String privKey; final String? pubKey;
final String? privKey;
@override @override
_CertificateDetailsScreenState createState() => _CertificateDetailsScreenState(); _CertificateDetailsScreenState createState() => _CertificateDetailsScreenState();
@ -33,8 +41,8 @@ class CertificateDetailsScreen extends StatefulWidget {
class _CertificateDetailsScreenState extends State<CertificateDetailsScreen> { class _CertificateDetailsScreenState extends State<CertificateDetailsScreen> {
bool changed = false; bool changed = false;
CertificateResult certResult; CertificateResult? certResult;
CertificateInfo certInfo; late CertificateInfo certInfo;
ScrollController controller = ScrollController(); ScrollController controller = ScrollController();
@override @override
@ -58,10 +66,10 @@ class _CertificateDetailsScreenState extends State<CertificateDetailsScreen> {
onSave: () { onSave: () {
if (widget.onSave != null) { if (widget.onSave != null) {
Navigator.pop(context); Navigator.pop(context);
widget.onSave(); widget.onSave!();
} else if (widget.onReplace != null) { } else if (widget.onReplace != null) {
Navigator.pop(context); Navigator.pop(context);
widget.onReplace(certResult); widget.onReplace!(certResult!);
} }
}, },
hideSave: widget.onSave == null && widget.onReplace == null, hideSave: widget.onSave == null && widget.onReplace == null,
@ -86,8 +94,8 @@ class _CertificateDetailsScreenState extends State<CertificateDetailsScreen> {
Widget _buildValid() { Widget _buildValid() {
var valid = Text('yes'); var valid = Text('yes');
if (certInfo.validity != null && !certInfo.validity.valid) { if (certInfo.validity != null && !certInfo.validity!.valid) {
valid = Text(certInfo.validity.valid ? 'yes' : certInfo.validity.reason, valid = Text(certInfo.validity!.valid ? 'yes' : certInfo.validity!.reason,
style: TextStyle(color: CupertinoColors.systemRed.resolveFrom(context))); style: TextStyle(color: CupertinoColors.systemRed.resolveFrom(context)));
} }
return ConfigSection( return ConfigSection(
@ -137,7 +145,7 @@ class _CertificateDetailsScreenState extends State<CertificateDetailsScreen> {
certInfo.rawCert != null certInfo.rawCert != null
? ConfigItem( ? ConfigItem(
label: Text('PEM Format'), 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) crossAxisAlignment: CrossAxisAlignment.start)
: Container(), : Container(),
], ],
@ -145,7 +153,7 @@ class _CertificateDetailsScreenState extends State<CertificateDetailsScreen> {
} }
Widget _buildReplace() { Widget _buildReplace() {
if (widget.onReplace == null) { if (widget.onReplace == null || widget.pubKey == null || widget.privKey == null) {
return Container(); return Container();
} }
@ -158,16 +166,19 @@ class _CertificateDetailsScreenState extends State<CertificateDetailsScreen> {
color: CupertinoColors.systemRed.resolveFrom(context), color: CupertinoColors.systemRed.resolveFrom(context),
onPressed: () { onPressed: () {
Utils.openPage(context, (context) { Utils.openPage(context, (context) {
return AddCertificateScreen(onReplace: (result) { return AddCertificateScreen(
setState(() { onReplace: (result) {
changed = true; setState(() {
certResult = result; changed = true;
certInfo = certResult.certInfo; certResult = result;
}); certInfo = result.certInfo;
// Slam the page back to the top });
controller.animateTo(0, // Slam the page back to the top
duration: const Duration(milliseconds: 10), curve: Curves.linearToEaseOut); controller.animateTo(0,
}, pubKey: widget.pubKey, privKey: widget.privKey, ); 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), color: CupertinoColors.systemRed.resolveFrom(context),
onPressed: () => Utils.confirmDelete(context, title, () async { onPressed: () => Utils.confirmDelete(context, title, () async {
Navigator.pop(context); 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'; import 'package:mobile_nebula/components/config/ConfigSection.dart';
class CipherScreen extends StatefulWidget { 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 String cipher;
final ValueChanged<String> onSave; final ValueChanged<String> onSave;
@ -16,7 +20,7 @@ class CipherScreen extends StatefulWidget {
} }
class _CipherScreenState extends State<CipherScreen> { class _CipherScreenState extends State<CipherScreen> {
String cipher; late String cipher;
bool changed = false; bool changed = false;
@override @override
@ -32,9 +36,7 @@ class _CipherScreenState extends State<CipherScreen> {
changed: changed, changed: changed,
onSave: () { onSave: () {
Navigator.pop(context); Navigator.pop(context);
if (widget.onSave != null) { widget.onSave(cipher);
widget.onSave(cipher);
}
}, },
child: Column( child: Column(
children: <Widget>[ children: <Widget>[

View File

@ -6,7 +6,11 @@ import 'package:mobile_nebula/components/config/ConfigCheckboxItem.dart';
import 'package:mobile_nebula/components/config/ConfigSection.dart'; import 'package:mobile_nebula/components/config/ConfigSection.dart';
class LogVerbosityScreen extends StatefulWidget { 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 String verbosity;
final ValueChanged<String> onSave; final ValueChanged<String> onSave;
@ -16,7 +20,7 @@ class LogVerbosityScreen extends StatefulWidget {
} }
class _LogVerbosityScreenState extends State<LogVerbosityScreen> { class _LogVerbosityScreenState extends State<LogVerbosityScreen> {
String verbosity; late String verbosity;
bool changed = false; bool changed = false;
@override @override
@ -32,9 +36,7 @@ class _LogVerbosityScreenState extends State<LogVerbosityScreen> {
changed: changed, changed: changed,
onSave: () { onSave: () {
Navigator.pop(context); Navigator.pop(context);
if (widget.onSave != null) { widget.onSave(verbosity);
widget.onSave(verbosity);
}
}, },
child: Column( child: Column(
children: <Widget>[ children: <Widget>[

View File

@ -7,7 +7,11 @@ class RenderedConfigScreen extends StatelessWidget {
final String config; final String config;
final String name; 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {

View File

@ -22,9 +22,13 @@ import 'package:mobile_nebula/services/utils.dart';
//TODO: Enforce a name //TODO: Enforce a name
class SiteConfigScreen extends StatefulWidget { 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 // This is called after the target OS has saved the configuration
final ValueChanged<Site> onSave; final ValueChanged<Site> onSave;
@ -37,9 +41,9 @@ class _SiteConfigScreenState extends State<SiteConfigScreen> {
bool changed = false; bool changed = false;
bool newSite = false; bool newSite = false;
bool debug = false; bool debug = false;
Site site; late Site site;
String pubKey; String? pubKey;
String privKey; String? privKey;
static const platform = MethodChannel('net.defined.mobileNebula/NebulaVpnService'); static const platform = MethodChannel('net.defined.mobileNebula/NebulaVpnService');
final nameController = TextEditingController(); final nameController = TextEditingController();
@ -52,7 +56,7 @@ class _SiteConfigScreenState extends State<SiteConfigScreen> {
newSite = true; newSite = true;
site = Site(); site = Site();
} else { } else {
site = widget.site; site = widget.site!;
nameController.text = site.name; nameController.text = site.name;
} }
@ -61,7 +65,7 @@ class _SiteConfigScreenState extends State<SiteConfigScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (pubKey == null) { if (pubKey == null || privKey == null) {
return Center( return Center(
child: fpw.PlatformCircularProgressIndicator(cupertino: (_, __) { child: fpw.PlatformCircularProgressIndicator(cupertino: (_, __) {
return fpw.CupertinoProgressIndicatorData(radius: 50); return fpw.CupertinoProgressIndicatorData(radius: 50);
@ -81,9 +85,7 @@ class _SiteConfigScreenState extends State<SiteConfigScreen> {
} }
Navigator.pop(context); Navigator.pop(context);
if (widget.onSave != null) { widget.onSave(site);
widget.onSave(site);
}
}, },
child: Column( child: Column(
children: <Widget>[ children: <Widget>[
@ -126,11 +128,11 @@ class _SiteConfigScreenState extends State<SiteConfigScreen> {
} }
Widget _keys() { 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; var caError = site.ca.length == 0;
if (!caError) { if (!caError) {
site.ca.forEach((ca) { site.ca.forEach((ca) {
if (!ca.validity.valid) { if (ca.validity == null || !ca.validity!.valid) {
caError = true; caError = true;
} }
}); });
@ -147,13 +149,13 @@ class _SiteConfigScreenState extends State<SiteConfigScreen> {
child: Icon(Icons.error, color: CupertinoColors.systemRed.resolveFrom(context), size: 20), child: Icon(Icons.error, color: CupertinoColors.systemRed.resolveFrom(context), size: 20),
padding: EdgeInsets.only(right: 5)) padding: EdgeInsets.only(right: 5))
: Container(), : Container(),
certError ? Text('Needs attention') : Text(site.certInfo.cert.details.name) certError ? Text('Needs attention') : Text(site.certInfo?.cert.details.name ?? 'Unknown certificate')
]), ]),
onPressed: () { onPressed: () {
Utils.openPage(context, (context) { Utils.openPage(context, (context) {
if (site.certInfo != null) { if (site.certInfo != null) {
return CertificateDetailsScreen( return CertificateDetailsScreen(
certInfo: site.certInfo, certInfo: site.certInfo!,
pubKey: pubKey, pubKey: pubKey,
privKey: privKey, privKey: privKey,
onReplace: (result) { onReplace: (result) {
@ -165,13 +167,16 @@ class _SiteConfigScreenState extends State<SiteConfigScreen> {
}); });
} }
return AddCertificateScreen(pubKey: pubKey, privKey: privKey, onSave: (result) { return AddCertificateScreen(
setState(() { pubKey: pubKey!,
changed = true; privKey: privKey!,
site.certInfo = result.certInfo; onSave: (result) {
site.key = result.key; setState(() {
}); changed = true;
}); site.certInfo = result.certInfo;
site.key = result.key;
});
});
}); });
}, },
), ),

View File

@ -15,35 +15,42 @@ class _IPAndPort {
final FocusNode focusNode; final FocusNode focusNode;
IPAndPort destination; IPAndPort destination;
_IPAndPort({this.focusNode, this.destination}); _IPAndPort({required this.focusNode, required this.destination});
} }
class StaticHostmapScreen extends StatefulWidget { class StaticHostmapScreen extends StatefulWidget {
const StaticHostmapScreen( StaticHostmapScreen({
{Key key, this.nebulaIp, this.destinations, this.lighthouse = false, this.onDelete, @required this.onSave}) Key? key,
: super(key: key); this.nebulaIp = '',
destinations,
this.lighthouse = false,
this.onDelete,
required this.onSave,
}) : this.destinations = destinations ?? [],
super(key: key);
final List<IPAndPort> destinations; final List<IPAndPort> destinations;
final String nebulaIp; final String nebulaIp;
final bool lighthouse; final bool lighthouse;
final ValueChanged<Hostmap> onSave; final ValueChanged<Hostmap> onSave;
final Function onDelete; final Function? onDelete;
@override @override
_StaticHostmapScreenState createState() => _StaticHostmapScreenState(); _StaticHostmapScreenState createState() => _StaticHostmapScreenState();
} }
class _StaticHostmapScreenState extends State<StaticHostmapScreen> { class _StaticHostmapScreenState extends State<StaticHostmapScreen> {
Map<Key, _IPAndPort> _destinations = {}; late Map<Key, _IPAndPort> _destinations;
String _nebulaIp; late String _nebulaIp;
bool _lighthouse; late bool _lighthouse;
bool changed = false; bool changed = false;
@override @override
void initState() { void initState() {
_nebulaIp = widget.nebulaIp; _nebulaIp = widget.nebulaIp;
_lighthouse = widget.lighthouse; _lighthouse = widget.lighthouse;
widget.destinations?.forEach((dest) { _destinations = {};
widget.destinations.forEach((dest) {
_destinations[UniqueKey()] = _IPAndPort(focusNode: FocusNode(), destination: dest); _destinations[UniqueKey()] = _IPAndPort(focusNode: FocusNode(), destination: dest);
}); });
@ -75,7 +82,9 @@ class _StaticHostmapScreenState extends State<StaticHostmapScreen> {
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end,
textInputAction: TextInputAction.next, textInputAction: TextInputAction.next,
onSaved: (v) { onSaved: (v) {
_nebulaIp = v; if (v != null) {
_nebulaIp = v;
}
})), })),
ConfigItem( ConfigItem(
label: Text('Lighthouse'), label: Text('Lighthouse'),
@ -107,7 +116,7 @@ class _StaticHostmapScreenState extends State<StaticHostmapScreen> {
color: CupertinoColors.systemRed.resolveFrom(context), color: CupertinoColors.systemRed.resolveFrom(context),
onPressed: () => Utils.confirmDelete(context, 'Delete host map?', () { onPressed: () => Utils.confirmDelete(context, 'Delete host map?', () {
Navigator.of(context).pop(); Navigator.of(context).pop();
widget.onDelete(); widget.onDelete!();
}), }),
))) )))
: Container() : Container()
@ -116,15 +125,13 @@ class _StaticHostmapScreenState extends State<StaticHostmapScreen> {
_onSave() { _onSave() {
Navigator.pop(context); 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) { _destinations.forEach((_, dest) {
map.destinations.add(dest.destination); map.destinations.add(dest.destination);
}); });
widget.onSave(map); widget.onSave(map);
}
} }
List<Widget> _buildHosts() { List<Widget> _buildHosts() {
@ -152,7 +159,9 @@ class _StaticHostmapScreenState extends State<StaticHostmapScreen> {
noBorder: true, noBorder: true,
initialValue: dest.destination, initialValue: dest.destination,
onSaved: (v) { onSaved: (v) {
dest.destination = v; if (v != null) {
dest.destination = v;
}
}, },
)), )),
]), ]),

View File

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

View File

@ -10,18 +10,23 @@ import 'package:mobile_nebula/models/UnsafeRoute.dart';
import 'package:mobile_nebula/services/utils.dart'; import 'package:mobile_nebula/services/utils.dart';
class UnsafeRouteScreen extends StatefulWidget { 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 UnsafeRoute route;
final ValueChanged<UnsafeRoute> onSave; final ValueChanged<UnsafeRoute> onSave;
final Function onDelete; final Function? onDelete;
@override @override
_UnsafeRouteScreenState createState() => _UnsafeRouteScreenState(); _UnsafeRouteScreenState createState() => _UnsafeRouteScreenState();
} }
class _UnsafeRouteScreenState extends State<UnsafeRouteScreen> { class _UnsafeRouteScreenState extends State<UnsafeRouteScreen> {
UnsafeRoute route; late UnsafeRoute route;
bool changed = false; bool changed = false;
FocusNode routeFocus = FocusNode(); FocusNode routeFocus = FocusNode();
@ -36,7 +41,7 @@ class _UnsafeRouteScreenState extends State<UnsafeRouteScreen> {
@override @override
Widget build(BuildContext context) { 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( return FormPage(
title: widget.onDelete == null ? 'New Unsafe Route' : 'Edit Unsafe Route', title: widget.onDelete == null ? 'New Unsafe Route' : 'Edit Unsafe Route',
@ -57,7 +62,7 @@ class _UnsafeRouteScreenState extends State<UnsafeRouteScreen> {
ConfigItem( ConfigItem(
label: Text('Via'), label: Text('Via'),
content: IPFormField( content: IPFormField(
initialValue: route?.via ?? "", initialValue: route.via ?? '',
ipOnly: true, ipOnly: true,
help: 'nebula ip', help: 'nebula ip',
textAlign: TextAlign.end, textAlign: TextAlign.end,
@ -66,7 +71,9 @@ class _UnsafeRouteScreenState extends State<UnsafeRouteScreen> {
focusNode: viaFocus, focusNode: viaFocus,
nextFocusNode: mtuFocus, nextFocusNode: mtuFocus,
onSaved: (v) { onSaved: (v) {
route.via = v; if (v != null) {
route.via = v;
}
})), })),
//TODO: Android doesn't appear to support route based MTU, figure this out //TODO: Android doesn't appear to support route based MTU, figure this out
// ConfigItem( // ConfigItem(
@ -94,7 +101,7 @@ class _UnsafeRouteScreenState extends State<UnsafeRouteScreen> {
color: CupertinoColors.systemRed.resolveFrom(context), color: CupertinoColors.systemRed.resolveFrom(context),
onPressed: () => Utils.confirmDelete(context, 'Delete unsafe route?', () { onPressed: () => Utils.confirmDelete(context, 'Delete unsafe route?', () {
Navigator.of(context).pop(); Navigator.of(context).pop();
widget.onDelete(); widget.onDelete!();
}), }),
))) )))
: Container() : Container()
@ -103,8 +110,6 @@ class _UnsafeRouteScreenState extends State<UnsafeRouteScreen> {
_onSave() { _onSave() {
Navigator.pop(context); 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'; import 'package:mobile_nebula/services/utils.dart';
class UnsafeRoutesScreen extends StatefulWidget { 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 List<UnsafeRoute> unsafeRoutes;
final ValueChanged<List<UnsafeRoute>> onSave; final ValueChanged<List<UnsafeRoute>> onSave;
@ -18,11 +22,12 @@ class UnsafeRoutesScreen extends StatefulWidget {
} }
class _UnsafeRoutesScreenState extends State<UnsafeRoutesScreen> { class _UnsafeRoutesScreenState extends State<UnsafeRoutesScreen> {
Map<Key, UnsafeRoute> unsafeRoutes = {}; late Map<Key, UnsafeRoute> unsafeRoutes;
bool changed = false; bool changed = false;
@override @override
void initState() { void initState() {
unsafeRoutes = {};
widget.unsafeRoutes.forEach((route) { widget.unsafeRoutes.forEach((route) {
unsafeRoutes[UniqueKey()] = route; unsafeRoutes[UniqueKey()] = route;
}); });
@ -43,9 +48,7 @@ class _UnsafeRoutesScreenState extends State<UnsafeRoutesScreen> {
_onSave() { _onSave() {
Navigator.pop(context); Navigator.pop(context);
if (widget.onSave != null) { widget.onSave(unsafeRoutes.values.toList());
widget.onSave(unsafeRoutes.values.toList());
}
} }
List<Widget> _buildRoutes() { List<Widget> _buildRoutes() {
@ -53,7 +56,7 @@ class _UnsafeRoutesScreenState extends State<UnsafeRoutesScreen> {
List<Widget> items = []; List<Widget> items = [];
unsafeRoutes.forEach((key, route) { unsafeRoutes.forEach((key, route) {
items.add(ConfigPageItem( items.add(ConfigPageItem(
label: Text(route.route), label: Text(route.route ?? ''),
labelWidth: ipWidth, labelWidth: ipWidth,
content: Text('via ${route.via}', textAlign: TextAlign.end), content: Text('via ${route.via}', textAlign: TextAlign.end),
onPressed: () { onPressed: () {

View File

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

View File

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

View File

@ -34,7 +34,7 @@ class Storage {
return directory.path; return directory.path;
} }
Future<String> readFile(String path) async { Future<String?> readFile(String path) async {
try { try {
final parent = await localPath; final parent = await localPath;
final file = File(p.join(parent, path)); 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. /// Builds a simple leading widget that pops the current screen.
/// Provide your own onPressed to override that behavior, just remember you have to pop /// 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) { if (Platform.isIOS) {
return CupertinoButton( return CupertinoButton(
child: Row(children: <Widget>[Icon(context.platformIcons.back), Text(label)]), child: Row(children: <Widget>[Icon(context.platformIcons.back), Text(label)]),
@ -83,11 +83,11 @@ class Utils {
static Widget trailingSaveWidget(BuildContext context, Function onPressed) { static Widget trailingSaveWidget(BuildContext context, Function onPressed) {
return CupertinoButton( return CupertinoButton(
child: Text('Save', style: TextStyle( child: Text('Save',
fontWeight: FontWeight.bold, style: TextStyle(
//TODO: For some reason on android if inherit is the default of true the text color here turns to the background color fontWeight: FontWeight.bold,
inherit: Platform.isIOS ? true : false //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, padding: Platform.isAndroid ? null : EdgeInsets.zero,
onPressed: () => onPressed()); 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) { if (stack != null) {
error += '\n${stack.toString()}'; 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; 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(); await FilePicker.platform.clearTemporaryFiles();
final result = await FilePicker.platform.pickFiles(allowMultiple: false); final result = await FilePicker.platform.pickFiles(allowMultiple: false);
if (result == null) { if (result == null) {
return null; return null;
} }
final file = File(result.files.first.path); final file = File(result!.files.first.path!);
return file.readAsString(); return file.readAsString();
} }
} }

View File

@ -1,6 +1,10 @@
import 'dart:io'; 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); final ia = InternetAddress.tryParse(str);
if (ia == null) { if (ia == null) {
return false; return false;

View File

@ -6,7 +6,7 @@ go 1.18
require ( require (
github.com/sirupsen/logrus v1.8.1 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 golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29
gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v2 v2.4.0
) )
@ -33,7 +33,6 @@ require (
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 // indirect github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 // indirect
github.com/vishvananda/netlink v1.1.0 // indirect github.com/vishvananda/netlink v1.1.0 // indirect
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // 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/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
golang.org/x/net v0.0.0-20220403103023-749bd193bc2b // indirect golang.org/x/net v0.0.0-20220403103023-749bd193bc2b // indirect
golang.org/x/sys v0.0.0-20220406155245-289d7a0edf71 // 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.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= 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.1-0.20220919174748-4c0ae3df5ef7 h1:u06zUX/HdLxZnyewES34xIjRELl5xDXCLseiiBn6Dyo=
github.com/slackhq/nebula v1.6.0/go.mod h1:UmkqnXe4O53QwToSl/gG7sM4BroQwAB7dd4hUaT6MlI= 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 h1:TG/diQgUe0pntT/2D9tmUCz4VNwm9MfrtPr0SU2qSX8=
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E= 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= 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.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/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.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.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 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-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-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-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-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-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 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-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/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-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-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.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.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 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-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-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-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-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-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220403103023-749bd193bc2b h1:vI32FkLJNAWtGD4BwkThwEy6XS7ZLLMHkSkYfF8M0W0= 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-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-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-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-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-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220406155245-289d7a0edf71 h1:PRD0hj6tTuUnCFD08vkvjkYFbQg/9lV8KIxe1y4/cvU= 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-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 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.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 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20=
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= 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= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@ -7,14 +7,7 @@ packages:
name: async name: async
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.8.2" version: "2.9.0"
barcode_scan:
dependency: "direct main"
description:
name: barcode_scan
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.1"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
@ -28,28 +21,21 @@ packages:
name: characters name: characters
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.2.0" version: "1.2.1"
charcode:
dependency: transitive
description:
name: charcode
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.1"
clock: clock:
dependency: transitive dependency: transitive
description: description:
name: clock name: clock
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.0" version: "1.1.1"
collection: collection:
dependency: transitive dependency: transitive
description: description:
name: collection name: collection
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.15.0" version: "1.16.0"
crypto: crypto:
dependency: transitive dependency: transitive
description: description:
@ -70,14 +56,14 @@ packages:
name: fake_async name: fake_async
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.2.0" version: "1.3.1"
ffi: ffi:
dependency: transitive dependency: transitive
description: description:
name: ffi name: ffi
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.2" version: "2.0.1"
file: file:
dependency: transitive dependency: transitive
description: description:
@ -91,33 +77,33 @@ packages:
name: file_picker name: file_picker
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.0.4" version: "5.0.1"
fixnum:
dependency: transitive
description:
name: fixnum
url: "https://pub.dartlang.org"
source: hosted
version: "0.10.11"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" 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: flutter_platform_widgets:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_platform_widgets name: flutter_platform_widgets
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.12.1" version: "2.0.0"
flutter_plugin_android_lifecycle: flutter_plugin_android_lifecycle:
dependency: transitive dependency: transitive
description: description:
name: flutter_plugin_android_lifecycle name: flutter_plugin_android_lifecycle
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.5" version: "2.0.7"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@ -134,21 +120,28 @@ packages:
name: js name: js
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.6.3" version: "0.6.4"
matcher: matcher:
dependency: transitive dependency: transitive
description: description:
name: matcher name: matcher
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted 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: meta:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.7.0" version: "1.8.0"
package_info: package_info:
dependency: "direct main" dependency: "direct main"
description: description:
@ -162,14 +155,14 @@ packages:
name: path name: path
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.8.0" version: "1.8.2"
path_provider: path_provider:
dependency: "direct main" dependency: "direct main"
description: description:
name: path_provider name: path_provider
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.8" version: "2.0.11"
path_provider_android: path_provider_android:
dependency: transitive dependency: transitive
description: description:
@ -190,7 +183,7 @@ packages:
name: path_provider_linux name: path_provider_linux
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.4" version: "2.1.7"
path_provider_macos: path_provider_macos:
dependency: transitive dependency: transitive
description: description:
@ -211,7 +204,7 @@ packages:
name: path_provider_windows name: path_provider_windows
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.4" version: "2.1.3"
platform: platform:
dependency: transitive dependency: transitive
description: description:
@ -233,13 +226,6 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "4.2.4" version: "4.2.4"
protobuf:
dependency: transitive
description:
name: protobuf
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.4"
pull_to_refresh: pull_to_refresh:
dependency: "direct main" dependency: "direct main"
description: description:
@ -258,7 +244,7 @@ packages:
name: source_span name: source_span
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.8.1" version: "1.9.0"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
@ -279,21 +265,21 @@ packages:
name: string_scanner name: string_scanner
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.0" version: "1.1.1"
term_glyph: term_glyph:
dependency: transitive dependency: transitive
description: description:
name: term_glyph name: term_glyph
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.2.0" version: "1.2.1"
test_api: test_api:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.4.3" version: "0.4.12"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:
@ -370,14 +356,14 @@ packages:
name: vector_math name: vector_math
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.1" version: "2.1.2"
win32: win32:
dependency: transitive dependency: transitive
description: description:
name: win32 name: win32
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.3.3" version: "2.7.0"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:
@ -386,5 +372,5 @@ packages:
source: hosted source: hosted
version: "0.2.0" version: "0.2.0"
sdks: sdks:
dart: ">=2.14.0 <3.0.0" dart: ">=2.18.1 <3.0.0"
flutter: ">=2.5.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. # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at # Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 0.1.0+52 version: 0.1.0+54
environment: environment:
sdk: ">=2.1.0 <3.0.0" sdk: '>=2.18.1 <3.0.0'
dependencies: dependencies:
flutter: flutter:
@ -23,14 +23,14 @@ dependencies:
# The following adds the Cupertino Icons font to your application. # The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons. # Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2 cupertino_icons: ^1.0.2
flutter_platform_widgets: ^1.2.0 flutter_platform_widgets: ^2.0.0
path_provider: ^2.0.1 path_provider: ^2.0.11
file_picker: ^3.0.2+2 file_picker: ^5.0.1
barcode_scan: ^3.0.1
uuid: ^3.0.4 uuid: ^3.0.4
package_info: ^2.0.0 package_info: ^2.0.0
url_launcher: ^6.0.6 url_launcher: ^6.0.6
pull_to_refresh: ^2.0.0 pull_to_refresh: ^2.0.0
flutter_barcode_scanner: ^2.0.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: