Add decryption of credential encrypted fields; refactored config;
add app icon
|  | @ -33,7 +33,7 @@ android { | |||
| 
 | ||||
|     defaultConfig { | ||||
|         // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). | ||||
|         applicationId "com.example.selfpass_mobile" | ||||
|         applicationId "com.mjfs.selfpass" | ||||
|         minSdkVersion 16 | ||||
|         targetSdkVersion 28 | ||||
|         versionCode flutterVersionCode.toInteger() | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     package="com.example.selfpass_mobile"> | ||||
|     package="com.mjfs.selfpass"> | ||||
|     <!-- Flutter needs it to communicate with the running application | ||||
|          to allow setting breakpoints, to provide hot reload, etc. | ||||
|     --> | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     package="com.example.selfpass_mobile"> | ||||
|     package="com.mjfs.selfpass"> | ||||
| 
 | ||||
|     <!-- io.flutter.app.FlutterApplication is an android.app.Application that | ||||
|          calls FlutterMain.startInitialization(this); in its onCreate method. | ||||
|  | @ -8,7 +8,7 @@ | |||
|          FlutterApplication and put your custom class here. --> | ||||
|     <application | ||||
|         android:name="io.flutter.app.FlutterApplication" | ||||
|         android:label="selfpass_mobile" | ||||
|         android:label="selfpass_client" | ||||
|         android:icon="@mipmap/ic_launcher"> | ||||
|         <activity | ||||
|             android:name=".MainActivity" | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     package="com.example.selfpass_mobile"> | ||||
|     package="com.mjfs.selfpass"> | ||||
|     <!-- Flutter needs it to communicate with the running application | ||||
|          to allow setting breakpoints, to provide hot reload, etc. | ||||
|     --> | ||||
|  |  | |||
|  | @ -4,12 +4,12 @@ PODS: | |||
|     - Flutter | ||||
| 
 | ||||
| DEPENDENCIES: | ||||
|   - Flutter (from `.symlinks/flutter/ios`) | ||||
|   - Flutter (from `.symlinks/flutter/ios-release`) | ||||
|   - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) | ||||
| 
 | ||||
| EXTERNAL SOURCES: | ||||
|   Flutter: | ||||
|     :path: ".symlinks/flutter/ios" | ||||
|     :path: ".symlinks/flutter/ios-release" | ||||
|   flutter_secure_storage: | ||||
|     :path: ".symlinks/plugins/flutter_secure_storage/ios" | ||||
| 
 | ||||
|  |  | |||
|  | @ -253,7 +253,7 @@ | |||
| 			); | ||||
| 			inputPaths = ( | ||||
| 				"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", | ||||
| 				"${PODS_ROOT}/../.symlinks/flutter/ios/Flutter.framework", | ||||
| 				"${PODS_ROOT}/../.symlinks/flutter/ios-release/Flutter.framework", | ||||
| 			); | ||||
| 			name = "[CP] Embed Pods Frameworks"; | ||||
| 			outputPaths = ( | ||||
|  |  | |||
							
								
								
									
										
											BIN
										
									
								
								ios/Runner/Assets.xcassets/AppIcon.appiconset/1024-1.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 37 KiB | 
							
								
								
									
										
											BIN
										
									
								
								ios/Runner/Assets.xcassets/AppIcon.appiconset/1024.png
									
									
									
									
									
										Executable file
									
								
							
							
						
						| After Width: | Height: | Size: 37 KiB | 
							
								
								
									
										
											BIN
										
									
								
								ios/Runner/Assets.xcassets/AppIcon.appiconset/114.png
									
									
									
									
									
										Executable file
									
								
							
							
						
						| After Width: | Height: | Size: 1.5 KiB | 
							
								
								
									
										
											BIN
										
									
								
								ios/Runner/Assets.xcassets/AppIcon.appiconset/120.png
									
									
									
									
									
										Executable file
									
								
							
							
						
						| After Width: | Height: | Size: 1.5 KiB | 
							
								
								
									
										
											BIN
										
									
								
								ios/Runner/Assets.xcassets/AppIcon.appiconset/128.png
									
									
									
									
									
										Executable file
									
								
							
							
						
						| After Width: | Height: | Size: 1.8 KiB | 
							
								
								
									
										
											BIN
										
									
								
								ios/Runner/Assets.xcassets/AppIcon.appiconset/16.png
									
									
									
									
									
										Executable file
									
								
							
							
						
						| After Width: | Height: | Size: 163 B | 
							
								
								
									
										
											BIN
										
									
								
								ios/Runner/Assets.xcassets/AppIcon.appiconset/180.png
									
									
									
									
									
										Executable file
									
								
							
							
						
						| After Width: | Height: | Size: 2.9 KiB | 
							
								
								
									
										
											BIN
										
									
								
								ios/Runner/Assets.xcassets/AppIcon.appiconset/256.png
									
									
									
									
									
										Executable file
									
								
							
							
						
						| After Width: | Height: | Size: 5.3 KiB | 
							
								
								
									
										
											BIN
										
									
								
								ios/Runner/Assets.xcassets/AppIcon.appiconset/29.png
									
									
									
									
									
										Executable file
									
								
							
							
						
						| After Width: | Height: | Size: 233 B | 
							
								
								
									
										
											BIN
										
									
								
								ios/Runner/Assets.xcassets/AppIcon.appiconset/32.png
									
									
									
									
									
										Executable file
									
								
							
							
						
						| After Width: | Height: | Size: 276 B | 
							
								
								
									
										
											BIN
										
									
								
								ios/Runner/Assets.xcassets/AppIcon.appiconset/40.png
									
									
									
									
									
										Executable file
									
								
							
							
						
						| After Width: | Height: | Size: 394 B | 
							
								
								
									
										
											BIN
										
									
								
								ios/Runner/Assets.xcassets/AppIcon.appiconset/512.png
									
									
									
									
									
										Executable file
									
								
							
							
						
						| After Width: | Height: | Size: 17 KiB | 
							
								
								
									
										
											BIN
										
									
								
								ios/Runner/Assets.xcassets/AppIcon.appiconset/57.png
									
									
									
									
									
										Executable file
									
								
							
							
						
						| After Width: | Height: | Size: 549 B | 
							
								
								
									
										
											BIN
										
									
								
								ios/Runner/Assets.xcassets/AppIcon.appiconset/58.png
									
									
									
									
									
										Executable file
									
								
							
							
						
						| After Width: | Height: | Size: 600 B | 
							
								
								
									
										
											BIN
										
									
								
								ios/Runner/Assets.xcassets/AppIcon.appiconset/60.png
									
									
									
									
									
										Executable file
									
								
							
							
						
						| After Width: | Height: | Size: 601 B | 
							
								
								
									
										
											BIN
										
									
								
								ios/Runner/Assets.xcassets/AppIcon.appiconset/64.png
									
									
									
									
									
										Executable file
									
								
							
							
						
						| After Width: | Height: | Size: 695 B | 
							
								
								
									
										
											BIN
										
									
								
								ios/Runner/Assets.xcassets/AppIcon.appiconset/80.png
									
									
									
									
									
										Executable file
									
								
							
							
						
						| After Width: | Height: | Size: 893 B | 
							
								
								
									
										
											BIN
										
									
								
								ios/Runner/Assets.xcassets/AppIcon.appiconset/87.png
									
									
									
									
									
										Executable file
									
								
							
							
						
						| After Width: | Height: | Size: 1 KiB | 
							
								
								
									
										148
									
								
								ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						|  | @ -3,116 +3,134 @@ | |||
|     { | ||||
|       "size" : "20x20", | ||||
|       "idiom" : "iphone", | ||||
|       "filename" : "Icon-App-20x20@2x.png", | ||||
|       "filename" : "40.png", | ||||
|       "scale" : "2x" | ||||
|     }, | ||||
|     { | ||||
|       "size" : "20x20", | ||||
|       "idiom" : "iphone", | ||||
|       "filename" : "Icon-App-20x20@3x.png", | ||||
|       "filename" : "60.png", | ||||
|       "scale" : "3x" | ||||
|     }, | ||||
|     { | ||||
|       "size" : "29x29", | ||||
|       "idiom" : "iphone", | ||||
|       "filename" : "Icon-App-29x29@1x.png", | ||||
|       "filename" : "29.png", | ||||
|       "scale" : "1x" | ||||
|     }, | ||||
|     { | ||||
|       "size" : "29x29", | ||||
|       "idiom" : "iphone", | ||||
|       "filename" : "Icon-App-29x29@2x.png", | ||||
|       "filename" : "58.png", | ||||
|       "scale" : "2x" | ||||
|     }, | ||||
|     { | ||||
|       "size" : "29x29", | ||||
|       "idiom" : "iphone", | ||||
|       "filename" : "Icon-App-29x29@3x.png", | ||||
|       "filename" : "87.png", | ||||
|       "scale" : "3x" | ||||
|     }, | ||||
|     { | ||||
|       "size" : "40x40", | ||||
|       "idiom" : "iphone", | ||||
|       "filename" : "Icon-App-40x40@2x.png", | ||||
|       "filename" : "80.png", | ||||
|       "scale" : "2x" | ||||
|     }, | ||||
|     { | ||||
|       "size" : "40x40", | ||||
|       "idiom" : "iphone", | ||||
|       "filename" : "Icon-App-40x40@3x.png", | ||||
|       "filename" : "120.png", | ||||
|       "scale" : "3x" | ||||
|     }, | ||||
|     { | ||||
|       "size" : "60x60", | ||||
|       "size" : "57x57", | ||||
|       "idiom" : "iphone", | ||||
|       "filename" : "Icon-App-60x60@2x.png", | ||||
|       "filename" : "57.png", | ||||
|       "scale" : "1x" | ||||
|     }, | ||||
|     { | ||||
|       "size" : "57x57", | ||||
|       "idiom" : "iphone", | ||||
|       "filename" : "114.png", | ||||
|       "scale" : "2x" | ||||
|     }, | ||||
|     { | ||||
|       "size" : "60x60", | ||||
|       "idiom" : "iphone", | ||||
|       "filename" : "Icon-App-60x60@3x.png", | ||||
|       "filename" : "120.png", | ||||
|       "scale" : "2x" | ||||
|     }, | ||||
|     { | ||||
|       "size" : "60x60", | ||||
|       "idiom" : "iphone", | ||||
|       "filename" : "180.png", | ||||
|       "scale" : "3x" | ||||
|     }, | ||||
|     { | ||||
|       "size" : "20x20", | ||||
|       "idiom" : "ipad", | ||||
|       "filename" : "Icon-App-20x20@1x.png", | ||||
|       "scale" : "1x" | ||||
|     }, | ||||
|     { | ||||
|       "size" : "20x20", | ||||
|       "idiom" : "ipad", | ||||
|       "filename" : "Icon-App-20x20@2x.png", | ||||
|       "scale" : "2x" | ||||
|     }, | ||||
|     { | ||||
|       "size" : "29x29", | ||||
|       "idiom" : "ipad", | ||||
|       "filename" : "Icon-App-29x29@1x.png", | ||||
|       "scale" : "1x" | ||||
|     }, | ||||
|     { | ||||
|       "size" : "29x29", | ||||
|       "idiom" : "ipad", | ||||
|       "filename" : "Icon-App-29x29@2x.png", | ||||
|       "scale" : "2x" | ||||
|     }, | ||||
|     { | ||||
|       "size" : "40x40", | ||||
|       "idiom" : "ipad", | ||||
|       "filename" : "Icon-App-40x40@1x.png", | ||||
|       "scale" : "1x" | ||||
|     }, | ||||
|     { | ||||
|       "size" : "40x40", | ||||
|       "idiom" : "ipad", | ||||
|       "filename" : "Icon-App-40x40@2x.png", | ||||
|       "scale" : "2x" | ||||
|     }, | ||||
|     { | ||||
|       "size" : "76x76", | ||||
|       "idiom" : "ipad", | ||||
|       "filename" : "Icon-App-76x76@1x.png", | ||||
|       "scale" : "1x" | ||||
|     }, | ||||
|     { | ||||
|       "size" : "76x76", | ||||
|       "idiom" : "ipad", | ||||
|       "filename" : "Icon-App-76x76@2x.png", | ||||
|       "scale" : "2x" | ||||
|     }, | ||||
|     { | ||||
|       "size" : "83.5x83.5", | ||||
|       "idiom" : "ipad", | ||||
|       "filename" : "Icon-App-83.5x83.5@2x.png", | ||||
|       "scale" : "2x" | ||||
|     }, | ||||
|     { | ||||
|       "size" : "1024x1024", | ||||
|       "idiom" : "ios-marketing", | ||||
|       "filename" : "Icon-App-1024x1024@1x.png", | ||||
|       "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" : { | ||||
|  |  | |||
| Before Width: | Height: | Size: 11 KiB | 
| Before Width: | Height: | Size: 564 B | 
| Before Width: | Height: | Size: 1.3 KiB | 
| Before Width: | Height: | Size: 1.6 KiB | 
| Before Width: | Height: | Size: 1 KiB | 
| Before Width: | Height: | Size: 1.7 KiB | 
| Before Width: | Height: | Size: 1.9 KiB | 
| Before Width: | Height: | Size: 1.3 KiB | 
| Before Width: | Height: | Size: 1.9 KiB | 
| Before Width: | Height: | Size: 2.6 KiB | 
| Before Width: | Height: | Size: 2.6 KiB | 
| Before Width: | Height: | Size: 3.7 KiB | 
| Before Width: | Height: | Size: 1.8 KiB | 
| Before Width: | Height: | Size: 3.2 KiB | 
| Before Width: | Height: | Size: 3.5 KiB | 
							
								
								
									
										6
									
								
								ios/Runner/Assets.xcassets/Contents.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,6 @@ | |||
| { | ||||
|   "info" : { | ||||
|     "version" : 1, | ||||
|     "author" : "xcode" | ||||
|   } | ||||
| } | ||||
|  | @ -13,7 +13,7 @@ | |||
| 	<key>CFBundleInfoDictionaryVersion</key> | ||||
| 	<string>6.0</string> | ||||
| 	<key>CFBundleName</key> | ||||
| 	<string>selfpass_mobile</string> | ||||
| 	<string>selfpass_client</string> | ||||
| 	<key>CFBundlePackageType</key> | ||||
| 	<string>APPL</string> | ||||
| 	<key>CFBundleShortVersionString</key> | ||||
|  |  | |||
|  | @ -60,7 +60,9 @@ class Selfpass extends StatelessWidget { | |||
|               break; | ||||
| 
 | ||||
|             case '/config': | ||||
|               final ConfigScreenArguments arguments = settings.arguments; | ||||
|               final ConfigScreenArguments arguments = settings.arguments == null | ||||
|                   ? ConfigScreenArguments() | ||||
|                   : settings.arguments; | ||||
|               title = 'Configuration'; | ||||
|               builder = (BuildContext context) => | ||||
|                   Config(arguments.connectionConfig, arguments.privateKey); | ||||
|  |  | |||
|  | @ -4,25 +4,32 @@ | |||
| /// | ||||
| // 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 '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')) | ||||
|   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 | ||||
|   ; | ||||
|     ..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); | ||||
|   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)); | ||||
|   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._(); | ||||
|  | @ -32,30 +39,36 @@ class Timestamp extends $pb.GeneratedMessage { | |||
|   static Timestamp _defaultInstance; | ||||
| 
 | ||||
|   Int64 get seconds => $_getI64(0); | ||||
|   set seconds(Int64 v) { $_setInt64(0, v); } | ||||
|   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); } | ||||
|   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; | ||||
|     } | ||||
|   /// 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; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -11,4 +11,3 @@ const Timestamp$json = const { | |||
|     const {'1': 'nanos', '3': 2, '4': 1, '5': 5, '10': 'nanos'}, | ||||
|   ], | ||||
| }; | ||||
| 
 | ||||
