diff --git a/client/.gitignore b/client/.gitignore
new file mode 100644
index 0000000..07488ba
--- /dev/null
+++ b/client/.gitignore
@@ -0,0 +1,70 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.buildlog/
+.history
+.svn/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# Visual Studio Code related
+.vscode/
+
+# Flutter/Dart/Pub related
+**/doc/api/
+.dart_tool/
+.flutter-plugins
+.packages
+.pub-cache/
+.pub/
+/build/
+
+# Android related
+**/android/**/gradle-wrapper.jar
+**/android/.gradle
+**/android/captures/
+**/android/gradlew
+**/android/gradlew.bat
+**/android/local.properties
+**/android/**/GeneratedPluginRegistrant.java
+
+# iOS/XCode related
+**/ios/**/*.mode1v3
+**/ios/**/*.mode2v3
+**/ios/**/*.moved-aside
+**/ios/**/*.pbxuser
+**/ios/**/*.perspectivev3
+**/ios/**/*sync/
+**/ios/**/.sconsign.dblite
+**/ios/**/.tags*
+**/ios/**/.vagrant/
+**/ios/**/DerivedData/
+**/ios/**/Icon?
+**/ios/**/Pods/
+**/ios/**/.symlinks/
+**/ios/**/profile
+**/ios/**/xcuserdata
+**/ios/.generated/
+**/ios/Flutter/App.framework
+**/ios/Flutter/Flutter.framework
+**/ios/Flutter/Generated.xcconfig
+**/ios/Flutter/app.flx
+**/ios/Flutter/app.zip
+**/ios/Flutter/flutter_assets/
+**/ios/ServiceDefinitions.json
+**/ios/Runner/GeneratedPluginRegistrant.*
+
+# Exceptions to above rules.
+!**/ios/**/default.mode1v3
+!**/ios/**/default.mode2v3
+!**/ios/**/default.pbxuser
+!**/ios/**/default.perspectivev3
+!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
diff --git a/client/.metadata b/client/.metadata
new file mode 100644
index 0000000..033ad2a
--- /dev/null
+++ b/client/.metadata
@@ -0,0 +1,10 @@
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled and should not be manually edited.
+
+version:
+ revision: 7a4c33425ddd78c54aba07d86f3f9a4a0051769b
+ channel: stable
+
+project_type: app
diff --git a/client/README.md b/client/README.md
new file mode 100644
index 0000000..06bcb2c
--- /dev/null
+++ b/client/README.md
@@ -0,0 +1,3 @@
+# selfpass_client
+
+This is the multi-platform native Selfpass client built with the Flutter framework.
diff --git a/client/android/app/build.gradle b/client/android/app/build.gradle
new file mode 100644
index 0000000..6ef7ec2
--- /dev/null
+++ b/client/android/app/build.gradle
@@ -0,0 +1,61 @@
+def localProperties = new Properties()
+def localPropertiesFile = rootProject.file('local.properties')
+if (localPropertiesFile.exists()) {
+ localPropertiesFile.withReader('UTF-8') { reader ->
+ localProperties.load(reader)
+ }
+}
+
+def flutterRoot = localProperties.getProperty('flutter.sdk')
+if (flutterRoot == null) {
+ throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
+}
+
+def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
+if (flutterVersionCode == null) {
+ flutterVersionCode = '1'
+}
+
+def flutterVersionName = localProperties.getProperty('flutter.versionName')
+if (flutterVersionName == null) {
+ flutterVersionName = '1.0'
+}
+
+apply plugin: 'com.android.application'
+apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
+
+android {
+ compileSdkVersion 28
+
+ lintOptions {
+ disable 'InvalidPackage'
+ }
+
+ defaultConfig {
+ // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
+ applicationId "com.mjfs.selfpass"
+ minSdkVersion 16
+ targetSdkVersion 28
+ versionCode flutterVersionCode.toInteger()
+ versionName flutterVersionName
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ // TODO: Add your own signing config for the release build.
+ // Signing with the debug keys for now, so `flutter run --release` works.
+ signingConfig signingConfigs.debug
+ }
+ }
+}
+
+flutter {
+ source '../..'
+}
+
+dependencies {
+ testImplementation 'junit:junit:4.12'
+ androidTestImplementation 'com.android.support.test:runner:1.0.2'
+ androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
+}
diff --git a/client/android/app/src/debug/AndroidManifest.xml b/client/android/app/src/debug/AndroidManifest.xml
new file mode 100644
index 0000000..65d8d3b
--- /dev/null
+++ b/client/android/app/src/debug/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/client/android/app/src/main/AndroidManifest.xml b/client/android/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..868e068
--- /dev/null
+++ b/client/android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/client/android/app/src/main/java/com/example/selfpass_mobile/MainActivity.java b/client/android/app/src/main/java/com/example/selfpass_mobile/MainActivity.java
new file mode 100644
index 0000000..f31de72
--- /dev/null
+++ b/client/android/app/src/main/java/com/example/selfpass_mobile/MainActivity.java
@@ -0,0 +1,13 @@
+package com.example.selfpass_mobile;
+
+import android.os.Bundle;
+import io.flutter.app.FlutterActivity;
+import io.flutter.plugins.GeneratedPluginRegistrant;
+
+public class MainActivity extends FlutterActivity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ GeneratedPluginRegistrant.registerWith(this);
+ }
+}
diff --git a/client/android/app/src/main/res/drawable/launch_background.xml b/client/android/app/src/main/res/drawable/launch_background.xml
new file mode 100644
index 0000000..304732f
--- /dev/null
+++ b/client/android/app/src/main/res/drawable/launch_background.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/client/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/client/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..db77bb4
Binary files /dev/null and b/client/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/client/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/client/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..17987b7
Binary files /dev/null and b/client/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/client/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/client/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..09d4391
Binary files /dev/null and b/client/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/client/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/client/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..d5f1c8d
Binary files /dev/null and b/client/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/client/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/client/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..4d6372e
Binary files /dev/null and b/client/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/client/android/app/src/main/res/values/styles.xml b/client/android/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..00fa441
--- /dev/null
+++ b/client/android/app/src/main/res/values/styles.xml
@@ -0,0 +1,8 @@
+
+
+
+
diff --git a/client/android/app/src/profile/AndroidManifest.xml b/client/android/app/src/profile/AndroidManifest.xml
new file mode 100644
index 0000000..65d8d3b
--- /dev/null
+++ b/client/android/app/src/profile/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/client/android/build.gradle b/client/android/build.gradle
new file mode 100644
index 0000000..bb8a303
--- /dev/null
+++ b/client/android/build.gradle
@@ -0,0 +1,29 @@
+buildscript {
+ repositories {
+ google()
+ jcenter()
+ }
+
+ dependencies {
+ classpath 'com.android.tools.build:gradle:3.2.1'
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ jcenter()
+ }
+}
+
+rootProject.buildDir = '../build'
+subprojects {
+ project.buildDir = "${rootProject.buildDir}/${project.name}"
+}
+subprojects {
+ project.evaluationDependsOn(':app')
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
diff --git a/client/android/gradle.properties b/client/android/gradle.properties
new file mode 100644
index 0000000..8bd86f6
--- /dev/null
+++ b/client/android/gradle.properties
@@ -0,0 +1 @@
+org.gradle.jvmargs=-Xmx1536M
diff --git a/client/android/gradle/wrapper/gradle-wrapper.properties b/client/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..2819f02
--- /dev/null
+++ b/client/android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Fri Jun 23 08:50:38 CEST 2017
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip
diff --git a/client/android/settings.gradle b/client/android/settings.gradle
new file mode 100644
index 0000000..5a2f14f
--- /dev/null
+++ b/client/android/settings.gradle
@@ -0,0 +1,15 @@
+include ':app'
+
+def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
+
+def plugins = new Properties()
+def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
+if (pluginsFile.exists()) {
+ pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
+}
+
+plugins.each { name, path ->
+ def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
+ include ":$name"
+ project(":$name").projectDir = pluginDirectory
+}
diff --git a/client/app.png b/client/app.png
new file mode 100644
index 0000000..ee2b41c
Binary files /dev/null and b/client/app.png differ
diff --git a/client/app.xcf b/client/app.xcf
new file mode 100644
index 0000000..628cbea
Binary files /dev/null and b/client/app.xcf differ
diff --git a/client/ios/Flutter/AppFrameworkInfo.plist b/client/ios/Flutter/AppFrameworkInfo.plist
new file mode 100644
index 0000000..9367d48
--- /dev/null
+++ b/client/ios/Flutter/AppFrameworkInfo.plist
@@ -0,0 +1,26 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ App
+ CFBundleIdentifier
+ io.flutter.flutter.app
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ App
+ CFBundlePackageType
+ FMWK
+ CFBundleShortVersionString
+ 1.0
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ 1.0
+ MinimumOSVersion
+ 8.0
+
+
diff --git a/client/ios/Flutter/Debug.xcconfig b/client/ios/Flutter/Debug.xcconfig
new file mode 100644
index 0000000..e8efba1
--- /dev/null
+++ b/client/ios/Flutter/Debug.xcconfig
@@ -0,0 +1,2 @@
+#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
+#include "Generated.xcconfig"
diff --git a/client/ios/Flutter/Release.xcconfig b/client/ios/Flutter/Release.xcconfig
new file mode 100644
index 0000000..399e934
--- /dev/null
+++ b/client/ios/Flutter/Release.xcconfig
@@ -0,0 +1,2 @@
+#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
+#include "Generated.xcconfig"
diff --git a/client/ios/Podfile b/client/ios/Podfile
new file mode 100644
index 0000000..d077b08
--- /dev/null
+++ b/client/ios/Podfile
@@ -0,0 +1,69 @@
+# Uncomment this line to define a global platform for your project
+# platform :ios, '9.0'
+
+# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
+ENV['COCOAPODS_DISABLE_STATS'] = 'true'
+
+project 'Runner', {
+ 'Debug' => :debug,
+ 'Profile' => :release,
+ 'Release' => :release,
+}
+
+def parse_KV_file(file, separator='=')
+ file_abs_path = File.expand_path(file)
+ if !File.exists? file_abs_path
+ return [];
+ end
+ pods_ary = []
+ skip_line_start_symbols = ["#", "/"]
+ File.foreach(file_abs_path) { |line|
+ next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ }
+ plugin = line.split(pattern=separator)
+ if plugin.length == 2
+ podname = plugin[0].strip()
+ path = plugin[1].strip()
+ podpath = File.expand_path("#{path}", file_abs_path)
+ pods_ary.push({:name => podname, :path => podpath});
+ else
+ puts "Invalid plugin specification: #{line}"
+ end
+ }
+ return pods_ary
+end
+
+target 'Runner' do
+ # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock
+ # referring to absolute paths on developers' machines.
+ system('rm -rf .symlinks')
+ system('mkdir -p .symlinks/plugins')
+
+ # Flutter Pods
+ generated_xcode_build_settings = parse_KV_file('./Flutter/Generated.xcconfig')
+ if generated_xcode_build_settings.empty?
+ puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter packages get is executed first."
+ end
+ generated_xcode_build_settings.map { |p|
+ if p[:name] == 'FLUTTER_FRAMEWORK_DIR'
+ symlink = File.join('.symlinks', 'flutter')
+ File.symlink(File.dirname(p[:path]), symlink)
+ pod 'Flutter', :path => File.join(symlink, File.basename(p[:path]))
+ end
+ }
+
+ # Plugin Pods
+ plugin_pods = parse_KV_file('../.flutter-plugins')
+ plugin_pods.map { |p|
+ symlink = File.join('.symlinks', 'plugins', p[:name])
+ File.symlink(p[:path], symlink)
+ pod p[:name], :path => File.join(symlink, 'ios')
+ }
+end
+
+post_install do |installer|
+ installer.pods_project.targets.each do |target|
+ target.build_configurations.each do |config|
+ config.build_settings['ENABLE_BITCODE'] = 'NO'
+ end
+ end
+end
diff --git a/client/ios/Podfile.lock b/client/ios/Podfile.lock
new file mode 100644
index 0000000..557bbc7
--- /dev/null
+++ b/client/ios/Podfile.lock
@@ -0,0 +1,22 @@
+PODS:
+ - Flutter (1.0.0)
+ - flutter_secure_storage (3.2.0):
+ - Flutter
+
+DEPENDENCIES:
+ - Flutter (from `.symlinks/flutter/ios`)
+ - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
+
+EXTERNAL SOURCES:
+ Flutter:
+ :path: ".symlinks/flutter/ios"
+ flutter_secure_storage:
+ :path: ".symlinks/plugins/flutter_secure_storage/ios"
+
+SPEC CHECKSUMS:
+ Flutter: 58dd7d1b27887414a370fcccb9e645c08ffd7a6a
+ flutter_secure_storage: 0c5779648ff644110e507909b77a57e620cbbf8b
+
+PODFILE CHECKSUM: aff02bfeed411c636180d6812254b2daeea14d09
+
+COCOAPODS: 1.7.2
diff --git a/client/ios/Runner.xcodeproj/project.pbxproj b/client/ios/Runner.xcodeproj/project.pbxproj
new file mode 100644
index 0000000..3b9aebf
--- /dev/null
+++ b/client/ios/Runner.xcodeproj/project.pbxproj
@@ -0,0 +1,587 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 46;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
+ 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
+ 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; };
+ 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+ 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; };
+ 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+ 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; };
+ 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; };
+ 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; };
+ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
+ 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
+ 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
+ F8EF57AFFF0C7C353E3604B3 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 097444538E941EBFED1976C8 /* libPods-Runner.a */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXCopyFilesBuildPhase section */
+ 9705A1C41CF9048500538489 /* Embed Frameworks */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = "";
+ dstSubfolderSpec = 10;
+ files = (
+ 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */,
+ 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */,
+ );
+ name = "Embed Frameworks";
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXCopyFilesBuildPhase section */
+
+/* Begin PBXFileReference section */
+ 097444538E941EBFED1976C8 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; };
+ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; };
+ 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; };
+ 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
+ 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; };
+ 4674E99068360BD2489EC50C /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; };
+ 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
+ 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; };
+ 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; };
+ 880DAEE09FC4F07D8ED7F46C /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; };
+ 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; };
+ 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; };
+ 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; };
+ 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; };
+ 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
+ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
+ 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
+ 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ A4F4986515B0C16B78A73BD7 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 97C146EB1CF9000F007C117D /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */,
+ 3B80C3941E831B6300D905FE /* App.framework in Frameworks */,
+ F8EF57AFFF0C7C353E3604B3 /* libPods-Runner.a in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 9740EEB11CF90186004384FC /* Flutter */ = {
+ isa = PBXGroup;
+ children = (
+ 3B80C3931E831B6300D905FE /* App.framework */,
+ 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
+ 9740EEBA1CF902C7004384FC /* Flutter.framework */,
+ 9740EEB21CF90195004384FC /* Debug.xcconfig */,
+ 7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
+ 9740EEB31CF90195004384FC /* Generated.xcconfig */,
+ );
+ name = Flutter;
+ sourceTree = "";
+ };
+ 97C146E51CF9000F007C117D = {
+ isa = PBXGroup;
+ children = (
+ 9740EEB11CF90186004384FC /* Flutter */,
+ 97C146F01CF9000F007C117D /* Runner */,
+ 97C146EF1CF9000F007C117D /* Products */,
+ D52790F7E6421775A2150276 /* Pods */,
+ E491E94631566224F7EB2248 /* Frameworks */,
+ );
+ sourceTree = "";
+ };
+ 97C146EF1CF9000F007C117D /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 97C146EE1CF9000F007C117D /* Runner.app */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ 97C146F01CF9000F007C117D /* Runner */ = {
+ isa = PBXGroup;
+ children = (
+ 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */,
+ 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */,
+ 97C146FA1CF9000F007C117D /* Main.storyboard */,
+ 97C146FD1CF9000F007C117D /* Assets.xcassets */,
+ 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
+ 97C147021CF9000F007C117D /* Info.plist */,
+ 97C146F11CF9000F007C117D /* Supporting Files */,
+ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
+ 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
+ );
+ path = Runner;
+ sourceTree = "";
+ };
+ 97C146F11CF9000F007C117D /* Supporting Files */ = {
+ isa = PBXGroup;
+ children = (
+ 97C146F21CF9000F007C117D /* main.m */,
+ );
+ name = "Supporting Files";
+ sourceTree = "";
+ };
+ D52790F7E6421775A2150276 /* Pods */ = {
+ isa = PBXGroup;
+ children = (
+ 880DAEE09FC4F07D8ED7F46C /* Pods-Runner.debug.xcconfig */,
+ A4F4986515B0C16B78A73BD7 /* Pods-Runner.release.xcconfig */,
+ 4674E99068360BD2489EC50C /* Pods-Runner.profile.xcconfig */,
+ );
+ path = Pods;
+ sourceTree = "";
+ };
+ E491E94631566224F7EB2248 /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ 097444538E941EBFED1976C8 /* libPods-Runner.a */,
+ );
+ name = Frameworks;
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ 97C146ED1CF9000F007C117D /* Runner */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
+ buildPhases = (
+ 0D0C54F13DCFB588B50287A7 /* [CP] Check Pods Manifest.lock */,
+ 9740EEB61CF901F6004384FC /* Run Script */,
+ 97C146EA1CF9000F007C117D /* Sources */,
+ 97C146EB1CF9000F007C117D /* Frameworks */,
+ 97C146EC1CF9000F007C117D /* Resources */,
+ 9705A1C41CF9048500538489 /* Embed Frameworks */,
+ 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
+ 273F25DC0E60392B1958115D /* [CP] Embed Pods Frameworks */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = Runner;
+ productName = Runner;
+ productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
+ productType = "com.apple.product-type.application";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 97C146E61CF9000F007C117D /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ LastUpgradeCheck = 0910;
+ ORGANIZATIONNAME = "The Chromium Authors";
+ TargetAttributes = {
+ 97C146ED1CF9000F007C117D = {
+ CreatedOnToolsVersion = 7.3.1;
+ DevelopmentTeam = D8G6NQ28ZR;
+ ProvisioningStyle = Automatic;
+ };
+ };
+ };
+ buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
+ compatibilityVersion = "Xcode 3.2";
+ developmentRegion = English;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ English,
+ en,
+ Base,
+ );
+ mainGroup = 97C146E51CF9000F007C117D;
+ productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ 97C146ED1CF9000F007C117D /* Runner */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ 97C146EC1CF9000F007C117D /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
+ 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
+ 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */,
+ 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
+ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXShellScriptBuildPhase section */
+ 0D0C54F13DCFB588B50287A7 /* [CP] Check Pods Manifest.lock */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ );
+ inputPaths = (
+ "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+ "${PODS_ROOT}/Manifest.lock",
+ );
+ name = "[CP] Check Pods Manifest.lock";
+ outputFileListPaths = (
+ );
+ outputPaths = (
+ "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
+ showEnvVarsInLog = 0;
+ };
+ 273F25DC0E60392B1958115D /* [CP] Embed Pods Frameworks */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh",
+ "${PODS_ROOT}/../.symlinks/flutter/ios/Flutter.framework",
+ );
+ name = "[CP] Embed Pods Frameworks";
+ outputPaths = (
+ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
+ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ name = "Thin Binary";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin";
+ };
+ 9740EEB61CF901F6004384FC /* Run Script */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ name = "Run Script";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
+ };
+/* End PBXShellScriptBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ 97C146EA1CF9000F007C117D /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */,
+ 97C146F31CF9000F007C117D /* main.m in Sources */,
+ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXVariantGroup section */
+ 97C146FA1CF9000F007C117D /* Main.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 97C146FB1CF9000F007C117D /* Base */,
+ );
+ name = Main.storyboard;
+ sourceTree = "";
+ };
+ 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 97C147001CF9000F007C117D /* Base */,
+ );
+ name = LaunchScreen.storyboard;
+ sourceTree = "";
+ };
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+ 249021D3217E4FDB00AE95B9 /* Profile */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 8.0;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ SDKROOT = iphoneos;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Profile;
+ };
+ 249021D4217E4FDB00AE95B9 /* Profile */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CODE_SIGN_IDENTITY = "iPhone Developer";
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
+ DEVELOPMENT_TEAM = D8G6NQ28ZR;
+ ENABLE_BITCODE = NO;
+ FRAMEWORK_SEARCH_PATHS = (
+ "$(inherited)",
+ "$(PROJECT_DIR)/Flutter",
+ );
+ INFOPLIST_FILE = Runner/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ LIBRARY_SEARCH_PATHS = (
+ "$(inherited)",
+ "$(PROJECT_DIR)/Flutter",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = com.mjfs.selfpass;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ PROVISIONING_PROFILE_SPECIFIER = "";
+ VERSIONING_SYSTEM = "apple-generic";
+ };
+ name = Profile;
+ };
+ 97C147031CF9000F007C117D /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 8.0;
+ MTL_ENABLE_DEBUG_INFO = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ SDKROOT = iphoneos;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ 97C147041CF9000F007C117D /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 8.0;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ SDKROOT = iphoneos;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Release;
+ };
+ 97C147061CF9000F007C117D /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CODE_SIGN_IDENTITY = "iPhone Developer";
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
+ DEVELOPMENT_TEAM = D8G6NQ28ZR;
+ ENABLE_BITCODE = NO;
+ FRAMEWORK_SEARCH_PATHS = (
+ "$(inherited)",
+ "$(PROJECT_DIR)/Flutter",
+ );
+ INFOPLIST_FILE = Runner/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ LIBRARY_SEARCH_PATHS = (
+ "$(inherited)",
+ "$(PROJECT_DIR)/Flutter",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = com.mjfs.selfpass;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ PROVISIONING_PROFILE_SPECIFIER = "";
+ VERSIONING_SYSTEM = "apple-generic";
+ };
+ name = Debug;
+ };
+ 97C147071CF9000F007C117D /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CODE_SIGN_IDENTITY = "iPhone Developer";
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
+ DEVELOPMENT_TEAM = D8G6NQ28ZR;
+ ENABLE_BITCODE = NO;
+ FRAMEWORK_SEARCH_PATHS = (
+ "$(inherited)",
+ "$(PROJECT_DIR)/Flutter",
+ );
+ INFOPLIST_FILE = Runner/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ LIBRARY_SEARCH_PATHS = (
+ "$(inherited)",
+ "$(PROJECT_DIR)/Flutter",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = com.mjfs.selfpass;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ PROVISIONING_PROFILE_SPECIFIER = "";
+ VERSIONING_SYSTEM = "apple-generic";
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 97C147031CF9000F007C117D /* Debug */,
+ 97C147041CF9000F007C117D /* Release */,
+ 249021D3217E4FDB00AE95B9 /* Profile */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 97C147061CF9000F007C117D /* Debug */,
+ 97C147071CF9000F007C117D /* Release */,
+ 249021D4217E4FDB00AE95B9 /* Profile */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = 97C146E61CF9000F007C117D /* Project object */;
+}
diff --git a/client/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/client/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..1d526a1
--- /dev/null
+++ b/client/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/client/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/client/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/client/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/client/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/client/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
new file mode 100644
index 0000000..786d6aa
--- /dev/null
+++ b/client/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/client/ios/Runner.xcworkspace/contents.xcworkspacedata b/client/ios/Runner.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..21a3cc1
--- /dev/null
+++ b/client/ios/Runner.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
diff --git a/client/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/client/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/client/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/client/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/client/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 0000000..949b678
--- /dev/null
+++ b/client/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -0,0 +1,8 @@
+
+
+
+
+ BuildSystemType
+ Original
+
+
diff --git a/client/ios/Runner/AppDelegate.h b/client/ios/Runner/AppDelegate.h
new file mode 100644
index 0000000..36e21bb
--- /dev/null
+++ b/client/ios/Runner/AppDelegate.h
@@ -0,0 +1,6 @@
+#import
+#import
+
+@interface AppDelegate : FlutterAppDelegate
+
+@end
diff --git a/client/ios/Runner/AppDelegate.m b/client/ios/Runner/AppDelegate.m
new file mode 100644
index 0000000..59a72e9
--- /dev/null
+++ b/client/ios/Runner/AppDelegate.m
@@ -0,0 +1,13 @@
+#include "AppDelegate.h"
+#include "GeneratedPluginRegistrant.h"
+
+@implementation AppDelegate
+
+- (BOOL)application:(UIApplication *)application
+ didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
+ [GeneratedPluginRegistrant registerWithRegistry:self];
+ // Override point for customization after application launch.
+ return [super application:application didFinishLaunchingWithOptions:launchOptions];
+}
+
+@end
diff --git a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/1024-1.png b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/1024-1.png
new file mode 100644
index 0000000..381444c
Binary files /dev/null and b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/1024-1.png differ
diff --git a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/1024.png b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/1024.png
new file mode 100755
index 0000000..381444c
Binary files /dev/null and b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/1024.png differ
diff --git a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/114.png b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/114.png
new file mode 100755
index 0000000..7e681ba
Binary files /dev/null and b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/114.png differ
diff --git a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/120.png b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/120.png
new file mode 100755
index 0000000..1d235b7
Binary files /dev/null and b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/120.png differ
diff --git a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/128.png b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/128.png
new file mode 100755
index 0000000..52546f2
Binary files /dev/null and b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/128.png differ
diff --git a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/16.png b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/16.png
new file mode 100755
index 0000000..0caacea
Binary files /dev/null and b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/16.png differ
diff --git a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/180.png b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/180.png
new file mode 100755
index 0000000..e0bb064
Binary files /dev/null and b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/180.png differ
diff --git a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/256.png b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/256.png
new file mode 100755
index 0000000..238e1d2
Binary files /dev/null and b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/256.png differ
diff --git a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/29.png b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/29.png
new file mode 100755
index 0000000..b66b905
Binary files /dev/null and b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/29.png differ
diff --git a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/32.png b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/32.png
new file mode 100755
index 0000000..d84a8ef
Binary files /dev/null and b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/32.png differ
diff --git a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/40.png b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/40.png
new file mode 100755
index 0000000..f02f15e
Binary files /dev/null and b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/40.png differ
diff --git a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/512.png b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/512.png
new file mode 100755
index 0000000..1412c6b
Binary files /dev/null and b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/512.png differ
diff --git a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/57.png b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/57.png
new file mode 100755
index 0000000..b71905b
Binary files /dev/null and b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/57.png differ
diff --git a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/58.png b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/58.png
new file mode 100755
index 0000000..065e97f
Binary files /dev/null and b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/58.png differ
diff --git a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/60.png b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/60.png
new file mode 100755
index 0000000..87baafc
Binary files /dev/null and b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/60.png differ
diff --git a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/64.png b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/64.png
new file mode 100755
index 0000000..fc630f3
Binary files /dev/null and b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/64.png differ
diff --git a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/80.png b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/80.png
new file mode 100755
index 0000000..1ebd450
Binary files /dev/null and b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/80.png differ
diff --git a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/87.png b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/87.png
new file mode 100755
index 0000000..b27c3e6
Binary files /dev/null and b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/87.png differ
diff --git a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100755
index 0000000..75c41f6
--- /dev/null
+++ b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,140 @@
+{
+ "images" : [
+ {
+ "size" : "20x20",
+ "idiom" : "iphone",
+ "filename" : "40.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "20x20",
+ "idiom" : "iphone",
+ "filename" : "60.png",
+ "scale" : "3x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "iphone",
+ "filename" : "29.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "iphone",
+ "filename" : "58.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "iphone",
+ "filename" : "87.png",
+ "scale" : "3x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "iphone",
+ "filename" : "80.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "iphone",
+ "filename" : "120.png",
+ "scale" : "3x"
+ },
+ {
+ "size" : "57x57",
+ "idiom" : "iphone",
+ "filename" : "57.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "57x57",
+ "idiom" : "iphone",
+ "filename" : "114.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "60x60",
+ "idiom" : "iphone",
+ "filename" : "120.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "60x60",
+ "idiom" : "iphone",
+ "filename" : "180.png",
+ "scale" : "3x"
+ },
+ {
+ "size" : "1024x1024",
+ "idiom" : "ios-marketing",
+ "filename" : "1024.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "16x16",
+ "idiom" : "mac",
+ "filename" : "16.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "16x16",
+ "idiom" : "mac",
+ "filename" : "32.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "32x32",
+ "idiom" : "mac",
+ "filename" : "32.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "32x32",
+ "idiom" : "mac",
+ "filename" : "64.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "128x128",
+ "idiom" : "mac",
+ "filename" : "128.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "128x128",
+ "idiom" : "mac",
+ "filename" : "256.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "256x256",
+ "idiom" : "mac",
+ "filename" : "256.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "256x256",
+ "idiom" : "mac",
+ "filename" : "512.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "512x512",
+ "idiom" : "mac",
+ "filename" : "512.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "512x512",
+ "idiom" : "mac",
+ "filename" : "1024-1.png",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/client/ios/Runner/Assets.xcassets/Contents.json b/client/ios/Runner/Assets.xcassets/Contents.json
new file mode 100644
index 0000000..da4a164
--- /dev/null
+++ b/client/ios/Runner/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/client/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/client/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
new file mode 100644
index 0000000..0bedcf2
--- /dev/null
+++ b/client/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "LaunchImage.png",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "filename" : "LaunchImage@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "filename" : "LaunchImage@3x.png",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
diff --git a/client/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/client/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
new file mode 100644
index 0000000..9da19ea
Binary files /dev/null and b/client/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ
diff --git a/client/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/client/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
new file mode 100644
index 0000000..9da19ea
Binary files /dev/null and b/client/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ
diff --git a/client/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/client/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
new file mode 100644
index 0000000..9da19ea
Binary files /dev/null and b/client/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ
diff --git a/client/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/client/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
new file mode 100644
index 0000000..89c2725
--- /dev/null
+++ b/client/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
@@ -0,0 +1,5 @@
+# Launch Screen Assets
+
+You can customize the launch screen with your own desired assets by replacing the image files in this directory.
+
+You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
\ No newline at end of file
diff --git a/client/ios/Runner/Base.lproj/LaunchScreen.storyboard b/client/ios/Runner/Base.lproj/LaunchScreen.storyboard
new file mode 100644
index 0000000..f2e259c
--- /dev/null
+++ b/client/ios/Runner/Base.lproj/LaunchScreen.storyboard
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/client/ios/Runner/Base.lproj/Main.storyboard b/client/ios/Runner/Base.lproj/Main.storyboard
new file mode 100644
index 0000000..f3c2851
--- /dev/null
+++ b/client/ios/Runner/Base.lproj/Main.storyboard
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/client/ios/Runner/Info.plist b/client/ios/Runner/Info.plist
new file mode 100644
index 0000000..d48d92d
--- /dev/null
+++ b/client/ios/Runner/Info.plist
@@ -0,0 +1,47 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleDisplayName
+ Selfpass
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ selfpass_client
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ $(FLUTTER_BUILD_NAME)
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ $(FLUTTER_BUILD_NUMBER)
+ LSRequiresIPhoneOS
+
+ UILaunchStoryboardName
+ LaunchScreen
+ UIMainStoryboardFile
+ Main
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UIViewControllerBasedStatusBarAppearance
+
+
+
diff --git a/client/ios/Runner/main.m b/client/ios/Runner/main.m
new file mode 100644
index 0000000..dff6597
--- /dev/null
+++ b/client/ios/Runner/main.m
@@ -0,0 +1,9 @@
+#import
+#import
+#import "AppDelegate.h"
+
+int main(int argc, char* argv[]) {
+ @autoreleasepool {
+ return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
+ }
+}
diff --git a/client/lib/main.dart b/client/lib/main.dart
new file mode 100644
index 0000000..4c84a88
--- /dev/null
+++ b/client/lib/main.dart
@@ -0,0 +1,78 @@
+import 'package:flutter/cupertino.dart';
+import 'package:provider/provider.dart';
+
+import 'repositories/grpc_credentials_client.dart';
+import 'repositories/secure_storage_config.dart';
+
+import 'screens/authentication.dart';
+import 'screens/credential.dart';
+import 'screens/credentials.dart';
+import 'screens/config.dart';
+import 'screens/home.dart';
+
+import 'types/abstracts.dart';
+import 'types/screen_arguments.dart';
+
+void main() => runApp(Selfpass());
+
+class Selfpass extends StatelessWidget {
+ @override
+ Widget build(BuildContext context) {
+ return Provider(
+ builder: (BuildContext context) => SecureStorageConfig(),
+ child: CupertinoApp(
+ title: 'Selfpass',
+ onGenerateRoute: (RouteSettings settings) {
+ String title;
+ WidgetBuilder builder;
+
+ switch (settings.name) {
+ case '/':
+ title = 'Authentication';
+ builder = (BuildContext context) => Authentication();
+ break;
+
+ case '/home':
+ title = 'Hosts';
+ builder = (BuildContext context) => Provider(
+ builder: (BuildContext context) =>
+ GRPCCredentialsClient.cached(config: settings.arguments),
+ child: Home(),
+ );
+ break;
+
+ case '/credentials':
+ title = 'Credentials';
+ builder = (BuildContext context) => Provider(
+ builder: (BuildContext context) =>
+ GRPCCredentialsClient.cached(),
+ child: Credentials(settings.arguments),
+ );
+ break;
+
+ case '/credential':
+ title = 'Credential';
+ builder = (BuildContext context) => Provider(
+ builder: (BuildContext context) =>
+ GRPCCredentialsClient.cached(),
+ child: Credential(settings.arguments),
+ );
+ break;
+
+ case '/config':
+ final ConfigScreenArguments arguments = settings.arguments == null
+ ? ConfigScreenArguments()
+ : settings.arguments;
+
+ title = 'Configuration';
+ builder = (BuildContext context) =>
+ Config(arguments.connectionConfig, arguments.privateKey);
+ break;
+ }
+
+ return CupertinoPageRoute(builder: builder, title: title);
+ },
+ ),
+ );
+ }
+}
diff --git a/client/lib/protobuf/google/protobuf/timestamp.pb.dart b/client/lib/protobuf/google/protobuf/timestamp.pb.dart
new file mode 100644
index 0000000..4cbf12f
--- /dev/null
+++ b/client/lib/protobuf/google/protobuf/timestamp.pb.dart
@@ -0,0 +1,74 @@
+///
+// Generated code. Do not modify.
+// source: timestamp.proto
+///
+// ignore_for_file: camel_case_types,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name
+
+import 'dart:core' as $core
+ show bool, Deprecated, double, int, List, Map, override, pragma, String;
+
+import 'package:fixnum/fixnum.dart';
+import 'package:protobuf/protobuf.dart' as $pb;
+
+import 'dart:core' as $core show DateTime, Duration;
+
+class Timestamp extends $pb.GeneratedMessage {
+ static final $pb.BuilderInfo _i = $pb.BuilderInfo('Timestamp',
+ package: const $pb.PackageName('google.protobuf'))
+ ..aInt64(1, 'seconds')
+ ..a<$core.int>(2, 'nanos', $pb.PbFieldType.O3)
+ ..hasRequiredFields = false;
+
+ Timestamp._() : super();
+ factory Timestamp() => create();
+ factory Timestamp.fromBuffer($core.List<$core.int> i,
+ [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
+ create()..mergeFromBuffer(i, r);
+ factory Timestamp.fromJson($core.String i,
+ [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
+ create()..mergeFromJson(i, r);
+ Timestamp clone() => Timestamp()..mergeFromMessage(this);
+ Timestamp copyWith(void Function(Timestamp) updates) =>
+ super.copyWith((message) => updates(message as Timestamp));
+ $pb.BuilderInfo get info_ => _i;
+ @$core.pragma('dart2js:noInline')
+ static Timestamp create() => Timestamp._();
+ Timestamp createEmptyInstance() => create();
+ static $pb.PbList createRepeated() => $pb.PbList();
+ static Timestamp getDefault() => _defaultInstance ??= create()..freeze();
+ static Timestamp _defaultInstance;
+
+ Int64 get seconds => $_getI64(0);
+ set seconds(Int64 v) {
+ $_setInt64(0, v);
+ }
+
+ $core.bool hasSeconds() => $_has(0);
+ void clearSeconds() => clearField(1);
+
+ $core.int get nanos => $_get(1, 0);
+ set nanos($core.int v) {
+ $_setSignedInt32(1, v);
+ }
+
+ $core.bool hasNanos() => $_has(1);
+ void clearNanos() => clearField(2);
+
+ /// Converts an instance to [DateTime].
+ ///
+ /// The result is in UTC time zone and has microsecond precision, as
+ /// [DateTime] does not support nanosecond precision.
+ $core.DateTime toDateTime() => $core.DateTime.fromMicrosecondsSinceEpoch(
+ seconds.toInt() * $core.Duration.microsecondsPerSecond + nanos ~/ 1000,
+ isUtc: true);
+
+ /// Creates a new instance from [dateTime].
+ ///
+ /// Time zone information will not be preserved.
+ static Timestamp fromDateTime($core.DateTime dateTime) {
+ $core.int micros = dateTime.microsecondsSinceEpoch;
+ return Timestamp()
+ ..seconds = Int64(micros ~/ $core.Duration.microsecondsPerSecond)
+ ..nanos = (micros % $core.Duration.microsecondsPerSecond).toInt() * 1000;
+ }
+}
diff --git a/client/lib/protobuf/google/protobuf/timestamp.pbenum.dart b/client/lib/protobuf/google/protobuf/timestamp.pbenum.dart
new file mode 100644
index 0000000..fd4e632
--- /dev/null
+++ b/client/lib/protobuf/google/protobuf/timestamp.pbenum.dart
@@ -0,0 +1,6 @@
+///
+// Generated code. Do not modify.
+// source: timestamp.proto
+///
+// ignore_for_file: camel_case_types,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name
+
diff --git a/client/lib/protobuf/google/protobuf/timestamp.pbjson.dart b/client/lib/protobuf/google/protobuf/timestamp.pbjson.dart
new file mode 100644
index 0000000..a072d7e
--- /dev/null
+++ b/client/lib/protobuf/google/protobuf/timestamp.pbjson.dart
@@ -0,0 +1,13 @@
+///
+// Generated code. Do not modify.
+// source: timestamp.proto
+///
+// ignore_for_file: camel_case_types,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name
+
+const Timestamp$json = const {
+ '1': 'Timestamp',
+ '2': const [
+ const {'1': 'seconds', '3': 1, '4': 1, '5': 3, '10': 'seconds'},
+ const {'1': 'nanos', '3': 2, '4': 1, '5': 5, '10': 'nanos'},
+ ],
+};
diff --git a/client/lib/protobuf/service.pb.dart b/client/lib/protobuf/service.pb.dart
new file mode 100644
index 0000000..6fcd5a1
--- /dev/null
+++ b/client/lib/protobuf/service.pb.dart
@@ -0,0 +1,384 @@
+///
+// Generated code. Do not modify.
+// source: service.proto
+///
+// ignore_for_file: camel_case_types,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name
+
+import 'dart:core' as $core show bool, Deprecated, double, int, List, Map, override, pragma, String;
+
+import 'package:protobuf/protobuf.dart' as $pb;
+
+import 'google/protobuf/timestamp.pb.dart' as $1;
+
+class DeleteResponse extends $pb.GeneratedMessage {
+ static final $pb.BuilderInfo _i = $pb.BuilderInfo('DeleteResponse', package: const $pb.PackageName('selfpass.credentials'))
+ ..aOB(1, 'success')
+ ..hasRequiredFields = false
+ ;
+
+ DeleteResponse._() : super();
+ factory DeleteResponse() => create();
+ factory DeleteResponse.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+ factory DeleteResponse.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+ DeleteResponse clone() => DeleteResponse()..mergeFromMessage(this);
+ DeleteResponse copyWith(void Function(DeleteResponse) updates) => super.copyWith((message) => updates(message as DeleteResponse));
+ $pb.BuilderInfo get info_ => _i;
+ @$core.pragma('dart2js:noInline')
+ static DeleteResponse create() => DeleteResponse._();
+ DeleteResponse createEmptyInstance() => create();
+ static $pb.PbList createRepeated() => $pb.PbList();
+ static DeleteResponse getDefault() => _defaultInstance ??= create()..freeze();
+ static DeleteResponse _defaultInstance;
+
+ $core.bool get success => $_get(0, false);
+ set success($core.bool v) { $_setBool(0, v); }
+ $core.bool hasSuccess() => $_has(0);
+ void clearSuccess() => clearField(1);
+}
+
+class GetAllMetadataRequest extends $pb.GeneratedMessage {
+ static final $pb.BuilderInfo _i = $pb.BuilderInfo('GetAllMetadataRequest', package: const $pb.PackageName('selfpass.credentials'))
+ ..aOS(1, 'sourceHost')
+ ..hasRequiredFields = false
+ ;
+
+ GetAllMetadataRequest._() : super();
+ factory GetAllMetadataRequest() => create();
+ factory GetAllMetadataRequest.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+ factory GetAllMetadataRequest.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+ GetAllMetadataRequest clone() => GetAllMetadataRequest()..mergeFromMessage(this);
+ GetAllMetadataRequest copyWith(void Function(GetAllMetadataRequest) updates) => super.copyWith((message) => updates(message as GetAllMetadataRequest));
+ $pb.BuilderInfo get info_ => _i;
+ @$core.pragma('dart2js:noInline')
+ static GetAllMetadataRequest create() => GetAllMetadataRequest._();
+ GetAllMetadataRequest createEmptyInstance() => create();
+ static $pb.PbList createRepeated() => $pb.PbList();
+ static GetAllMetadataRequest getDefault() => _defaultInstance ??= create()..freeze();
+ static GetAllMetadataRequest _defaultInstance;
+
+ $core.String get sourceHost => $_getS(0, '');
+ set sourceHost($core.String v) { $_setString(0, v); }
+ $core.bool hasSourceHost() => $_has(0);
+ void clearSourceHost() => clearField(1);
+}
+
+class IdRequest extends $pb.GeneratedMessage {
+ static final $pb.BuilderInfo _i = $pb.BuilderInfo('IdRequest', package: const $pb.PackageName('selfpass.credentials'))
+ ..aOS(1, 'id')
+ ..hasRequiredFields = false
+ ;
+
+ IdRequest._() : super();
+ factory IdRequest() => create();
+ factory IdRequest.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+ factory IdRequest.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+ IdRequest clone() => IdRequest()..mergeFromMessage(this);
+ IdRequest copyWith(void Function(IdRequest) updates) => super.copyWith((message) => updates(message as IdRequest));
+ $pb.BuilderInfo get info_ => _i;
+ @$core.pragma('dart2js:noInline')
+ static IdRequest create() => IdRequest._();
+ IdRequest createEmptyInstance() => create();
+ static $pb.PbList createRepeated() => $pb.PbList();
+ static IdRequest getDefault() => _defaultInstance ??= create()..freeze();
+ static IdRequest _defaultInstance;
+
+ $core.String get id => $_getS(0, '');
+ set id($core.String v) { $_setString(0, v); }
+ $core.bool hasId() => $_has(0);
+ void clearId() => clearField(1);
+}
+
+class UpdateRequest extends $pb.GeneratedMessage {
+ static final $pb.BuilderInfo _i = $pb.BuilderInfo('UpdateRequest', package: const $pb.PackageName('selfpass.credentials'))
+ ..aOS(1, 'id')
+ ..a(2, 'credential', $pb.PbFieldType.OM, CredentialRequest.getDefault, CredentialRequest.create)
+ ..hasRequiredFields = false
+ ;
+
+ UpdateRequest._() : super();
+ factory UpdateRequest() => create();
+ factory UpdateRequest.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+ factory UpdateRequest.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+ UpdateRequest clone() => UpdateRequest()..mergeFromMessage(this);
+ UpdateRequest copyWith(void Function(UpdateRequest) updates) => super.copyWith((message) => updates(message as UpdateRequest));
+ $pb.BuilderInfo get info_ => _i;
+ @$core.pragma('dart2js:noInline')
+ static UpdateRequest create() => UpdateRequest._();
+ UpdateRequest createEmptyInstance() => create();
+ static $pb.PbList createRepeated() => $pb.PbList();
+ static UpdateRequest getDefault() => _defaultInstance ??= create()..freeze();
+ static UpdateRequest _defaultInstance;
+
+ $core.String get id => $_getS(0, '');
+ set id($core.String v) { $_setString(0, v); }
+ $core.bool hasId() => $_has(0);
+ void clearId() => clearField(1);
+
+ CredentialRequest get credential => $_getN(1);
+ set credential(CredentialRequest v) { setField(2, v); }
+ $core.bool hasCredential() => $_has(1);
+ void clearCredential() => clearField(2);
+}
+
+class DumpResponse extends $pb.GeneratedMessage {
+ static final $pb.BuilderInfo _i = $pb.BuilderInfo('DumpResponse', package: const $pb.PackageName('selfpass.credentials'))
+ ..a<$core.List<$core.int>>(1, 'contents', $pb.PbFieldType.OY)
+ ..hasRequiredFields = false
+ ;
+
+ DumpResponse._() : super();
+ factory DumpResponse() => create();
+ factory DumpResponse.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+ factory DumpResponse.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+ DumpResponse clone() => DumpResponse()..mergeFromMessage(this);
+ DumpResponse copyWith(void Function(DumpResponse) updates) => super.copyWith((message) => updates(message as DumpResponse));
+ $pb.BuilderInfo get info_ => _i;
+ @$core.pragma('dart2js:noInline')
+ static DumpResponse create() => DumpResponse._();
+ DumpResponse createEmptyInstance() => create();
+ static $pb.PbList createRepeated() => $pb.PbList();
+ static DumpResponse getDefault() => _defaultInstance ??= create()..freeze();
+ static DumpResponse _defaultInstance;
+
+ $core.List<$core.int> get contents => $_getN(0);
+ set contents($core.List<$core.int> v) { $_setBytes(0, v); }
+ $core.bool hasContents() => $_has(0);
+ void clearContents() => clearField(1);
+}
+
+class EmptyRequest extends $pb.GeneratedMessage {
+ static final $pb.BuilderInfo _i = $pb.BuilderInfo('EmptyRequest', package: const $pb.PackageName('selfpass.credentials'))
+ ..hasRequiredFields = false
+ ;
+
+ EmptyRequest._() : super();
+ factory EmptyRequest() => create();
+ factory EmptyRequest.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+ factory EmptyRequest.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+ EmptyRequest clone() => EmptyRequest()..mergeFromMessage(this);
+ EmptyRequest copyWith(void Function(EmptyRequest) updates) => super.copyWith((message) => updates(message as EmptyRequest));
+ $pb.BuilderInfo get info_ => _i;
+ @$core.pragma('dart2js:noInline')
+ static EmptyRequest create() => EmptyRequest._();
+ EmptyRequest createEmptyInstance() => create();
+ static $pb.PbList createRepeated() => $pb.PbList();
+ static EmptyRequest getDefault() => _defaultInstance ??= create()..freeze();
+ static EmptyRequest _defaultInstance;
+}
+
+class Metadata extends $pb.GeneratedMessage {
+ static final $pb.BuilderInfo _i = $pb.BuilderInfo('Metadata', package: const $pb.PackageName('selfpass.credentials'))
+ ..aOS(1, 'id')
+ ..a<$1.Timestamp>(2, 'createdAt', $pb.PbFieldType.OM, $1.Timestamp.getDefault, $1.Timestamp.create)
+ ..a<$1.Timestamp>(3, 'updatedAt', $pb.PbFieldType.OM, $1.Timestamp.getDefault, $1.Timestamp.create)
+ ..aOS(4, 'primary')
+ ..aOS(5, 'sourceHost')
+ ..aOS(6, 'loginUrl')
+ ..aOS(7, 'tag')
+ ..hasRequiredFields = false
+ ;
+
+ Metadata._() : super();
+ factory Metadata() => create();
+ factory Metadata.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+ factory Metadata.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+ Metadata clone() => Metadata()..mergeFromMessage(this);
+ Metadata copyWith(void Function(Metadata) updates) => super.copyWith((message) => updates(message as Metadata));
+ $pb.BuilderInfo get info_ => _i;
+ @$core.pragma('dart2js:noInline')
+ static Metadata create() => Metadata._();
+ Metadata createEmptyInstance() => create();
+ static $pb.PbList createRepeated() => $pb.PbList();
+ static Metadata getDefault() => _defaultInstance ??= create()..freeze();
+ static Metadata _defaultInstance;
+
+ $core.String get id => $_getS(0, '');
+ set id($core.String v) { $_setString(0, v); }
+ $core.bool hasId() => $_has(0);
+ void clearId() => clearField(1);
+
+ $1.Timestamp get createdAt => $_getN(1);
+ set createdAt($1.Timestamp v) { setField(2, v); }
+ $core.bool hasCreatedAt() => $_has(1);
+ void clearCreatedAt() => clearField(2);
+
+ $1.Timestamp get updatedAt => $_getN(2);
+ set updatedAt($1.Timestamp v) { setField(3, v); }
+ $core.bool hasUpdatedAt() => $_has(2);
+ void clearUpdatedAt() => clearField(3);
+
+ $core.String get primary => $_getS(3, '');
+ set primary($core.String v) { $_setString(3, v); }
+ $core.bool hasPrimary() => $_has(3);
+ void clearPrimary() => clearField(4);
+
+ $core.String get sourceHost => $_getS(4, '');
+ set sourceHost($core.String v) { $_setString(4, v); }
+ $core.bool hasSourceHost() => $_has(4);
+ void clearSourceHost() => clearField(5);
+
+ $core.String get loginUrl => $_getS(5, '');
+ set loginUrl($core.String v) { $_setString(5, v); }
+ $core.bool hasLoginUrl() => $_has(5);
+ void clearLoginUrl() => clearField(6);
+
+ $core.String get tag => $_getS(6, '');
+ set tag($core.String v) { $_setString(6, v); }
+ $core.bool hasTag() => $_has(6);
+ void clearTag() => clearField(7);
+}
+
+class Credential extends $pb.GeneratedMessage {
+ static final $pb.BuilderInfo _i = $pb.BuilderInfo('Credential', package: const $pb.PackageName('selfpass.credentials'))
+ ..aOS(1, 'id')
+ ..a<$1.Timestamp>(2, 'createdAt', $pb.PbFieldType.OM, $1.Timestamp.getDefault, $1.Timestamp.create)
+ ..a<$1.Timestamp>(3, 'updatedAt', $pb.PbFieldType.OM, $1.Timestamp.getDefault, $1.Timestamp.create)
+ ..aOS(4, 'primary')
+ ..aOS(5, 'username')
+ ..aOS(6, 'email')
+ ..aOS(7, 'password')
+ ..aOS(8, 'sourceHost')
+ ..aOS(9, 'loginUrl')
+ ..aOS(10, 'tag')
+ ..aOS(11, 'otpSecret')
+ ..hasRequiredFields = false
+ ;
+
+ Credential._() : super();
+ factory Credential() => create();
+ factory Credential.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+ factory Credential.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+ Credential clone() => Credential()..mergeFromMessage(this);
+ Credential copyWith(void Function(Credential) updates) => super.copyWith((message) => updates(message as Credential));
+ $pb.BuilderInfo get info_ => _i;
+ @$core.pragma('dart2js:noInline')
+ static Credential create() => Credential._();
+ Credential createEmptyInstance() => create();
+ static $pb.PbList createRepeated() => $pb.PbList();
+ static Credential getDefault() => _defaultInstance ??= create()..freeze();
+ static Credential _defaultInstance;
+
+ $core.String get id => $_getS(0, '');
+ set id($core.String v) { $_setString(0, v); }
+ $core.bool hasId() => $_has(0);
+ void clearId() => clearField(1);
+
+ $1.Timestamp get createdAt => $_getN(1);
+ set createdAt($1.Timestamp v) { setField(2, v); }
+ $core.bool hasCreatedAt() => $_has(1);
+ void clearCreatedAt() => clearField(2);
+
+ $1.Timestamp get updatedAt => $_getN(2);
+ set updatedAt($1.Timestamp v) { setField(3, v); }
+ $core.bool hasUpdatedAt() => $_has(2);
+ void clearUpdatedAt() => clearField(3);
+
+ $core.String get primary => $_getS(3, '');
+ set primary($core.String v) { $_setString(3, v); }
+ $core.bool hasPrimary() => $_has(3);
+ void clearPrimary() => clearField(4);
+
+ $core.String get username => $_getS(4, '');
+ set username($core.String v) { $_setString(4, v); }
+ $core.bool hasUsername() => $_has(4);
+ void clearUsername() => clearField(5);
+
+ $core.String get email => $_getS(5, '');
+ set email($core.String v) { $_setString(5, v); }
+ $core.bool hasEmail() => $_has(5);
+ void clearEmail() => clearField(6);
+
+ $core.String get password => $_getS(6, '');
+ set password($core.String v) { $_setString(6, v); }
+ $core.bool hasPassword() => $_has(6);
+ void clearPassword() => clearField(7);
+
+ $core.String get sourceHost => $_getS(7, '');
+ set sourceHost($core.String v) { $_setString(7, v); }
+ $core.bool hasSourceHost() => $_has(7);
+ void clearSourceHost() => clearField(8);
+
+ $core.String get loginUrl => $_getS(8, '');
+ set loginUrl($core.String v) { $_setString(8, v); }
+ $core.bool hasLoginUrl() => $_has(8);
+ void clearLoginUrl() => clearField(9);
+
+ $core.String get tag => $_getS(9, '');
+ set tag($core.String v) { $_setString(9, v); }
+ $core.bool hasTag() => $_has(9);
+ void clearTag() => clearField(10);
+
+ $core.String get otpSecret => $_getS(10, '');
+ set otpSecret($core.String v) { $_setString(10, v); }
+ $core.bool hasOtpSecret() => $_has(10);
+ void clearOtpSecret() => clearField(11);
+}
+
+class CredentialRequest extends $pb.GeneratedMessage {
+ static final $pb.BuilderInfo _i = $pb.BuilderInfo('CredentialRequest', package: const $pb.PackageName('selfpass.credentials'))
+ ..aOS(1, 'primary')
+ ..aOS(2, 'username')
+ ..aOS(3, 'email')
+ ..aOS(4, 'password')
+ ..aOS(5, 'sourceHost')
+ ..aOS(6, 'loginUrl')
+ ..aOS(7, 'tag')
+ ..aOS(8, 'otpSecret')
+ ..hasRequiredFields = false
+ ;
+
+ CredentialRequest._() : super();
+ factory CredentialRequest() => create();
+ factory CredentialRequest.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+ factory CredentialRequest.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+ CredentialRequest clone() => CredentialRequest()..mergeFromMessage(this);
+ CredentialRequest copyWith(void Function(CredentialRequest) updates) => super.copyWith((message) => updates(message as CredentialRequest));
+ $pb.BuilderInfo get info_ => _i;
+ @$core.pragma('dart2js:noInline')
+ static CredentialRequest create() => CredentialRequest._();
+ CredentialRequest createEmptyInstance() => create();
+ static $pb.PbList createRepeated() => $pb.PbList();
+ static CredentialRequest getDefault() => _defaultInstance ??= create()..freeze();
+ static CredentialRequest _defaultInstance;
+
+ $core.String get primary => $_getS(0, '');
+ set primary($core.String v) { $_setString(0, v); }
+ $core.bool hasPrimary() => $_has(0);
+ void clearPrimary() => clearField(1);
+
+ $core.String get username => $_getS(1, '');
+ set username($core.String v) { $_setString(1, v); }
+ $core.bool hasUsername() => $_has(1);
+ void clearUsername() => clearField(2);
+
+ $core.String get email => $_getS(2, '');
+ set email($core.String v) { $_setString(2, v); }
+ $core.bool hasEmail() => $_has(2);
+ void clearEmail() => clearField(3);
+
+ $core.String get password => $_getS(3, '');
+ set password($core.String v) { $_setString(3, v); }
+ $core.bool hasPassword() => $_has(3);
+ void clearPassword() => clearField(4);
+
+ $core.String get sourceHost => $_getS(4, '');
+ set sourceHost($core.String v) { $_setString(4, v); }
+ $core.bool hasSourceHost() => $_has(4);
+ void clearSourceHost() => clearField(5);
+
+ $core.String get loginUrl => $_getS(5, '');
+ set loginUrl($core.String v) { $_setString(5, v); }
+ $core.bool hasLoginUrl() => $_has(5);
+ void clearLoginUrl() => clearField(6);
+
+ $core.String get tag => $_getS(6, '');
+ set tag($core.String v) { $_setString(6, v); }
+ $core.bool hasTag() => $_has(6);
+ void clearTag() => clearField(7);
+
+ $core.String get otpSecret => $_getS(7, '');
+ set otpSecret($core.String v) { $_setString(7, v); }
+ $core.bool hasOtpSecret() => $_has(7);
+ void clearOtpSecret() => clearField(8);
+}
+
diff --git a/client/lib/protobuf/service.pbenum.dart b/client/lib/protobuf/service.pbenum.dart
new file mode 100644
index 0000000..cf0e02d
--- /dev/null
+++ b/client/lib/protobuf/service.pbenum.dart
@@ -0,0 +1,6 @@
+///
+// Generated code. Do not modify.
+// source: service.proto
+///
+// ignore_for_file: camel_case_types,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name
+
diff --git a/client/lib/protobuf/service.pbgrpc.dart b/client/lib/protobuf/service.pbgrpc.dart
new file mode 100644
index 0000000..40a8c1f
--- /dev/null
+++ b/client/lib/protobuf/service.pbgrpc.dart
@@ -0,0 +1,158 @@
+///
+// Generated code. Do not modify.
+// source: service.proto
+///
+// ignore_for_file: camel_case_types,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name
+
+import 'dart:async' as $async;
+
+import 'dart:core' as $core show int, String, List;
+
+import 'package:grpc/service_api.dart' as $grpc;
+import 'service.pb.dart' as $0;
+export 'service.pb.dart';
+
+class CredentialServiceClient extends $grpc.Client {
+ static final _$getAllMetadata =
+ $grpc.ClientMethod<$0.GetAllMetadataRequest, $0.Metadata>(
+ '/selfpass.credentials.CredentialService/GetAllMetadata',
+ ($0.GetAllMetadataRequest value) => value.writeToBuffer(),
+ ($core.List<$core.int> value) => $0.Metadata.fromBuffer(value));
+ static final _$get = $grpc.ClientMethod<$0.IdRequest, $0.Credential>(
+ '/selfpass.credentials.CredentialService/Get',
+ ($0.IdRequest value) => value.writeToBuffer(),
+ ($core.List<$core.int> value) => $0.Credential.fromBuffer(value));
+ static final _$create =
+ $grpc.ClientMethod<$0.CredentialRequest, $0.Credential>(
+ '/selfpass.credentials.CredentialService/Create',
+ ($0.CredentialRequest value) => value.writeToBuffer(),
+ ($core.List<$core.int> value) => $0.Credential.fromBuffer(value));
+ static final _$update = $grpc.ClientMethod<$0.UpdateRequest, $0.Credential>(
+ '/selfpass.credentials.CredentialService/Update',
+ ($0.UpdateRequest value) => value.writeToBuffer(),
+ ($core.List<$core.int> value) => $0.Credential.fromBuffer(value));
+ static final _$delete = $grpc.ClientMethod<$0.IdRequest, $0.DeleteResponse>(
+ '/selfpass.credentials.CredentialService/Delete',
+ ($0.IdRequest value) => value.writeToBuffer(),
+ ($core.List<$core.int> value) => $0.DeleteResponse.fromBuffer(value));
+
+ CredentialServiceClient($grpc.ClientChannel channel,
+ {$grpc.CallOptions options})
+ : super(channel, options: options);
+
+ $grpc.ResponseStream<$0.Metadata> getAllMetadata(
+ $0.GetAllMetadataRequest request,
+ {$grpc.CallOptions options}) {
+ final call = $createCall(
+ _$getAllMetadata, $async.Stream.fromIterable([request]),
+ options: options);
+ return $grpc.ResponseStream(call);
+ }
+
+ $grpc.ResponseFuture<$0.Credential> get($0.IdRequest request,
+ {$grpc.CallOptions options}) {
+ final call = $createCall(_$get, $async.Stream.fromIterable([request]),
+ options: options);
+ return $grpc.ResponseFuture(call);
+ }
+
+ $grpc.ResponseFuture<$0.Credential> create($0.CredentialRequest request,
+ {$grpc.CallOptions options}) {
+ final call = $createCall(_$create, $async.Stream.fromIterable([request]),
+ options: options);
+ return $grpc.ResponseFuture(call);
+ }
+
+ $grpc.ResponseFuture<$0.Credential> update($0.UpdateRequest request,
+ {$grpc.CallOptions options}) {
+ final call = $createCall(_$update, $async.Stream.fromIterable([request]),
+ options: options);
+ return $grpc.ResponseFuture(call);
+ }
+
+ $grpc.ResponseFuture<$0.DeleteResponse> delete($0.IdRequest request,
+ {$grpc.CallOptions options}) {
+ final call = $createCall(_$delete, $async.Stream.fromIterable([request]),
+ options: options);
+ return $grpc.ResponseFuture(call);
+ }
+}
+
+abstract class CredentialServiceBase extends $grpc.Service {
+ $core.String get $name => 'selfpass.credentials.CredentialService';
+
+ CredentialServiceBase() {
+ $addMethod($grpc.ServiceMethod<$0.GetAllMetadataRequest, $0.Metadata>(
+ 'GetAllMetadata',
+ getAllMetadata_Pre,
+ false,
+ true,
+ ($core.List<$core.int> value) =>
+ $0.GetAllMetadataRequest.fromBuffer(value),
+ ($0.Metadata value) => value.writeToBuffer()));
+ $addMethod($grpc.ServiceMethod<$0.IdRequest, $0.Credential>(
+ 'Get',
+ get_Pre,
+ false,
+ false,
+ ($core.List<$core.int> value) => $0.IdRequest.fromBuffer(value),
+ ($0.Credential value) => value.writeToBuffer()));
+ $addMethod($grpc.ServiceMethod<$0.CredentialRequest, $0.Credential>(
+ 'Create',
+ create_Pre,
+ false,
+ false,
+ ($core.List<$core.int> value) => $0.CredentialRequest.fromBuffer(value),
+ ($0.Credential value) => value.writeToBuffer()));
+ $addMethod($grpc.ServiceMethod<$0.UpdateRequest, $0.Credential>(
+ 'Update',
+ update_Pre,
+ false,
+ false,
+ ($core.List<$core.int> value) => $0.UpdateRequest.fromBuffer(value),
+ ($0.Credential value) => value.writeToBuffer()));
+ $addMethod($grpc.ServiceMethod<$0.IdRequest, $0.DeleteResponse>(
+ 'Delete',
+ delete_Pre,
+ false,
+ false,
+ ($core.List<$core.int> value) => $0.IdRequest.fromBuffer(value),
+ ($0.DeleteResponse value) => value.writeToBuffer()));
+ }
+
+ $async.Stream<$0.Metadata> getAllMetadata_Pre(
+ $grpc.ServiceCall call, $async.Future request) async* {
+ yield* getAllMetadata(call, (await request) as $0.GetAllMetadataRequest);
+ }
+
+ $async.Future<$0.Credential> get_Pre(
+ $grpc.ServiceCall call, $async.Future request) async {
+ return get(call, await request);
+ }
+
+ $async.Future<$0.Credential> create_Pre(
+ $grpc.ServiceCall call, $async.Future request) async {
+ return create(call, await request);
+ }
+
+ $async.Future<$0.Credential> update_Pre(
+ $grpc.ServiceCall call, $async.Future request) async {
+ return update(call, await request);
+ }
+
+ $async.Future<$0.DeleteResponse> delete_Pre(
+ $grpc.ServiceCall call, $async.Future request) async {
+ return delete(call, await request);
+ }
+
+ $async.Stream<$0.Metadata> getAllMetadata(
+ $grpc.ServiceCall call, $0.GetAllMetadataRequest request);
+ $async.Future<$0.Credential> get(
+ $grpc.ServiceCall call, $0.IdRequest request);
+ $async.Future<$0.Credential> create(
+ $grpc.ServiceCall call, $0.CredentialRequest request);
+ $async.Future<$0.Credential> update(
+ $grpc.ServiceCall call, $0.UpdateRequest request);
+ $async.Future<$0.DeleteResponse> delete(
+ $grpc.ServiceCall call, $0.IdRequest request);
+}
diff --git a/client/lib/protobuf/service.pbjson.dart b/client/lib/protobuf/service.pbjson.dart
new file mode 100644
index 0000000..0cfafe3
--- /dev/null
+++ b/client/lib/protobuf/service.pbjson.dart
@@ -0,0 +1,124 @@
+///
+// Generated code. Do not modify.
+// source: service.proto
+///
+// ignore_for_file: camel_case_types,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name
+
+const DeleteResponse$json = const {
+ '1': 'DeleteResponse',
+ '2': const [
+ const {'1': 'success', '3': 1, '4': 1, '5': 8, '10': 'success'},
+ ],
+};
+
+const GetAllMetadataRequest$json = const {
+ '1': 'GetAllMetadataRequest',
+ '2': const [
+ const {'1': 'source_host', '3': 1, '4': 1, '5': 9, '10': 'sourceHost'},
+ ],
+};
+
+const IdRequest$json = const {
+ '1': 'IdRequest',
+ '2': const [
+ const {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'},
+ ],
+};
+
+const UpdateRequest$json = const {
+ '1': 'UpdateRequest',
+ '2': const [
+ const {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'},
+ const {
+ '1': 'credential',
+ '3': 2,
+ '4': 1,
+ '5': 11,
+ '6': '.selfpass.credentials.CredentialRequest',
+ '10': 'credential'
+ },
+ ],
+};
+
+const DumpResponse$json = const {
+ '1': 'DumpResponse',
+ '2': const [
+ const {'1': 'contents', '3': 1, '4': 1, '5': 12, '10': 'contents'},
+ ],
+};
+
+const EmptyRequest$json = const {
+ '1': 'EmptyRequest',
+};
+
+const Metadata$json = const {
+ '1': 'Metadata',
+ '2': const [
+ const {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'},
+ const {
+ '1': 'created_at',
+ '3': 2,
+ '4': 1,
+ '5': 11,
+ '6': '.google.protobuf.Timestamp',
+ '10': 'createdAt'
+ },
+ const {
+ '1': 'updated_at',
+ '3': 3,
+ '4': 1,
+ '5': 11,
+ '6': '.google.protobuf.Timestamp',
+ '10': 'updatedAt'
+ },
+ const {'1': 'primary', '3': 4, '4': 1, '5': 9, '10': 'primary'},
+ const {'1': 'source_host', '3': 5, '4': 1, '5': 9, '10': 'sourceHost'},
+ const {'1': 'login_url', '3': 6, '4': 1, '5': 9, '10': 'loginUrl'},
+ const {'1': 'tag', '3': 7, '4': 1, '5': 9, '10': 'tag'},
+ ],
+};
+
+const Credential$json = const {
+ '1': 'Credential',
+ '2': const [
+ const {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'},
+ const {
+ '1': 'created_at',
+ '3': 2,
+ '4': 1,
+ '5': 11,
+ '6': '.google.protobuf.Timestamp',
+ '10': 'createdAt'
+ },
+ const {
+ '1': 'updated_at',
+ '3': 3,
+ '4': 1,
+ '5': 11,
+ '6': '.google.protobuf.Timestamp',
+ '10': 'updatedAt'
+ },
+ const {'1': 'primary', '3': 4, '4': 1, '5': 9, '10': 'primary'},
+ const {'1': 'username', '3': 5, '4': 1, '5': 9, '10': 'username'},
+ const {'1': 'email', '3': 6, '4': 1, '5': 9, '10': 'email'},
+ const {'1': 'password', '3': 7, '4': 1, '5': 9, '10': 'password'},
+ const {'1': 'source_host', '3': 8, '4': 1, '5': 9, '10': 'sourceHost'},
+ const {'1': 'login_url', '3': 9, '4': 1, '5': 9, '10': 'loginUrl'},
+ const {'1': 'tag', '3': 10, '4': 1, '5': 9, '10': 'tag'},
+ const {'1': 'otp_secret', '3': 11, '4': 1, '5': 9, '10': 'otpSecret'},
+ ],
+};
+
+const CredentialRequest$json = const {
+ '1': 'CredentialRequest',
+ '2': const [
+ const {'1': 'primary', '3': 1, '4': 1, '5': 9, '10': 'primary'},
+ const {'1': 'username', '3': 2, '4': 1, '5': 9, '10': 'username'},
+ const {'1': 'email', '3': 3, '4': 1, '5': 9, '10': 'email'},
+ const {'1': 'password', '3': 4, '4': 1, '5': 9, '10': 'password'},
+ const {'1': 'source_host', '3': 5, '4': 1, '5': 9, '10': 'sourceHost'},
+ const {'1': 'login_url', '3': 6, '4': 1, '5': 9, '10': 'loginUrl'},
+ const {'1': 'tag', '3': 7, '4': 1, '5': 9, '10': 'tag'},
+ const {'1': 'otp_secret', '3': 8, '4': 1, '5': 9, '10': 'otpSecret'},
+ ],
+};
diff --git a/client/lib/protobuf/service.proto b/client/lib/protobuf/service.proto
new file mode 100644
index 0000000..c9dfa49
--- /dev/null
+++ b/client/lib/protobuf/service.proto
@@ -0,0 +1,75 @@
+syntax = "proto3";
+
+package selfpass.credentials;
+
+option go_package = "protobuf";
+
+import "google/protobuf/timestamp.proto";
+
+service CredentialService {
+ rpc GetAllMetadata (GetAllMetadataRequest) returns (stream Metadata);
+ rpc Get (IdRequest) returns (Credential);
+ rpc Create (CredentialRequest) returns (Credential);
+ rpc Update (UpdateRequest) returns (Credential);
+ rpc Delete (IdRequest) returns (DeleteResponse);
+ // rpc Dump (EmptyRequest) returns (DumpResponse);
+}
+
+message DeleteResponse {
+ bool success = 1;
+}
+
+message GetAllMetadataRequest {
+ string source_host = 1;
+}
+
+message IdRequest {
+ string id = 1;
+}
+
+message UpdateRequest {
+ string id = 1;
+ CredentialRequest credential = 2;
+}
+
+message DumpResponse {
+ bytes contents = 1;
+}
+
+message EmptyRequest {
+}
+
+message Metadata {
+ string id = 1;
+ google.protobuf.Timestamp created_at = 2;
+ google.protobuf.Timestamp updated_at = 3;
+ string primary = 4;
+ string source_host = 5;
+ string login_url = 6;
+ string tag = 7;
+}
+
+message Credential {
+ string id = 1;
+ google.protobuf.Timestamp created_at = 2;
+ google.protobuf.Timestamp updated_at = 3;
+ string primary = 4;
+ string username = 5;
+ string email = 6;
+ string password = 7;
+ string source_host = 8;
+ string login_url = 9;
+ string tag = 10;
+ string otp_secret = 11;
+}
+
+message CredentialRequest {
+ string primary = 1;
+ string username = 2;
+ string email = 3;
+ string password = 4;
+ string source_host = 5;
+ string login_url = 6;
+ string tag = 7;
+ string otp_secret = 8;
+}
diff --git a/client/lib/repositories/grpc_credentials_client.dart b/client/lib/repositories/grpc_credentials_client.dart
new file mode 100644
index 0000000..df1811f
--- /dev/null
+++ b/client/lib/repositories/grpc_credentials_client.dart
@@ -0,0 +1,83 @@
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:grpc/grpc.dart';
+
+import '../protobuf/service.pbgrpc.dart' as grpc;
+import '../protobuf/service.pb.dart' as protobuf;
+
+import '../types/abstracts.dart';
+import '../types/connection_config.dart';
+import '../types/credential.dart';
+
+class GRPCCredentialsClient implements CredentialsRepo {
+ static GRPCCredentialsClient _cached;
+ grpc.CredentialServiceClient _client;
+
+ GRPCCredentialsClient(ConnectionConfig config) {
+ final caCert = utf8.encode(config.caCertificate);
+ final cert = utf8.encode(config.certificate);
+ final privateCert = utf8.encode(config.privateCertificate);
+
+ final splitHost = config.host.split(':');
+ final hostname = splitHost[0];
+ final port = int.parse(splitHost[1]);
+
+ _client = grpc.CredentialServiceClient(ClientChannel(
+ hostname,
+ port: port,
+ options: ChannelOptions(
+ credentials: _ChannelCredentials(caCert, cert, privateCert),
+ ),
+ ));
+ }
+
+ factory GRPCCredentialsClient.cached({ConnectionConfig config}) =>
+ config == null ? _cached : _cached = GRPCCredentialsClient(config);
+
+ Stream getAllMetadata(String sourceHost) {
+ final request = grpc.GetAllMetadataRequest();
+ request.sourceHost = sourceHost;
+
+ return _client.getAllMetadata(request).map(
+ (protobuf.Metadata pbMetadata) => Metadata.fromProtobuf(pbMetadata));
+ }
+
+ Future get(String id) async {
+ final request = grpc.IdRequest();
+ request.id = id;
+
+ return Credential.fromProtobuf(await _client.get(request));
+ }
+
+ Future create(CredentialInput input) async {
+ return Credential();
+ }
+
+ Future update(String id, CredentialInput input) async {
+ return Credential();
+ }
+
+ Future delete(String id) {
+ final request = grpc.IdRequest();
+ request.id = id;
+
+ return _client.delete(request);
+ }
+}
+
+class _ChannelCredentials extends ChannelCredentials {
+ final List _key;
+ final List _cert;
+
+ const _ChannelCredentials(List caCert, this._cert, this._key)
+ : super.secure(certificates: caCert);
+
+ @override
+ SecurityContext get securityContext {
+ return super.securityContext
+ ..usePrivateKeyBytes(_key)
+ ..useCertificateChainBytes(_cert);
+ }
+}
diff --git a/client/lib/repositories/secure_storage_config.dart b/client/lib/repositories/secure_storage_config.dart
new file mode 100644
index 0000000..9d9c4cf
--- /dev/null
+++ b/client/lib/repositories/secure_storage_config.dart
@@ -0,0 +1,91 @@
+import 'dart:convert';
+
+import 'package:flutter_secure_storage/flutter_secure_storage.dart';
+
+import '../types/abstracts.dart';
+import '../types/connection_config.dart';
+
+import '../utils/crypto.dart' as crypto;
+
+class SecureStorageConfig implements ConfigRepo {
+ static const _keyPrivateKey = "private_key";
+ static const _keyConnectionConfig = "connection_config";
+ static const _keyPassword = "password";
+
+ final _storage = FlutterSecureStorage();
+
+ bool _passwordMatched = false;
+ String _password;
+
+ String get password {
+ _checkIfPasswordMatched();
+ return _password;
+ }
+
+ Future setPrivateKey(String key) {
+ _checkIfPasswordMatched();
+ return _storage.write(key: _keyPrivateKey, value: key.replaceAll('-', ''));
+ }
+
+ Future get privateKey {
+ _checkIfPasswordMatched();
+ return _storage.read(key: _keyPrivateKey);
+ }
+
+ Future setPassword(String password) {
+ _checkIfPasswordMatched();
+
+ _password = password;
+
+ return _storage.write(
+ key: _keyPassword, value: crypto.hashPassword(password));
+ }
+
+ Future get passwordIsSet async {
+ final passHash = await _storage.read(key: _keyPassword);
+
+ if (passHash != null) {
+ return true;
+ }
+
+ _passwordMatched = true;
+
+ return false;
+ }
+
+ Future matchesPasswordHash(String password) async {
+ _passwordMatched = crypto.matchHashedPassword(
+ await _storage.read(key: _keyPassword), password);
+
+ if (_passwordMatched) _password = password;
+
+ return _passwordMatched;
+ }
+
+ Future setConnectionConfig(ConnectionConfig config) {
+ _checkIfPasswordMatched();
+ return _storage.write(
+ key: _keyConnectionConfig, value: json.encode(config));
+ }
+
+ Future get connectionConfig async {
+ _checkIfPasswordMatched();
+ final connConfig = await _storage.read(key: _keyConnectionConfig);
+
+ if (connConfig == null) {
+ return null;
+ }
+
+ return ConnectionConfig.fromJson(json.decode(connConfig));
+ }
+
+ Future deleteAll() {
+ _checkIfPasswordMatched();
+ return _storage.deleteAll();
+ }
+
+ void _checkIfPasswordMatched() {
+ if (_passwordMatched) return;
+ throw Exception('password not matched yet');
+ }
+}
diff --git a/client/lib/screens/authentication.dart b/client/lib/screens/authentication.dart
new file mode 100644
index 0000000..f2da82f
--- /dev/null
+++ b/client/lib/screens/authentication.dart
@@ -0,0 +1,129 @@
+import 'package:flutter/cupertino.dart';
+import 'package:provider/provider.dart';
+
+import '../types/abstracts.dart';
+
+import '../widgets/text_field.dart';
+
+class Authentication extends StatefulWidget {
+ @override
+ _AuthenticationState createState() => _AuthenticationState();
+}
+
+class _AuthenticationState extends State {
+ final TextEditingController _passwordController = TextEditingController();
+ final TextEditingController _confirmController = TextEditingController();
+ bool _invalid = false;
+ bool _passesDontMatch = false;
+ ConfigRepo _config;
+ Future _passwordIsSet;
+
+ @override
+ didChangeDependencies() {
+ super.didChangeDependencies();
+ _config = Provider.of(context);
+ _passwordIsSet = _config.passwordIsSet;
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return CupertinoPageScaffold(
+ child: Container(
+ margin: const EdgeInsets.symmetric(horizontal: 50.0),
+ child: FutureBuilder(
+ future: _passwordIsSet,
+ builder: (BuildContext context, AsyncSnapshot snapshot) =>
+ snapshot.connectionState == ConnectionState.done
+ ? Column(
+ children: _buildColumnChildren(context, snapshot.data))
+ : Center(child: CupertinoActivityIndicator()),
+ ),
+ ),
+ );
+ }
+
+ List _buildColumnChildren(BuildContext context, bool passwordIsSet) {
+ List children = [
+ const Spacer(flex: 4),
+ const Flexible(child: Text('Master password:')),
+ Flexible(
+ child: TextField(
+ maxLines: 1,
+ obscure: true,
+ autofocus: true,
+ controller: _passwordController,
+ ),
+ ),
+ ];
+
+ if (!passwordIsSet) {
+ children.add(const Flexible(child: Text('Re-enter password:')));
+ children.add(Flexible(
+ child: TextField(
+ maxLines: 1,
+ obscure: true,
+ controller: _confirmController,
+ ),
+ ));
+ }
+
+ if (_invalid) {
+ children.add(const Flexible(
+ child: Text(
+ 'invalid masterpass',
+ style: TextStyle(color: CupertinoColors.destructiveRed),
+ ),
+ ));
+ }
+
+ if (_passesDontMatch) {
+ children.add(const Flexible(
+ child: Text(
+ 'passwords don\'t match',
+ style: TextStyle(color: CupertinoColors.destructiveRed),
+ ),
+ ));
+ }
+
+ children.add(CupertinoButton(
+ child: Text('Enter'),
+ onPressed: _buildEnterPressedBuilder(context),
+ ));
+
+ if (_passesDontMatch) {
+ children.add(const Spacer(flex: 1));
+ } else if (_invalid || !passwordIsSet) {
+ children.add(const Spacer(flex: 2));
+ } else {
+ children.add(const Spacer(flex: 3));
+ }
+
+ return children;
+ }
+
+ VoidCallback _buildEnterPressedBuilder(BuildContext context) {
+ return () async {
+ if (await _passwordIsSet) {
+ if (await _config.matchesPasswordHash(_passwordController.text)) {
+ Navigator.of(context).pushReplacementNamed('/home',
+ arguments: await _config.connectionConfig);
+ return;
+ }
+
+ this.setState(() => _invalid = true);
+ return;
+ }
+
+ if (_passwordController.text != _confirmController.text) {
+ this.setState(() {
+ _passesDontMatch = true;
+ });
+ return;
+ }
+
+ _config.setPassword(_passwordController.text);
+ _passwordIsSet = Future.value(true);
+ Navigator.of(context).pushReplacementNamed('/config');
+ };
+ }
+}
diff --git a/client/lib/screens/config.dart b/client/lib/screens/config.dart
new file mode 100644
index 0000000..58b7929
--- /dev/null
+++ b/client/lib/screens/config.dart
@@ -0,0 +1,135 @@
+import 'package:flutter/cupertino.dart';
+import 'package:provider/provider.dart';
+
+import '../types/abstracts.dart';
+import '../types/connection_config.dart';
+
+import '../widgets/text_field.dart';
+
+class Config extends StatefulWidget {
+ final ConnectionConfig connectionConfig;
+ final String privateKey;
+
+ const Config(this.connectionConfig, this.privateKey, {Key key})
+ : super(key: key);
+
+ @override
+ State createState() => _ConfigState(this.connectionConfig, this.privateKey);
+}
+
+class _ConfigState extends State {
+ TextEditingController _hostController;
+ TextEditingController _caCertController;
+ TextEditingController _certController;
+ TextEditingController _privateCertController;
+ TextEditingController _privateKeyController;
+ ConnectionConfig _connectionConfig;
+ String _privateKey;
+ ConfigRepo _config;
+
+ _ConfigState(this._connectionConfig, this._privateKey) {
+ if (_connectionConfig == null) {
+ _connectionConfig = ConnectionConfig();
+ }
+
+ _hostController = TextEditingController(text: _connectionConfig.host);
+ _certController =
+ TextEditingController(text: _connectionConfig.certificate);
+ _caCertController =
+ TextEditingController(text: _connectionConfig.caCertificate);
+ _privateCertController =
+ TextEditingController(text: _connectionConfig.privateCertificate);
+
+ _privateKeyController = TextEditingController(text: _privateKey);
+ }
+
+ @override
+ didChangeDependencies() async {
+ super.didChangeDependencies();
+
+ _config = Provider.of(context);
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return CupertinoPageScaffold(
+ navigationBar: _connectionConfig.host == null
+ ? null
+ : CupertinoNavigationBar(
+ trailing: GestureDetector(
+ onTap: _buildResetAllHandler(context),
+ child: Text('Reset App',
+ style: TextStyle(color: CupertinoColors.destructiveRed)),
+ ),
+ ),
+ child: Container(
+ margin: const EdgeInsets.symmetric(horizontal: 30),
+ child: ListView(children: [
+ Container(margin: EdgeInsets.only(top: 10), child: Text('Host:')),
+ TextField(maxLines: 1, controller: _hostController),
+ Container(
+ margin: EdgeInsets.only(top: 5), child: Text('Private key:')),
+ TextField(maxLines: 1, controller: _privateKeyController),
+ Container(
+ margin: EdgeInsets.only(top: 5), child: Text('CA certificate:')),
+ TextField(maxLines: 5, controller: _caCertController),
+ Container(
+ margin: EdgeInsets.only(top: 5),
+ child: Text('Client certificate:')),
+ TextField(maxLines: 5, controller: _certController),
+ Container(
+ margin: EdgeInsets.only(top: 5),
+ child: Text('Private certificate:')),
+ TextField(maxLines: 5, controller: _privateCertController),
+ CupertinoButton(
+ child: Text('Save'), onPressed: _makeSaveOnPressed(context)),
+ ]),
+ ),
+ );
+ }
+
+ GestureTapCallback _buildResetAllHandler(BuildContext context) {
+ return () {
+ showCupertinoDialog(
+ context: context,
+ builder: (BuildContext context) => CupertinoAlertDialog(
+ content: Text('Are you sure?'),
+ actions: [
+ CupertinoDialogAction(
+ isDefaultAction: true,
+ child: Text('Cancel'),
+ onPressed: () => Navigator.of(context).pop(),
+ ),
+ CupertinoDialogAction(
+ isDestructiveAction: true,
+ child: Text('Confirm'),
+ onPressed: () async {
+ _connectionConfig = null;
+ await _config.deleteAll();
+ Navigator.of(context)
+ .pushNamedAndRemoveUntil('/', ModalRoute.withName('/'));
+ },
+ ),
+ ],
+ ),
+ );
+ };
+ }
+
+ VoidCallback _makeSaveOnPressed(BuildContext context) {
+ return () async {
+ final connConfig = ConnectionConfig(
+ host: _hostController.text,
+ certificate: _certController.text,
+ caCertificate: _caCertController.text,
+ privateCertificate: _privateCertController.text,
+ );
+
+ await _config.setConnectionConfig(connConfig);
+ await _config.setPrivateKey(_privateKeyController.text);
+
+ Navigator.of(context)
+ .pushReplacementNamed('/home', arguments: connConfig);
+ };
+ }
+}
diff --git a/client/lib/screens/credential.dart b/client/lib/screens/credential.dart
new file mode 100644
index 0000000..74f05af
--- /dev/null
+++ b/client/lib/screens/credential.dart
@@ -0,0 +1,177 @@
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/services.dart';
+import 'package:otp/otp.dart';
+
+import '../types/credential.dart' as types;
+
+import '../widgets/text_field.dart';
+
+class Credential extends StatefulWidget {
+ final types.Credential credential;
+
+ const Credential(this.credential, {Key key}) : super(key: key);
+
+ @override
+ State createState() => _CredentialState(credential);
+}
+
+class _CredentialState extends State {
+ _CredentialControllers _controllers;
+ Map _fieldMap;
+ types.Credential _credential;
+
+ _CredentialState(this._credential) : super();
+
+ @override
+ Widget build(BuildContext context) {
+ return CupertinoPageScaffold(
+ navigationBar: CupertinoNavigationBar(),
+ child: Container(
+ padding: const EdgeInsets.only(top: 15, bottom: 30, left: 30),
+ child: ListView(
+ children: _buildFieldRows(context),
+ ),
+ ),
+ );
+ }
+
+ Map _buildFieldMap(
+ _CredentialControllers controllers,
+ types.Credential credential,
+ ) {
+ final fieldMap = {
+ 'Id:': _FieldBuildConfig(mutable: false, text: credential.meta.id),
+ 'Created:': _FieldBuildConfig(
+ mutable: false,
+ copyable: false,
+ text: credential.meta.createdAt.toString(),
+ ),
+ 'Updated:': _FieldBuildConfig(
+ mutable: false,
+ copyable: false,
+ text: credential.meta.updatedAt.toString(),
+ ),
+ 'Host:': _FieldBuildConfig(controller: controllers.sourceHost),
+ 'Primary:': _FieldBuildConfig(controller: controllers.primary),
+ };
+
+ if (credential.meta.tag != null && credential.meta.tag != '') {
+ fieldMap['Tag'] = _FieldBuildConfig(controller: controllers.tag);
+ }
+
+ if (credential.username != null && credential.username != '') {
+ fieldMap['User:'] = _FieldBuildConfig(controller: controllers.username);
+ }
+
+ if (credential.email != null && credential.email != '') {
+ fieldMap['Email:'] = _FieldBuildConfig(controller: controllers.email);
+ }
+
+ fieldMap['Password:'] =
+ _FieldBuildConfig(controller: controllers.password, obscured: true);
+
+ if (credential.otpSecret != null && credential.otpSecret != '') {
+ fieldMap['OTP Secret:'] = _FieldBuildConfig(
+ controller: controllers.otpSecret, obscured: true, otp: true);
+ }
+
+ return fieldMap;
+ }
+
+ List _buildFieldRows(BuildContext context) {
+ List rows = [];
+
+ _controllers = _CredentialControllers.fromCredential(_credential);
+ _fieldMap = _buildFieldMap(_controllers, _credential);
+
+ _fieldMap.forEach((key, value) {
+ rows.add(Container(
+ margin: EdgeInsets.only(top: 2.5),
+ child: Text(key, style: TextStyle(fontWeight: FontWeight.w600)),
+ ));
+
+ final List widgets = [
+ Expanded(
+ flex: 3,
+ child: value.mutable
+ ? TextField(
+ maxLines: 1,
+ controller: value.controller,
+ obscure: value.obscured)
+ : Container(
+ margin: EdgeInsets.symmetric(vertical: 10),
+ child: Text(value.text)),
+ ),
+ ];
+
+ if (value.copyable) {
+ widgets.add(Expanded(
+ child: CupertinoButton(
+ child: Text(value.otp ? 'OTP' : 'Copy'),
+ onPressed: () => Clipboard.setData(ClipboardData(
+ text: value.otp
+ ? OTP
+ .generateTOTPCode(value.controller.text,
+ DateTime.now().millisecondsSinceEpoch)
+ .toString()
+ : value.mutable ? value.controller.text : value.text,
+ )),
+ ),
+ ));
+ }
+
+ rows.add(Row(children: widgets));
+ });
+
+ return rows;
+ }
+}
+
+class _FieldBuildConfig {
+ final TextEditingController controller;
+ final String text;
+ final bool mutable;
+ final bool copyable;
+ final bool obscured;
+ final bool otp;
+
+ const _FieldBuildConfig({
+ this.mutable = true,
+ this.copyable = true,
+ this.obscured = false,
+ this.otp = false,
+ this.controller,
+ this.text,
+ });
+}
+
+class _CredentialControllers {
+ final TextEditingController sourceHost;
+ final TextEditingController primary;
+ final TextEditingController tag;
+ final TextEditingController username;
+ final TextEditingController email;
+ final TextEditingController password;
+ final TextEditingController otpSecret;
+
+ const _CredentialControllers({
+ this.sourceHost,
+ this.primary,
+ this.tag,
+ this.username,
+ this.email,
+ this.password,
+ this.otpSecret,
+ });
+
+ factory _CredentialControllers.fromCredential(types.Credential credential) =>
+ _CredentialControllers(
+ sourceHost: TextEditingController(text: credential.meta.sourceHost),
+ primary: TextEditingController(text: credential.meta.primary),
+ tag: TextEditingController(text: credential.meta.tag),
+ username: TextEditingController(text: credential.username),
+ email: TextEditingController(text: credential.email),
+ password: TextEditingController(text: credential.password),
+ otpSecret: TextEditingController(text: credential.otpSecret),
+ );
+}
diff --git a/client/lib/screens/credentials.dart b/client/lib/screens/credentials.dart
new file mode 100644
index 0000000..ab102ab
--- /dev/null
+++ b/client/lib/screens/credentials.dart
@@ -0,0 +1,72 @@
+import 'package:flutter/cupertino.dart';
+import 'package:provider/provider.dart';
+
+import '../types/abstracts.dart';
+import '../types/credential.dart';
+
+import '../utils/crypto.dart' as crypto;
+
+import '../widgets/tappable_text_list.dart';
+
+class Credentials extends StatelessWidget {
+ final List metadatas;
+
+ const Credentials(this.metadatas);
+
+ @override
+ Widget build(BuildContext context) {
+ return CupertinoPageScaffold(
+ child: TappableTextList(tappableText: _buildTappableText(context)),
+ navigationBar: CupertinoNavigationBar(),
+ );
+ }
+
+ Map _buildTappableText(BuildContext context) {
+ final makeOnTapHandler = (String id) => () async {
+ showCupertinoDialog(
+ context: context,
+ builder: (BuildContext context) => CupertinoAlertDialog(
+ content: Column(
+ children: [
+ Text('Decrypting credential...'),
+ Container(
+ margin: EdgeInsets.only(top: 10),
+ child: CupertinoActivityIndicator()),
+ ],
+ )),
+ );
+
+ final config = Provider.of(context);
+ final client = Provider.of(context);
+
+ final Future privateKey = config.privateKey;
+ final String password = config.password;
+
+ final credential = await client.get(id);
+
+ credential.password = crypto.decryptPassword(
+ password, await privateKey, credential.password);
+
+ if (credential.otpSecret != null && credential.otpSecret != '') {
+ credential.otpSecret = crypto.decryptPassword(
+ password, await privateKey, credential.otpSecret);
+ }
+
+ Navigator.of(context)
+ ..pop()
+ ..pushNamed('/credential', arguments: credential);
+ };
+
+ Map tappableText = {};
+
+ metadatas.forEach((Metadata metadata) {
+ var primary = metadata.primary;
+ if (metadata.tag != null && metadata.tag != '') {
+ primary += "-" + metadata.tag;
+ }
+ tappableText[primary] = makeOnTapHandler(metadata.id);
+ });
+
+ return tappableText;
+ }
+}
diff --git a/client/lib/screens/home.dart b/client/lib/screens/home.dart
new file mode 100644
index 0000000..ce528c3
--- /dev/null
+++ b/client/lib/screens/home.dart
@@ -0,0 +1,139 @@
+import 'dart:async';
+
+import 'package:flutter/cupertino.dart';
+import 'package:provider/provider.dart';
+
+import '../types/abstracts.dart';
+import '../types/credential.dart';
+import '../types/screen_arguments.dart';
+
+import '../widgets/tappable_text_list.dart';
+
+class Home extends StatefulWidget {
+ const Home({Key key}) : super(key: key);
+
+ @override
+ State createState() => _HomeState();
+}
+
+class _HomeState extends State with WidgetsBindingObserver {
+ CredentialsRepo _client;
+ ConfigRepo _config;
+ Future> _metadatas;
+ bool _stateIsPaused = false;
+ Timer _pausedStateTimer;
+
+ @override
+ void initState() {
+ super.initState();
+ WidgetsBinding.instance.addObserver(this);
+ }
+
+ @override
+ void didChangeDependencies() {
+ super.didChangeDependencies();
+
+ _config = Provider.of(context);
+ _client = Provider.of(context);
+
+ _metadatas = _client.getAllMetadata('').toList();
+ }
+
+ @override
+ void didChangeAppLifecycleState(AppLifecycleState state) {
+ _stateIsPaused = state == AppLifecycleState.paused;
+
+ if (_stateIsPaused) {
+ _pausedStateTimer = _newPausedStateTimer();
+ return;
+ }
+
+ if (_pausedStateTimer != null) _pausedStateTimer.cancel();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return CupertinoPageScaffold(
+ child: FutureBuilder>(
+ future: _metadatas,
+ builder: (
+ BuildContext context,
+ AsyncSnapshot> snapshot,
+ ) =>
+ (snapshot.connectionState == ConnectionState.done)
+ ? TappableTextList(
+ tappableText: _buildTappableText(context, snapshot.data))
+ : Center(child: CupertinoActivityIndicator()),
+ ),
+ navigationBar: CupertinoNavigationBar(
+ leading: GestureDetector(
+ child: Align(
+ child: Text('Lock',
+ style: TextStyle(color: CupertinoColors.destructiveRed)),
+ alignment: Alignment(-0.9, 0)),
+ onTap: _makeLockOnTapHandler(context),
+ ),
+ trailing: GestureDetector(
+ child: Icon(CupertinoIcons.gear),
+ onTap: _makeConfigOnTapHandler(context),
+ ),
+ ),
+ );
+ }
+
+ @override
+ void dispose() {
+ WidgetsBinding.instance.removeObserver(this);
+ if (_pausedStateTimer != null) _pausedStateTimer.cancel();
+ super.dispose();
+ }
+
+ Timer _newPausedStateTimer() {
+ const checkPeriod = 30;
+
+ return Timer(Duration(seconds: checkPeriod), () {
+ Navigator.of(context)
+ .pushNamedAndRemoveUntil('/', ModalRoute.withName('/home'));
+ });
+ }
+
+ Map _buildTappableText(
+ BuildContext context,
+ List metadatas,
+ ) {
+ final Map> metaMap = {};
+
+ metadatas.sort((a, b) => a.id.compareTo(b.id));
+
+ for (var metadata in metadatas) {
+ final source = metadata.sourceHost;
+
+ if (metaMap[source] == null) {
+ metaMap[source] = [metadata];
+ } else {
+ metaMap[source].add(metadata);
+ }
+ }
+
+ final handleOnTap = (List metadatas) => () async =>
+ Navigator.of(context).pushNamed('/credentials', arguments: metadatas);
+
+ final Map tappableText = {};
+
+ metaMap.forEach((String key, List value) =>
+ tappableText[key] = handleOnTap(value));
+
+ return tappableText;
+ }
+
+ GestureTapCallback _makeLockOnTapHandler(BuildContext context) {
+ return () => Navigator.of(context).pushReplacementNamed('/');
+ }
+
+ GestureTapCallback _makeConfigOnTapHandler(BuildContext context) {
+ return () async => Navigator.of(context).pushNamed('/config',
+ arguments: ConfigScreenArguments(
+ connectionConfig: await _config.connectionConfig,
+ privateKey: await _config.privateKey));
+ }
+}
diff --git a/client/lib/types/abstracts.dart b/client/lib/types/abstracts.dart
new file mode 100644
index 0000000..b399efe
--- /dev/null
+++ b/client/lib/types/abstracts.dart
@@ -0,0 +1,27 @@
+import 'dart:async';
+
+import 'credential.dart';
+import 'connection_config.dart';
+
+abstract class CredentialsRepo {
+ Stream getAllMetadata(String sourceHost);
+ Future get(String id);
+ Future create(CredentialInput input);
+ Future update(String id, CredentialInput input);
+ Future delete(String id);
+}
+
+abstract class ConfigRepo {
+ Future setPrivateKey(String key);
+ Future get privateKey;
+
+ String get password;
+ Future setPassword(String password);
+ Future get passwordIsSet;
+ Future matchesPasswordHash(String password);
+
+ Future setConnectionConfig(ConnectionConfig config);
+ Future get connectionConfig;
+
+ Future deleteAll();
+}
diff --git a/client/lib/types/connection_config.dart b/client/lib/types/connection_config.dart
new file mode 100644
index 0000000..a9e6689
--- /dev/null
+++ b/client/lib/types/connection_config.dart
@@ -0,0 +1,27 @@
+class ConnectionConfig {
+ String host;
+ String caCertificate;
+ String certificate;
+ String privateCertificate;
+
+ ConnectionConfig({
+ this.host,
+ this.caCertificate,
+ this.certificate,
+ this.privateCertificate,
+ });
+
+ ConnectionConfig.fromJson(Map json) {
+ host = json['host'];
+ caCertificate = json['caCertificate'];
+ certificate = json['certificate'];
+ privateCertificate = json['privateCertificate'];
+ }
+
+ Map toJson() => {
+ 'host': host,
+ 'caCertificate': caCertificate,
+ 'certificate': certificate,
+ 'privateCertificate': privateCertificate,
+ };
+}
diff --git a/client/lib/types/credential.dart b/client/lib/types/credential.dart
new file mode 100644
index 0000000..2b0956e
--- /dev/null
+++ b/client/lib/types/credential.dart
@@ -0,0 +1,94 @@
+import '../protobuf/service.pb.dart' as protobuf;
+
+class Metadata {
+ String id;
+ String sourceHost;
+ DateTime createdAt;
+ DateTime updatedAt;
+ String primary;
+ String loginUrl;
+ String tag;
+
+ Metadata({
+ this.id,
+ this.sourceHost,
+ this.createdAt,
+ this.updatedAt,
+ this.primary,
+ this.loginUrl,
+ this.tag,
+ });
+
+ Metadata.fromProtobuf(protobuf.Metadata metadata) {
+ id = metadata.id;
+ createdAt = metadata.createdAt.toDateTime();
+ updatedAt = metadata.updatedAt.toDateTime();
+ sourceHost = metadata.sourceHost;
+ primary = metadata.primary;
+ loginUrl = metadata.loginUrl;
+ tag = metadata.tag;
+ }
+
+ @override
+ String toString() => "id: $id";
+}
+
+class MetadataInput {
+ String sourceHost;
+ String primary;
+ String loginUrl;
+ String tag;
+
+ MetadataInput({this.sourceHost, this.primary, this.loginUrl, this.tag});
+}
+
+class Credential {
+ Metadata meta;
+ String username;
+ String email;
+ String password;
+ String otpSecret;
+
+ Credential({
+ this.meta,
+ this.username,
+ this.email,
+ this.password,
+ this.otpSecret,
+ });
+
+ Credential.fromProtobuf(protobuf.Credential credential) {
+ meta = Metadata(
+ id: credential.id,
+ createdAt: credential.createdAt.toDateTime(),
+ updatedAt: credential.updatedAt.toDateTime(),
+ sourceHost: credential.sourceHost,
+ primary: credential.primary,
+ loginUrl: credential.loginUrl,
+ tag: credential.tag,
+ );
+ username = credential.username;
+ email = credential.email;
+ password = credential.password;
+ otpSecret = credential.otpSecret;
+ }
+
+ @override
+ String toString() => "meta: $meta\n";
+}
+
+class CredentialInput {
+ MetadataInput meta;
+ String username;
+ String email;
+ String password;
+ String otpSecret;
+
+ CredentialInput({
+ this.meta,
+ this.username,
+ this.email,
+ this.password,
+ this.otpSecret,
+ });
+}
diff --git a/client/lib/types/screen_arguments.dart b/client/lib/types/screen_arguments.dart
new file mode 100644
index 0000000..d7cd193
--- /dev/null
+++ b/client/lib/types/screen_arguments.dart
@@ -0,0 +1,8 @@
+import 'connection_config.dart';
+
+class ConfigScreenArguments {
+ final ConnectionConfig connectionConfig;
+ final String privateKey;
+
+ const ConfigScreenArguments({this.connectionConfig, this.privateKey});
+}
diff --git a/client/lib/utils/crypto.dart b/client/lib/utils/crypto.dart
new file mode 100644
index 0000000..ad572d6
--- /dev/null
+++ b/client/lib/utils/crypto.dart
@@ -0,0 +1,42 @@
+import 'dart:math';
+import 'dart:convert';
+import 'dart:typed_data';
+
+import 'package:crypt/crypt.dart';
+import 'package:encrypt/encrypt.dart';
+import 'package:password_hash/password_hash.dart';
+
+String hashPassword(String password) {
+ const saltSize = 16;
+ const saltIntMax = 256;
+
+ final random = Random.secure();
+ final saltInts =
+ List.generate(saltSize, (_) => random.nextInt(saltIntMax));
+ final salt = base64.encode(saltInts);
+
+ return Crypt.sha256(password, salt: salt).toString();
+}
+
+bool matchHashedPassword(String hashedPassword, String password) =>
+ Crypt(hashedPassword).match(password);
+
+String decryptPassword(String masterpass, privateKey, ciphertext) {
+ final key =
+ PBKDF2().generateKey(masterpass, privateKey, pbkdf2Rounds, keySize);
+
+ var cipherbytes = base64.decode(ciphertext);
+ final iv =
+ IV(Uint8List.fromList(cipherbytes.getRange(0, aesBlockSize).toList()));
+
+ cipherbytes = Uint8List.fromList(
+ cipherbytes.getRange(aesBlockSize, cipherbytes.length).toList());
+
+ final encrypter = Encrypter(AES(Key(key), mode: AESMode.cbc));
+
+ return encrypter.decrypt(Encrypted(cipherbytes), iv: iv);
+}
+
+const pbkdf2Rounds = 4096;
+const keySize = 32;
+const aesBlockSize = 16;
diff --git a/client/lib/widgets/tappable_text_list.dart b/client/lib/widgets/tappable_text_list.dart
new file mode 100644
index 0000000..20b7cc2
--- /dev/null
+++ b/client/lib/widgets/tappable_text_list.dart
@@ -0,0 +1,35 @@
+import 'package:flutter/cupertino.dart';
+
+class TappableTextList extends StatelessWidget {
+ final Map tappableText;
+
+ TappableTextList({Key key, this.tappableText}) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ return ListView(
+ children: _buildListChildren(context),
+ );
+ }
+
+ List _buildListChildren(BuildContext context) {
+ List widgets = [];
+
+ tappableText.forEach((String text, GestureTapCallback handleOnTap) {
+ widgets.add(GestureDetector(
+ onTap: handleOnTap,
+ child: Container(
+ padding: const EdgeInsets.symmetric(vertical: 15.0),
+ decoration: const BoxDecoration(
+ border: Border(
+ bottom: BorderSide(color: CupertinoColors.lightBackgroundGray),
+ ),
+ ),
+ child: Text(text, textAlign: TextAlign.center),
+ ),
+ ));
+ });
+
+ return widgets;
+ }
+}
diff --git a/client/lib/widgets/text_field.dart b/client/lib/widgets/text_field.dart
new file mode 100644
index 0000000..6ac2a24
--- /dev/null
+++ b/client/lib/widgets/text_field.dart
@@ -0,0 +1,57 @@
+import 'package:flutter/cupertino.dart';
+
+typedef OnSubmittedBuilder = ValueChanged Function(
+ BuildContext context,
+);
+
+class TextField extends StatelessWidget {
+ final OnSubmittedBuilder onSubmittedBuilder;
+ final TextEditingController controller;
+ final OverlayVisibilityMode clearButtonMode;
+ final Widget prefix;
+ final Widget suffix;
+ final bool obscure;
+ final bool autofocus;
+ final bool autocorrect;
+ final int minLines;
+ final int maxLines;
+
+ const TextField({
+ this.onSubmittedBuilder,
+ this.controller,
+ this.obscure = false,
+ this.autofocus = false,
+ this.minLines,
+ this.maxLines,
+ this.autocorrect = false,
+ this.prefix,
+ this.suffix,
+ this.clearButtonMode = OverlayVisibilityMode.editing,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ padding: const EdgeInsets.symmetric(vertical: 5.0),
+ child: CupertinoTextField(
+ decoration: BoxDecoration(
+ border: Border.all(color: CupertinoColors.black),
+ borderRadius: const BorderRadius.all(Radius.circular(5.0)),
+ ),
+ clearButtonMode: clearButtonMode,
+ textAlign: TextAlign.start,
+ onSubmitted: this.onSubmittedBuilder != null
+ ? onSubmittedBuilder(context)
+ : null,
+ controller: controller,
+ obscureText: obscure,
+ autofocus: autofocus,
+ autocorrect: autocorrect,
+ minLines: minLines,
+ maxLines: maxLines,
+ prefix: prefix,
+ suffix: suffix,
+ ),
+ );
+ }
+}
diff --git a/client/pubspec.lock b/client/pubspec.lock
new file mode 100644
index 0000000..f46a501
--- /dev/null
+++ b/client/pubspec.lock
@@ -0,0 +1,279 @@
+# Generated by pub
+# See https://dart.dev/tools/pub/glossary#lockfile
+packages:
+ args:
+ dependency: transitive
+ description:
+ name: args
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "1.5.2"
+ asn1lib:
+ dependency: transitive
+ description:
+ name: asn1lib
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "0.5.8"
+ async:
+ dependency: transitive
+ description:
+ name: async
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "2.2.0"
+ base32:
+ dependency: transitive
+ description:
+ name: base32
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "1.0.1"
+ boolean_selector:
+ dependency: transitive
+ description:
+ name: boolean_selector
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "1.0.4"
+ charcode:
+ dependency: transitive
+ description:
+ name: charcode
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "1.1.2"
+ collection:
+ dependency: transitive
+ description:
+ name: collection
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "1.14.11"
+ convert:
+ dependency: transitive
+ description:
+ name: convert
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "2.1.1"
+ crypt:
+ dependency: "direct main"
+ description:
+ name: crypt
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "1.0.7"
+ crypto:
+ dependency: transitive
+ description:
+ name: crypto
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "2.0.6"
+ cupertino_icons:
+ dependency: "direct main"
+ description:
+ name: cupertino_icons
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "0.1.2"
+ encrypt:
+ dependency: "direct main"
+ description:
+ name: encrypt
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "3.2.0"
+ fixnum:
+ dependency: transitive
+ description:
+ name: fixnum
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "0.10.9"
+ flutter:
+ dependency: "direct main"
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ flutter_secure_storage:
+ dependency: "direct main"
+ description:
+ name: flutter_secure_storage
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "3.2.1+1"
+ flutter_test:
+ dependency: "direct dev"
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ googleapis_auth:
+ dependency: transitive
+ description:
+ name: googleapis_auth
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "0.2.8"
+ grpc:
+ dependency: "direct main"
+ description:
+ name: grpc
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "1.0.3"
+ http:
+ dependency: transitive
+ description:
+ name: http
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "0.12.0+2"
+ http2:
+ dependency: transitive
+ description:
+ name: http2
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "1.0.0"
+ http_parser:
+ dependency: transitive
+ description:
+ name: http_parser
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "3.1.3"
+ matcher:
+ dependency: transitive
+ description:
+ name: matcher
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "0.12.5"
+ meta:
+ dependency: transitive
+ description:
+ name: meta
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "1.1.6"
+ otp:
+ dependency: "direct main"
+ description:
+ name: otp
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "1.0.3"
+ password_hash:
+ dependency: "direct main"
+ description:
+ name: password_hash
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "2.0.0"
+ path:
+ dependency: transitive
+ description:
+ name: path
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "1.6.2"
+ pedantic:
+ dependency: transitive
+ description:
+ name: pedantic
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "1.7.0"
+ pointycastle:
+ dependency: transitive
+ description:
+ name: pointycastle
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "1.0.1"
+ protobuf:
+ dependency: "direct main"
+ description:
+ name: protobuf
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "0.13.12"
+ provider:
+ dependency: "direct main"
+ description:
+ name: provider
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "3.0.0+1"
+ quiver:
+ dependency: transitive
+ description:
+ name: quiver
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "2.0.3"
+ sky_engine:
+ dependency: transitive
+ description: flutter
+ source: sdk
+ version: "0.0.99"
+ source_span:
+ dependency: transitive
+ description:
+ name: source_span
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "1.5.5"
+ stack_trace:
+ dependency: transitive
+ description:
+ name: stack_trace
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "1.9.3"
+ stream_channel:
+ dependency: transitive
+ description:
+ name: stream_channel
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "2.0.0"
+ string_scanner:
+ dependency: transitive
+ description:
+ name: string_scanner
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "1.0.4"
+ term_glyph:
+ dependency: transitive
+ description:
+ name: term_glyph
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "1.1.0"
+ test_api:
+ dependency: transitive
+ description:
+ name: test_api
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "0.2.5"
+ typed_data:
+ dependency: transitive
+ description:
+ name: typed_data
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "1.1.6"
+ vector_math:
+ dependency: transitive
+ description:
+ name: vector_math
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "2.0.8"
+sdks:
+ dart: ">=2.2.2 <3.0.0"
diff --git a/client/pubspec.yaml b/client/pubspec.yaml
new file mode 100644
index 0000000..b273453
--- /dev/null
+++ b/client/pubspec.yaml
@@ -0,0 +1,45 @@
+name: selfpass_client
+description: The cross-platform Selfpass client.
+
+# The following defines the version and build number for your application.
+# A version number is three numbers separated by dots, like 1.2.43
+# followed by an optional build number separated by a +.
+# Both the version and the builder number may be overridden in flutter
+# build by specifying --build-name and --build-number, respectively.
+# In Android, build-name is used as versionName while build-number used as versionCode.
+# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
+# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
+# Read more about iOS versioning at
+# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
+version: 0.1.0
+
+environment:
+ sdk: ">=2.1.0 <3.0.0"
+
+dependencies:
+ flutter:
+ sdk: flutter
+
+ cupertino_icons: ^0.1.2
+
+ grpc: ^1.0.3
+ protobuf: ^0.13.12
+
+ provider: ^3.0.0
+
+ crypt: ^1.0.7
+ flutter_secure_storage: ^3.2.1
+ password_hash: ^2.0.0
+ encrypt: ^3.2.0
+
+ otp: ^1.0.3
+
+dev_dependencies:
+ flutter_test:
+ sdk: flutter
+
+# For information on the generic Dart part of this file, see the
+# following page: https://www.dartlang.org/tools/pub/pubspec
+
+# The following section is specific to Flutter.
+flutter:
diff --git a/client/test/widget_test.dart b/client/test/widget_test.dart
new file mode 100644
index 0000000..28613c3
--- /dev/null
+++ b/client/test/widget_test.dart
@@ -0,0 +1,30 @@
+// This is a basic Flutter widget test.
+//
+// To perform an interaction with a widget in your test, use the WidgetTester
+// utility that Flutter provides. For example, you can send tap and scroll
+// gestures. You can also use WidgetTester to find child widgets in the widget
+// tree, read text, and verify that the values of widget properties are correct.
+
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+import 'package:selfpass_client/main.dart';
+
+void main() {
+ testWidgets('Counter increments smoke test', (WidgetTester tester) async {
+ // Build our app and trigger a frame.
+ await tester.pumpWidget(Selfpass());
+
+ // Verify that our counter starts at 0.
+ expect(find.text('0'), findsOneWidget);
+ expect(find.text('1'), findsNothing);
+
+ // Tap the '+' icon and trigger a frame.
+ await tester.tap(find.byIcon(Icons.add));
+ await tester.pump();
+
+ // Verify that our counter has incremented.
+ expect(find.text('0'), findsNothing);
+ expect(find.text('1'), findsOneWidget);
+ });
+}