forked from core/mobile_nebula
Upgrade to flutter 3 (#70)
Co-authored-by: John Maguire <contact@johnmaguire.me>
This commit is contained in:
parent
e3780bda1e
commit
dbe67c2f81
29
README.md
29
README.md
|
@ -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
|
||||||
|
|
|
@ -6,3 +6,4 @@ gradle-wrapper.jar
|
||||||
/local.properties
|
/local.properties
|
||||||
GeneratedPluginRegistrant.java
|
GeneratedPluginRegistrant.java
|
||||||
/build/build-attribution/
|
/build/build-attribution/
|
||||||
|
/mobileNebula/mobileNebula.aar
|
||||||
|
|
|
@ -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'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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"
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
configurations.maybeCreate("default")
|
||||||
|
exec {
|
||||||
|
workingDir '../../'
|
||||||
|
commandLine './gen-artifacts.sh', 'android'
|
||||||
|
}
|
||||||
|
artifacts.add("default", file('mobileNebula.aar'))
|
|
@ -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
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
include ':app'
|
|
|
@ -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"
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 = (
|
||||||
|
|
|
@ -47,5 +47,7 @@
|
||||||
</array>
|
</array>
|
||||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||||
<false/>
|
<false/>
|
||||||
|
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||||
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|
|
@ -133,13 +133,13 @@ class SiteUpdater: NSObject, FlutterStreamHandler {
|
||||||
let connected = self.site.connected
|
let connected = self.site.connected
|
||||||
self.site.status = statusString[self.site.manager!.connection.status]
|
self.site.status = statusString[self.site.manager!.connection.status]
|
||||||
self.site.connected = statusMap[self.site.manager!.connection.status]
|
self.site.connected = statusMap[self.site.manager!.connection.status]
|
||||||
|
|
||||||
// Check to see if we just moved to connected and if we have a start function to call when that happens
|
// Check to see if we just moved to connected and if we have a start function to call when that happens
|
||||||
if self.site.connected! && connected != self.site.connected && self.startFunc != nil {
|
if self.site.connected! && connected != self.site.connected && self.startFunc != nil {
|
||||||
self.startFunc!()
|
self.startFunc!()
|
||||||
self.startFunc = nil
|
self.startFunc = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
let d: Dictionary<String, Any> = [
|
let d: Dictionary<String, Any> = [
|
||||||
"connected": self.site.connected!,
|
"connected": self.site.connected!,
|
||||||
"status": self.site.status!,
|
"status": self.site.status!,
|
||||||
|
|
|
@ -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],
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 ?? "";
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 ?? "";
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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!,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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!();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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)),
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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]),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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']),
|
||||||
|
|
|
@ -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,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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});
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
],
|
],
|
||||||
|
|
|
@ -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!();
|
||||||
}))));
|
}))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>[
|
||||||
|
|
|
@ -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>[
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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,17 +128,17 @@ 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;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return ConfigSection(
|
return ConfigSection(
|
||||||
label: "IDENTITY",
|
label: "IDENTITY",
|
||||||
children: [
|
children: [
|
||||||
ConfigPageItem(
|
ConfigPageItem(
|
||||||
|
@ -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;
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
)),
|
)),
|
||||||
]),
|
]),
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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: () {
|
||||||
|
|
|
@ -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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>{
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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=
|
||||||
|
|
90
pubspec.lock
90
pubspec.lock
|
@ -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"
|
||||||
|
|
12
pubspec.yaml
12
pubspec.yaml
|
@ -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:
|
||||||
|
|
Loading…
Reference in New Issue