|  |  | |||
|  | @ -29,7 +29,14 @@ 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 { | ||||
|       '1': 'credential', | ||||
|       '3': 2, | ||||
|       '4': 1, | ||||
|       '5': 11, | ||||
|       '6': '.selfpass.credentials.CredentialRequest', | ||||
|       '10': 'credential' | ||||
|     }, | ||||
|   ], | ||||
| }; | ||||
| 
 | ||||
|  | @ -48,8 +55,22 @@ 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': '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'}, | ||||
|  | @ -61,8 +82,22 @@ 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': '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'}, | ||||
|  | @ -87,4 +122,3 @@ const CredentialRequest$json = const { | |||
|     const {'1': 'otp_secret', '3': 8, '4': 1, '5': 9, '10': 'otpSecret'}, | ||||
|   ], | ||||
| }; | ||||
| 
 | ||||
|  |  | |||
|  | @ -12,11 +12,17 @@ class Config implements ConfigRepo { | |||
|   static const _keyConnectionConfig = "connection_config"; | ||||
|   static const _keyPassword = "password"; | ||||
|   final FlutterSecureStorage _storage = FlutterSecureStorage(); | ||||
|   var _passwordMatched = false; | ||||
|   bool _passwordMatched = false; | ||||
|   String _password; | ||||
| 
 | ||||
|   String get password { | ||||
|     _checkIfPasswordMatched(); | ||||
|     return _password; | ||||
|   } | ||||
| 
 | ||||
|   Future<void> setPrivateKey(String key) { | ||||
|     _checkIfPasswordMatched(); | ||||
|     return _storage.write(key: _keyPrivateKey, value: key); | ||||
|     return _storage.write(key: _keyPrivateKey, value: key.replaceAll('-', '')); | ||||
|   } | ||||
| 
 | ||||
|   Future<String> get privateKey { | ||||
|  | @ -26,6 +32,7 @@ class Config implements ConfigRepo { | |||
| 
 | ||||
|   Future<void> setPassword(String password) { | ||||
|     _checkIfPasswordMatched(); | ||||
|     _password = password; | ||||
|     return _storage.write( | ||||
|         key: _keyPassword, value: crypto.hashPassword(password)); | ||||
|   } | ||||
|  | @ -42,9 +49,16 @@ class Config implements ConfigRepo { | |||
|     return false; | ||||
|   } | ||||
| 
 | ||||
|   Future<bool> matchesPasswordHash(String password) async => | ||||
|       _passwordMatched = crypto.matchHashedPassword( | ||||
|           await _storage.read(key: _keyPassword), password); | ||||
|   Future<bool> matchesPasswordHash(String password) async { | ||||
|     _passwordMatched = crypto.matchHashedPassword( | ||||
|         await _storage.read(key: _keyPassword), password); | ||||
| 
 | ||||
|     if (_passwordMatched) { | ||||
|       _password = password; | ||||
|     } | ||||
| 
 | ||||
|     return _passwordMatched; | ||||
|   } | ||||
| 
 | ||||
|   Future<void> setConnectionConfig(ConnectionConfig config) { | ||||
|     _checkIfPasswordMatched(); | ||||
|  |  | |||
|  | @ -105,10 +105,8 @@ class _AuthenticationState extends State<Authentication> { | |||
|     return () async { | ||||
|       if (await _passwordIsSet) { | ||||
|         if (await _config.matchesPasswordHash(_passwordController.text)) { | ||||
|           Navigator.of(context).pushReplacementNamed( | ||||
|             '/home', | ||||
|             arguments: await _config.connectionConfig, | ||||
|           ); | ||||
|           Navigator.of(context).pushReplacementNamed('/home', | ||||
|               arguments: await _config.connectionConfig); | ||||
|           return; | ||||
|         } | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,5 +1,6 @@ | |||
| import 'package:flutter/cupertino.dart'; | ||||
| import 'package:flutter/services.dart'; | ||||
| import 'package:otp/otp.dart'; | ||||
| 
 | ||||
| import '../types/credential.dart' as types; | ||||
| 
 | ||||
|  | @ -19,17 +20,14 @@ class _CredentialState extends State<Credential> { | |||
|   Map<String, _FieldBuildConfig> _fieldMap; | ||||
|   types.Credential _credential; | ||||
| 
 | ||||
|   _CredentialState(this._credential) : super() { | ||||
|     _controllers = _CredentialControllers.fromCredential(_credential); | ||||
|     _fieldMap = _buildFieldMap(_controllers, _credential); | ||||
|   } | ||||
|   _CredentialState(this._credential) : super(); | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return CupertinoPageScaffold( | ||||
|       navigationBar: CupertinoNavigationBar(), | ||||
|       child: Container( | ||||
|         margin: const EdgeInsets.only(top: 30, left: 30), | ||||
|         padding: const EdgeInsets.only(top: 15, bottom: 30, left: 30), | ||||
|         child: ListView( | ||||
|           children: _buildFieldRows(context), | ||||
|         ), | ||||
|  | @ -43,12 +41,12 @@ class _CredentialState extends State<Credential> { | |||
|   ) { | ||||
|     final fieldMap = { | ||||
|       'Id:': _FieldBuildConfig(mutable: false, text: credential.meta.id), | ||||
|       'Created: ': _FieldBuildConfig( | ||||
|       'Created:': _FieldBuildConfig( | ||||
|         mutable: false, | ||||
|         copyable: false, | ||||
|         text: credential.meta.createdAt.toString(), | ||||
|       ), | ||||
|       'Updated: ': _FieldBuildConfig( | ||||
|       'Updated:': _FieldBuildConfig( | ||||
|         mutable: false, | ||||
|         copyable: false, | ||||
|         text: credential.meta.updatedAt.toString(), | ||||
|  | @ -69,15 +67,26 @@ class _CredentialState extends State<Credential> { | |||
|       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<Widget> _buildFieldRows(BuildContext context) { | ||||
|     List<Widget> rows = []; | ||||
| 
 | ||||
|     _controllers = _CredentialControllers.fromCredential(_credential); | ||||
|     _fieldMap = _buildFieldMap(_controllers, _credential); | ||||
| 
 | ||||
|     _fieldMap.forEach((key, value) { | ||||
|       rows.add(Container( | ||||
|         margin: EdgeInsets.only(top: 10), | ||||
|         margin: EdgeInsets.only(top: 2.5), | ||||
|         child: Text(key, style: TextStyle(fontWeight: FontWeight.w600)), | ||||
|       )); | ||||
| 
 | ||||
|  | @ -85,20 +94,27 @@ class _CredentialState extends State<Credential> { | |||
|         Expanded( | ||||
|           flex: 3, | ||||
|           child: value.mutable | ||||
|               ? TextField(maxLines: 1, controller: value.controller) | ||||
|               ? TextField( | ||||
|                   maxLines: 1, | ||||
|                   controller: value.controller, | ||||
|                   obscure: value.obscured) | ||||
|               : Container( | ||||
|                   margin: EdgeInsets.symmetric(vertical: 10), | ||||
|                   child: Text(value.text), | ||||
|                 ), | ||||
|                   child: Text(value.text)), | ||||
|         ), | ||||
|       ]; | ||||
| 
 | ||||
|       if (value.copyable) { | ||||
|         widgets.add(Flexible( | ||||
|         widgets.add(Expanded( | ||||
|           child: CupertinoButton( | ||||
|             child: Text('Copy'), | ||||
|             child: Text(value.otp ? 'OTP' : 'Copy'), | ||||
|             onPressed: () => Clipboard.setData(ClipboardData( | ||||
|               text: value.mutable ? value.controller.text : value.text, | ||||
|               text: value.otp | ||||
|                   ? OTP | ||||
|                       .generateTOTPCode(value.controller.text, | ||||
|                           DateTime.now().millisecondsSinceEpoch) | ||||
|                       .toString() | ||||
|                   : value.mutable ? value.controller.text : value.text, | ||||
|             )), | ||||
|           ), | ||||
|         )); | ||||
|  | @ -116,10 +132,14 @@ class _FieldBuildConfig { | |||
|   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, | ||||
|   }); | ||||
|  | @ -151,5 +171,7 @@ class _CredentialControllers { | |||
|         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), | ||||
|       ); | ||||
| } | ||||
|  |  | |||
|  | @ -4,6 +4,8 @@ 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 { | ||||
|  | @ -20,10 +22,39 @@ class Credentials extends StatelessWidget { | |||
|   } | ||||
| 
 | ||||
|   Map<String, GestureTapCallback> _buildTappableText(BuildContext context) { | ||||
|     var makeOnTapHandler = (String id) => () async { | ||||
|           final credential = | ||||
|               await Provider.of<CredentialsRepo>(context).get(id); | ||||
|           Navigator.of(context).pushNamed('/credential', arguments: credential); | ||||
|     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<ConfigRepo>(context); | ||||
|           final client = Provider.of<CredentialsRepo>(context); | ||||
| 
 | ||||
|           final Future<String> 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<String, GestureTapCallback> tappableText = {}; | ||||
|  |  | |||
|  | @ -91,6 +91,7 @@ class _HomeState extends State<Home> { | |||
|   GestureTapCallback _makeConfigOnTapHandler(BuildContext context) { | ||||
|     return () async => Navigator.of(context).pushNamed('/config', | ||||
|         arguments: ConfigScreenArguments( | ||||
|             await _config.connectionConfig, await _config.privateKey)); | ||||
|             connectionConfig: await _config.connectionConfig, | ||||
|             privateKey: await _config.privateKey)); | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -15,6 +15,7 @@ abstract class ConfigRepo { | |||
|   Future<void> setPrivateKey(String key); | ||||
|   Future<String> get privateKey; | ||||
| 
 | ||||
|   String get password; | ||||
|   Future<void> setPassword(String password); | ||||
|   Future<bool> get passwordSet; | ||||
|   Future<bool> matchesPasswordHash(String password); | ||||
|  |  | |||
|  | @ -4,5 +4,5 @@ class ConfigScreenArguments { | |||
|   final ConnectionConfig connectionConfig; | ||||
|   final String privateKey; | ||||
| 
 | ||||
|   const ConfigScreenArguments(this.connectionConfig, this.privateKey); | ||||
|   const ConfigScreenArguments({this.connectionConfig, this.privateKey}); | ||||
| } | ||||
|  |  | |||
|  | @ -1,11 +1,18 @@ | |||
| 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<int>.generate(16, (_) => random.nextInt(256)); | ||||
|   final saltInts = | ||||
|       List<int>.generate(saltSize, (_) => random.nextInt(saltIntMax)); | ||||
|   final salt = base64.encode(saltInts); | ||||
| 
 | ||||
|   return Crypt.sha256(password, salt: salt).toString(); | ||||
|  | @ -13,3 +20,23 @@ String hashPassword(String password) { | |||
| 
 | ||||
| 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; | ||||
|  |  | |||
|  | @ -7,6 +7,7 @@ typedef OnSubmittedBuilder = ValueChanged<String> Function( | |||
| class TextField extends StatelessWidget { | ||||
|   final OnSubmittedBuilder onSubmittedBuilder; | ||||
|   final TextEditingController controller; | ||||
|   final OverlayVisibilityMode clearButtonMode; | ||||
|   final Widget prefix; | ||||
|   final Widget suffix; | ||||
|   final bool obscure; | ||||
|  | @ -25,6 +26,7 @@ class TextField extends StatelessWidget { | |||
|     this.autocorrect = false, | ||||
|     this.prefix, | ||||
|     this.suffix, | ||||
|     this.clearButtonMode = OverlayVisibilityMode.editing, | ||||
|   }); | ||||
| 
 | ||||
|   @override | ||||
|  | @ -36,7 +38,7 @@ class TextField extends StatelessWidget { | |||
|           border: Border.all(color: CupertinoColors.black), | ||||
|           borderRadius: const BorderRadius.all(Radius.circular(5.0)), | ||||
|         ), | ||||
|         clearButtonMode: OverlayVisibilityMode.editing, | ||||
|         clearButtonMode: clearButtonMode, | ||||
|         textAlign: TextAlign.start, | ||||
|         onSubmitted: this.onSubmittedBuilder != null | ||||
|             ? onSubmittedBuilder(context) | ||||
|  |  | |||
							
								
								
									
										61
									
								
								pubspec.lock
									
									
									
									
									
								
							
							
						
						|  | @ -1,5 +1,5 @@ | |||
| # Generated by pub | ||||
| # See https://www.dartlang.org/tools/pub/glossary#lockfile | ||||
| # See https://dart.dev/tools/pub/glossary#lockfile | ||||
| packages: | ||||
|   _discoveryapis_commons: | ||||
|     dependency: transitive | ||||
|  | @ -8,13 +8,34 @@ packages: | |||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "0.1.8+1" | ||||
|   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.1.0" | ||||
|     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: | ||||
|  | @ -64,6 +85,13 @@ packages: | |||
|       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: | ||||
|  | @ -144,6 +172,20 @@ packages: | |||
|       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: | ||||
|  | @ -157,7 +199,14 @@ packages: | |||
|       name: pedantic | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "1.5.0" | ||||
|     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: | ||||
|  | @ -178,7 +227,7 @@ packages: | |||
|       name: quiver | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "2.0.2" | ||||
|     version: "2.0.3" | ||||
|   sky_engine: | ||||
|     dependency: transitive | ||||
|     description: flutter | ||||
|  | @ -225,7 +274,7 @@ packages: | |||
|       name: test_api | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "0.2.4" | ||||
|     version: "0.2.5" | ||||
|   typed_data: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|  | @ -241,4 +290,4 @@ packages: | |||
|     source: hosted | ||||
|     version: "2.0.8" | ||||
| sdks: | ||||
|   dart: ">=2.2.0 <3.0.0" | ||||
|   dart: ">=2.2.2 <3.0.0" | ||||
|  |  | |||
|  | @ -30,15 +30,17 @@ dependencies: | |||
| 
 | ||||
|   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: | ||||
| 
 | ||||
|  |  | |||
|  | @ -8,12 +8,12 @@ | |||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_test/flutter_test.dart'; | ||||
| 
 | ||||
| import 'package:selfpass_mobile/main.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(MyApp()); | ||||
|     await tester.pumpWidget(Selfpass()); | ||||
| 
 | ||||
|     // Verify that our counter starts at 0. | ||||
|     expect(find.text('0'), findsOneWidget); | ||||
|  |  | |||