Add decryption of credential encrypted fields; refactored config;
add app icon
|
@ -33,7 +33,7 @@ android {
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
// 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
|
minSdkVersion 16
|
||||||
targetSdkVersion 28
|
targetSdkVersion 28
|
||||||
versionCode flutterVersionCode.toInteger()
|
versionCode flutterVersionCode.toInteger()
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<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
|
<!-- Flutter needs it to communicate with the running application
|
||||||
to allow setting breakpoints, to provide hot reload, etc.
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
-->
|
-->
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<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
|
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
|
||||||
calls FlutterMain.startInitialization(this); in its onCreate method.
|
calls FlutterMain.startInitialization(this); in its onCreate method.
|
||||||
|
@ -8,7 +8,7 @@
|
||||||
FlutterApplication and put your custom class here. -->
|
FlutterApplication and put your custom class here. -->
|
||||||
<application
|
<application
|
||||||
android:name="io.flutter.app.FlutterApplication"
|
android:name="io.flutter.app.FlutterApplication"
|
||||||
android:label="selfpass_mobile"
|
android:label="selfpass_client"
|
||||||
android:icon="@mipmap/ic_launcher">
|
android:icon="@mipmap/ic_launcher">
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<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
|
<!-- Flutter needs it to communicate with the running application
|
||||||
to allow setting breakpoints, to provide hot reload, etc.
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
-->
|
-->
|
||||||
|
|
|
@ -4,12 +4,12 @@ PODS:
|
||||||
- Flutter
|
- Flutter
|
||||||
|
|
||||||
DEPENDENCIES:
|
DEPENDENCIES:
|
||||||
- Flutter (from `.symlinks/flutter/ios`)
|
- Flutter (from `.symlinks/flutter/ios-release`)
|
||||||
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
|
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
|
||||||
|
|
||||||
EXTERNAL SOURCES:
|
EXTERNAL SOURCES:
|
||||||
Flutter:
|
Flutter:
|
||||||
:path: ".symlinks/flutter/ios"
|
:path: ".symlinks/flutter/ios-release"
|
||||||
flutter_secure_storage:
|
flutter_secure_storage:
|
||||||
:path: ".symlinks/plugins/flutter_secure_storage/ios"
|
:path: ".symlinks/plugins/flutter_secure_storage/ios"
|
||||||
|
|
||||||
|
|
|
@ -253,7 +253,7 @@
|
||||||
);
|
);
|
||||||
inputPaths = (
|
inputPaths = (
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh",
|
"${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";
|
name = "[CP] Embed Pods Frameworks";
|
||||||
outputPaths = (
|
outputPaths = (
|
||||||
|
|
After Width: | Height: | Size: 37 KiB |
After Width: | Height: | Size: 37 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 163 B |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 5.3 KiB |
After Width: | Height: | Size: 233 B |
After Width: | Height: | Size: 276 B |
After Width: | Height: | Size: 394 B |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 549 B |
After Width: | Height: | Size: 600 B |
After Width: | Height: | Size: 601 B |
After Width: | Height: | Size: 695 B |
After Width: | Height: | Size: 893 B |
After Width: | Height: | Size: 1.0 KiB |
|
@ -3,116 +3,134 @@
|
||||||
{
|
{
|
||||||
"size" : "20x20",
|
"size" : "20x20",
|
||||||
"idiom" : "iphone",
|
"idiom" : "iphone",
|
||||||
"filename" : "Icon-App-20x20@2x.png",
|
"filename" : "40.png",
|
||||||
"scale" : "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "20x20",
|
"size" : "20x20",
|
||||||
"idiom" : "iphone",
|
"idiom" : "iphone",
|
||||||
"filename" : "Icon-App-20x20@3x.png",
|
"filename" : "60.png",
|
||||||
"scale" : "3x"
|
"scale" : "3x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "29x29",
|
"size" : "29x29",
|
||||||
"idiom" : "iphone",
|
"idiom" : "iphone",
|
||||||
"filename" : "Icon-App-29x29@1x.png",
|
"filename" : "29.png",
|
||||||
"scale" : "1x"
|
"scale" : "1x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "29x29",
|
"size" : "29x29",
|
||||||
"idiom" : "iphone",
|
"idiom" : "iphone",
|
||||||
"filename" : "Icon-App-29x29@2x.png",
|
"filename" : "58.png",
|
||||||
"scale" : "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "29x29",
|
"size" : "29x29",
|
||||||
"idiom" : "iphone",
|
"idiom" : "iphone",
|
||||||
"filename" : "Icon-App-29x29@3x.png",
|
"filename" : "87.png",
|
||||||
"scale" : "3x"
|
"scale" : "3x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "40x40",
|
"size" : "40x40",
|
||||||
"idiom" : "iphone",
|
"idiom" : "iphone",
|
||||||
"filename" : "Icon-App-40x40@2x.png",
|
"filename" : "80.png",
|
||||||
"scale" : "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "40x40",
|
"size" : "40x40",
|
||||||
"idiom" : "iphone",
|
"idiom" : "iphone",
|
||||||
"filename" : "Icon-App-40x40@3x.png",
|
"filename" : "120.png",
|
||||||
"scale" : "3x"
|
"scale" : "3x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "60x60",
|
"size" : "57x57",
|
||||||
"idiom" : "iphone",
|
"idiom" : "iphone",
|
||||||
"filename" : "Icon-App-60x60@2x.png",
|
"filename" : "57.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "57x57",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "114.png",
|
||||||
"scale" : "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "60x60",
|
"size" : "60x60",
|
||||||
"idiom" : "iphone",
|
"idiom" : "iphone",
|
||||||
"filename" : "Icon-App-60x60@3x.png",
|
"filename" : "120.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "60x60",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "180.png",
|
||||||
"scale" : "3x"
|
"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",
|
"size" : "1024x1024",
|
||||||
"idiom" : "ios-marketing",
|
"idiom" : "ios-marketing",
|
||||||
"filename" : "Icon-App-1024x1024@1x.png",
|
"filename" : "1024.png",
|
||||||
"scale" : "1x"
|
"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" : {
|
"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.0 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 |
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,7 +13,7 @@
|
||||||
<key>CFBundleInfoDictionaryVersion</key>
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
<string>6.0</string>
|
<string>6.0</string>
|
||||||
<key>CFBundleName</key>
|
<key>CFBundleName</key>
|
||||||
<string>selfpass_mobile</string>
|
<string>selfpass_client</string>
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
|
|
|
@ -60,7 +60,9 @@ class Selfpass extends StatelessWidget {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case '/config':
|
case '/config':
|
||||||
final ConfigScreenArguments arguments = settings.arguments;
|
final ConfigScreenArguments arguments = settings.arguments == null
|
||||||
|
? ConfigScreenArguments()
|
||||||
|
: settings.arguments;
|
||||||
title = 'Configuration';
|
title = 'Configuration';
|
||||||
builder = (BuildContext context) =>
|
builder = (BuildContext context) =>
|
||||||
Config(arguments.connectionConfig, arguments.privateKey);
|
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
|
// 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:fixnum/fixnum.dart';
|
||||||
import 'package:protobuf/protobuf.dart' as $pb;
|
import 'package:protobuf/protobuf.dart' as $pb;
|
||||||
|
|
||||||
import 'dart:core' as $core show DateTime, Duration;
|
import 'dart:core' as $core show DateTime, Duration;
|
||||||
|
|
||||||
class Timestamp extends $pb.GeneratedMessage {
|
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')
|
..aInt64(1, 'seconds')
|
||||||
..a<$core.int>(2, 'nanos', $pb.PbFieldType.O3)
|
..a<$core.int>(2, 'nanos', $pb.PbFieldType.O3)
|
||||||
..hasRequiredFields = false
|
..hasRequiredFields = false;
|
||||||
;
|
|
||||||
|
|
||||||
Timestamp._() : super();
|
Timestamp._() : super();
|
||||||
factory Timestamp() => create();
|
factory Timestamp() => create();
|
||||||
factory Timestamp.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
factory Timestamp.fromBuffer($core.List<$core.int> i,
|
||||||
factory Timestamp.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
[$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 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;
|
$pb.BuilderInfo get info_ => _i;
|
||||||
@$core.pragma('dart2js:noInline')
|
@$core.pragma('dart2js:noInline')
|
||||||
static Timestamp create() => Timestamp._();
|
static Timestamp create() => Timestamp._();
|
||||||
|
@ -32,30 +39,36 @@ class Timestamp extends $pb.GeneratedMessage {
|
||||||
static Timestamp _defaultInstance;
|
static Timestamp _defaultInstance;
|
||||||
|
|
||||||
Int64 get seconds => $_getI64(0);
|
Int64 get seconds => $_getI64(0);
|
||||||
set seconds(Int64 v) { $_setInt64(0, v); }
|
set seconds(Int64 v) {
|
||||||
|
$_setInt64(0, v);
|
||||||
|
}
|
||||||
|
|
||||||
$core.bool hasSeconds() => $_has(0);
|
$core.bool hasSeconds() => $_has(0);
|
||||||
void clearSeconds() => clearField(1);
|
void clearSeconds() => clearField(1);
|
||||||
|
|
||||||
$core.int get nanos => $_get(1, 0);
|
$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);
|
$core.bool hasNanos() => $_has(1);
|
||||||
void clearNanos() => clearField(2);
|
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].
|
/// Converts an instance to [DateTime].
|
||||||
///
|
///
|
||||||
/// Time zone information will not be preserved.
|
/// The result is in UTC time zone and has microsecond precision, as
|
||||||
static Timestamp fromDateTime($core.DateTime dateTime) {
|
/// [DateTime] does not support nanosecond precision.
|
||||||
$core.int micros = dateTime.microsecondsSinceEpoch;
|
$core.DateTime toDateTime() => $core.DateTime.fromMicrosecondsSinceEpoch(
|
||||||
return Timestamp()
|
seconds.toInt() * $core.Duration.microsecondsPerSecond + nanos ~/ 1000,
|
||||||
..seconds = Int64(micros ~/ $core.Duration.microsecondsPerSecond)
|
isUtc: true);
|
||||||
..nanos = (micros % $core.Duration.microsecondsPerSecond).toInt() * 1000;
|
|
||||||
}
|
/// 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'},
|
const {'1': 'nanos', '3': 2, '4': 1, '5': 5, '10': 'nanos'},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,14 @@ const UpdateRequest$json = const {
|
||||||
'1': 'UpdateRequest',
|
'1': 'UpdateRequest',
|
||||||
'2': const [
|
'2': const [
|
||||||
const {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'},
|
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',
|
'1': 'Metadata',
|
||||||
'2': const [
|
'2': const [
|
||||||
const {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'},
|
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 {
|
||||||
const {'1': 'updated_at', '3': 3, '4': 1, '5': 11, '6': '.google.protobuf.Timestamp', '10': 'updatedAt'},
|
'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': 'primary', '3': 4, '4': 1, '5': 9, '10': 'primary'},
|
||||||
const {'1': 'source_host', '3': 5, '4': 1, '5': 9, '10': 'sourceHost'},
|
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': 'login_url', '3': 6, '4': 1, '5': 9, '10': 'loginUrl'},
|
||||||
|
@ -61,8 +82,22 @@ const Credential$json = const {
|
||||||
'1': 'Credential',
|
'1': 'Credential',
|
||||||
'2': const [
|
'2': const [
|
||||||
const {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'},
|
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 {
|
||||||
const {'1': 'updated_at', '3': 3, '4': 1, '5': 11, '6': '.google.protobuf.Timestamp', '10': 'updatedAt'},
|
'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': 'primary', '3': 4, '4': 1, '5': 9, '10': 'primary'},
|
||||||
const {'1': 'username', '3': 5, '4': 1, '5': 9, '10': 'username'},
|
const {'1': 'username', '3': 5, '4': 1, '5': 9, '10': 'username'},
|
||||||
const {'1': 'email', '3': 6, '4': 1, '5': 9, '10': 'email'},
|
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'},
|
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 _keyConnectionConfig = "connection_config";
|
||||||
static const _keyPassword = "password";
|
static const _keyPassword = "password";
|
||||||
final FlutterSecureStorage _storage = FlutterSecureStorage();
|
final FlutterSecureStorage _storage = FlutterSecureStorage();
|
||||||
var _passwordMatched = false;
|
bool _passwordMatched = false;
|
||||||
|
String _password;
|
||||||
|
|
||||||
|
String get password {
|
||||||
|
_checkIfPasswordMatched();
|
||||||
|
return _password;
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> setPrivateKey(String key) {
|
Future<void> setPrivateKey(String key) {
|
||||||
_checkIfPasswordMatched();
|
_checkIfPasswordMatched();
|
||||||
return _storage.write(key: _keyPrivateKey, value: key);
|
return _storage.write(key: _keyPrivateKey, value: key.replaceAll('-', ''));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> get privateKey {
|
Future<String> get privateKey {
|
||||||
|
@ -26,6 +32,7 @@ class Config implements ConfigRepo {
|
||||||
|
|
||||||
Future<void> setPassword(String password) {
|
Future<void> setPassword(String password) {
|
||||||
_checkIfPasswordMatched();
|
_checkIfPasswordMatched();
|
||||||
|
_password = password;
|
||||||
return _storage.write(
|
return _storage.write(
|
||||||
key: _keyPassword, value: crypto.hashPassword(password));
|
key: _keyPassword, value: crypto.hashPassword(password));
|
||||||
}
|
}
|
||||||
|
@ -42,9 +49,16 @@ class Config implements ConfigRepo {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> matchesPasswordHash(String password) async =>
|
Future<bool> matchesPasswordHash(String password) async {
|
||||||
_passwordMatched = crypto.matchHashedPassword(
|
_passwordMatched = crypto.matchHashedPassword(
|
||||||
await _storage.read(key: _keyPassword), password);
|
await _storage.read(key: _keyPassword), password);
|
||||||
|
|
||||||
|
if (_passwordMatched) {
|
||||||
|
_password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _passwordMatched;
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> setConnectionConfig(ConnectionConfig config) {
|
Future<void> setConnectionConfig(ConnectionConfig config) {
|
||||||
_checkIfPasswordMatched();
|
_checkIfPasswordMatched();
|
||||||
|
|
|
@ -105,10 +105,8 @@ class _AuthenticationState extends State<Authentication> {
|
||||||
return () async {
|
return () async {
|
||||||
if (await _passwordIsSet) {
|
if (await _passwordIsSet) {
|
||||||
if (await _config.matchesPasswordHash(_passwordController.text)) {
|
if (await _config.matchesPasswordHash(_passwordController.text)) {
|
||||||
Navigator.of(context).pushReplacementNamed(
|
Navigator.of(context).pushReplacementNamed('/home',
|
||||||
'/home',
|
arguments: await _config.connectionConfig);
|
||||||
arguments: await _config.connectionConfig,
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:otp/otp.dart';
|
||||||
|
|
||||||
import '../types/credential.dart' as types;
|
import '../types/credential.dart' as types;
|
||||||
|
|
||||||
|
@ -19,17 +20,14 @@ class _CredentialState extends State<Credential> {
|
||||||
Map<String, _FieldBuildConfig> _fieldMap;
|
Map<String, _FieldBuildConfig> _fieldMap;
|
||||||
types.Credential _credential;
|
types.Credential _credential;
|
||||||
|
|
||||||
_CredentialState(this._credential) : super() {
|
_CredentialState(this._credential) : super();
|
||||||
_controllers = _CredentialControllers.fromCredential(_credential);
|
|
||||||
_fieldMap = _buildFieldMap(_controllers, _credential);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return CupertinoPageScaffold(
|
return CupertinoPageScaffold(
|
||||||
navigationBar: CupertinoNavigationBar(),
|
navigationBar: CupertinoNavigationBar(),
|
||||||
child: Container(
|
child: Container(
|
||||||
margin: const EdgeInsets.only(top: 30, left: 30),
|
padding: const EdgeInsets.only(top: 15, bottom: 30, left: 30),
|
||||||
child: ListView(
|
child: ListView(
|
||||||
children: _buildFieldRows(context),
|
children: _buildFieldRows(context),
|
||||||
),
|
),
|
||||||
|
@ -43,12 +41,12 @@ class _CredentialState extends State<Credential> {
|
||||||
) {
|
) {
|
||||||
final fieldMap = {
|
final fieldMap = {
|
||||||
'Id:': _FieldBuildConfig(mutable: false, text: credential.meta.id),
|
'Id:': _FieldBuildConfig(mutable: false, text: credential.meta.id),
|
||||||
'Created: ': _FieldBuildConfig(
|
'Created:': _FieldBuildConfig(
|
||||||
mutable: false,
|
mutable: false,
|
||||||
copyable: false,
|
copyable: false,
|
||||||
text: credential.meta.createdAt.toString(),
|
text: credential.meta.createdAt.toString(),
|
||||||
),
|
),
|
||||||
'Updated: ': _FieldBuildConfig(
|
'Updated:': _FieldBuildConfig(
|
||||||
mutable: false,
|
mutable: false,
|
||||||
copyable: false,
|
copyable: false,
|
||||||
text: credential.meta.updatedAt.toString(),
|
text: credential.meta.updatedAt.toString(),
|
||||||
|
@ -69,15 +67,26 @@ class _CredentialState extends State<Credential> {
|
||||||
fieldMap['Email:'] = _FieldBuildConfig(controller: controllers.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;
|
return fieldMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Widget> _buildFieldRows(BuildContext context) {
|
List<Widget> _buildFieldRows(BuildContext context) {
|
||||||
List<Widget> rows = [];
|
List<Widget> rows = [];
|
||||||
|
|
||||||
|
_controllers = _CredentialControllers.fromCredential(_credential);
|
||||||
|
_fieldMap = _buildFieldMap(_controllers, _credential);
|
||||||
|
|
||||||
_fieldMap.forEach((key, value) {
|
_fieldMap.forEach((key, value) {
|
||||||
rows.add(Container(
|
rows.add(Container(
|
||||||
margin: EdgeInsets.only(top: 10),
|
margin: EdgeInsets.only(top: 2.5),
|
||||||
child: Text(key, style: TextStyle(fontWeight: FontWeight.w600)),
|
child: Text(key, style: TextStyle(fontWeight: FontWeight.w600)),
|
||||||
));
|
));
|
||||||
|
|
||||||
|
@ -85,20 +94,27 @@ class _CredentialState extends State<Credential> {
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 3,
|
flex: 3,
|
||||||
child: value.mutable
|
child: value.mutable
|
||||||
? TextField(maxLines: 1, controller: value.controller)
|
? TextField(
|
||||||
|
maxLines: 1,
|
||||||
|
controller: value.controller,
|
||||||
|
obscure: value.obscured)
|
||||||
: Container(
|
: Container(
|
||||||
margin: EdgeInsets.symmetric(vertical: 10),
|
margin: EdgeInsets.symmetric(vertical: 10),
|
||||||
child: Text(value.text),
|
child: Text(value.text)),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
if (value.copyable) {
|
if (value.copyable) {
|
||||||
widgets.add(Flexible(
|
widgets.add(Expanded(
|
||||||
child: CupertinoButton(
|
child: CupertinoButton(
|
||||||
child: Text('Copy'),
|
child: Text(value.otp ? 'OTP' : 'Copy'),
|
||||||
onPressed: () => Clipboard.setData(ClipboardData(
|
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 String text;
|
||||||
final bool mutable;
|
final bool mutable;
|
||||||
final bool copyable;
|
final bool copyable;
|
||||||
|
final bool obscured;
|
||||||
|
final bool otp;
|
||||||
|
|
||||||
const _FieldBuildConfig({
|
const _FieldBuildConfig({
|
||||||
this.mutable = true,
|
this.mutable = true,
|
||||||
this.copyable = true,
|
this.copyable = true,
|
||||||
|
this.obscured = false,
|
||||||
|
this.otp = false,
|
||||||
this.controller,
|
this.controller,
|
||||||
this.text,
|
this.text,
|
||||||
});
|
});
|
||||||
|
@ -151,5 +171,7 @@ class _CredentialControllers {
|
||||||
tag: TextEditingController(text: credential.meta.tag),
|
tag: TextEditingController(text: credential.meta.tag),
|
||||||
username: TextEditingController(text: credential.username),
|
username: TextEditingController(text: credential.username),
|
||||||
email: TextEditingController(text: credential.email),
|
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/abstracts.dart';
|
||||||
import '../types/credential.dart';
|
import '../types/credential.dart';
|
||||||
|
|
||||||
|
import '../utils/crypto.dart' as crypto;
|
||||||
|
|
||||||
import '../widgets/tappable_text_list.dart';
|
import '../widgets/tappable_text_list.dart';
|
||||||
|
|
||||||
class Credentials extends StatelessWidget {
|
class Credentials extends StatelessWidget {
|
||||||
|
@ -20,10 +22,39 @@ class Credentials extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, GestureTapCallback> _buildTappableText(BuildContext context) {
|
Map<String, GestureTapCallback> _buildTappableText(BuildContext context) {
|
||||||
var makeOnTapHandler = (String id) => () async {
|
final makeOnTapHandler = (String id) => () async {
|
||||||
final credential =
|
showCupertinoDialog(
|
||||||
await Provider.of<CredentialsRepo>(context).get(id);
|
context: context,
|
||||||
Navigator.of(context).pushNamed('/credential', arguments: credential);
|
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 = {};
|
Map<String, GestureTapCallback> tappableText = {};
|
||||||
|
|
|
@ -91,6 +91,7 @@ class _HomeState extends State<Home> {
|
||||||
GestureTapCallback _makeConfigOnTapHandler(BuildContext context) {
|
GestureTapCallback _makeConfigOnTapHandler(BuildContext context) {
|
||||||
return () async => Navigator.of(context).pushNamed('/config',
|
return () async => Navigator.of(context).pushNamed('/config',
|
||||||
arguments: ConfigScreenArguments(
|
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<void> setPrivateKey(String key);
|
||||||
Future<String> get privateKey;
|
Future<String> get privateKey;
|
||||||
|
|
||||||
|
String get password;
|
||||||
Future<void> setPassword(String password);
|
Future<void> setPassword(String password);
|
||||||
Future<bool> get passwordSet;
|
Future<bool> get passwordSet;
|
||||||
Future<bool> matchesPasswordHash(String password);
|
Future<bool> matchesPasswordHash(String password);
|
||||||
|
|
|
@ -4,5 +4,5 @@ class ConfigScreenArguments {
|
||||||
final ConnectionConfig connectionConfig;
|
final ConnectionConfig connectionConfig;
|
||||||
final String privateKey;
|
final String privateKey;
|
||||||
|
|
||||||
const ConfigScreenArguments(this.connectionConfig, this.privateKey);
|
const ConfigScreenArguments({this.connectionConfig, this.privateKey});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,18 @@
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:crypt/crypt.dart';
|
import 'package:crypt/crypt.dart';
|
||||||
|
import 'package:encrypt/encrypt.dart';
|
||||||
|
import 'package:password_hash/password_hash.dart';
|
||||||
|
|
||||||
String hashPassword(String password) {
|
String hashPassword(String password) {
|
||||||
|
const saltSize = 16;
|
||||||
|
const saltIntMax = 256;
|
||||||
|
|
||||||
final random = Random.secure();
|
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);
|
final salt = base64.encode(saltInts);
|
||||||
|
|
||||||
return Crypt.sha256(password, salt: salt).toString();
|
return Crypt.sha256(password, salt: salt).toString();
|
||||||
|
@ -13,3 +20,23 @@ String hashPassword(String password) {
|
||||||
|
|
||||||
bool matchHashedPassword(String hashedPassword, String password) =>
|
bool matchHashedPassword(String hashedPassword, String password) =>
|
||||||
Crypt(hashedPassword).match(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 {
|
class TextField extends StatelessWidget {
|
||||||
final OnSubmittedBuilder onSubmittedBuilder;
|
final OnSubmittedBuilder onSubmittedBuilder;
|
||||||
final TextEditingController controller;
|
final TextEditingController controller;
|
||||||
|
final OverlayVisibilityMode clearButtonMode;
|
||||||
final Widget prefix;
|
final Widget prefix;
|
||||||
final Widget suffix;
|
final Widget suffix;
|
||||||
final bool obscure;
|
final bool obscure;
|
||||||
|
@ -25,6 +26,7 @@ class TextField extends StatelessWidget {
|
||||||
this.autocorrect = false,
|
this.autocorrect = false,
|
||||||
this.prefix,
|
this.prefix,
|
||||||
this.suffix,
|
this.suffix,
|
||||||
|
this.clearButtonMode = OverlayVisibilityMode.editing,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -36,7 +38,7 @@ class TextField extends StatelessWidget {
|
||||||
border: Border.all(color: CupertinoColors.black),
|
border: Border.all(color: CupertinoColors.black),
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(5.0)),
|
borderRadius: const BorderRadius.all(Radius.circular(5.0)),
|
||||||
),
|
),
|
||||||
clearButtonMode: OverlayVisibilityMode.editing,
|
clearButtonMode: clearButtonMode,
|
||||||
textAlign: TextAlign.start,
|
textAlign: TextAlign.start,
|
||||||
onSubmitted: this.onSubmittedBuilder != null
|
onSubmitted: this.onSubmittedBuilder != null
|
||||||
? onSubmittedBuilder(context)
|
? onSubmittedBuilder(context)
|
||||||
|
|
61
pubspec.lock
|
@ -1,5 +1,5 @@
|
||||||
# Generated by pub
|
# Generated by pub
|
||||||
# See https://www.dartlang.org/tools/pub/glossary#lockfile
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
packages:
|
packages:
|
||||||
_discoveryapis_commons:
|
_discoveryapis_commons:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
|
@ -8,13 +8,34 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.8+1"
|
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:
|
async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: async
|
name: async
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
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:
|
boolean_selector:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -64,6 +85,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.2"
|
version: "0.1.2"
|
||||||
|
encrypt:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: encrypt
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.2.0"
|
||||||
fixnum:
|
fixnum:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -144,6 +172,20 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.6"
|
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:
|
path:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -157,7 +199,14 @@ packages:
|
||||||
name: pedantic
|
name: pedantic
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
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:
|
protobuf:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -178,7 +227,7 @@ packages:
|
||||||
name: quiver
|
name: quiver
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.2"
|
version: "2.0.3"
|
||||||
sky_engine:
|
sky_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
|
@ -225,7 +274,7 @@ packages:
|
||||||
name: test_api
|
name: test_api
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.4"
|
version: "0.2.5"
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -241,4 +290,4 @@ packages:
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.8"
|
version: "2.0.8"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=2.2.0 <3.0.0"
|
dart: ">=2.2.2 <3.0.0"
|
||||||
|
|
|
@ -30,15 +30,17 @@ dependencies:
|
||||||
|
|
||||||
crypt: ^1.0.7
|
crypt: ^1.0.7
|
||||||
flutter_secure_storage: ^3.2.1
|
flutter_secure_storage: ^3.2.1
|
||||||
|
password_hash: ^2.0.0
|
||||||
|
encrypt: ^3.2.0
|
||||||
|
|
||||||
|
otp: ^1.0.3
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
||||||
|
|
||||||
# For information on the generic Dart part of this file, see the
|
# For information on the generic Dart part of this file, see the
|
||||||
# following page: https://www.dartlang.org/tools/pub/pubspec
|
# following page: https://www.dartlang.org/tools/pub/pubspec
|
||||||
|
|
||||||
# The following section is specific to Flutter.
|
# The following section is specific to Flutter.
|
||||||
flutter:
|
flutter:
|
||||||
|
|
||||||
|
|
|
@ -8,12 +8,12 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
import 'package:selfpass_mobile/main.dart';
|
import 'package:selfpass_client/main.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
||||||
// Build our app and trigger a frame.
|
// Build our app and trigger a frame.
|
||||||
await tester.pumpWidget(MyApp());
|
await tester.pumpWidget(Selfpass());
|
||||||
|
|
||||||
// Verify that our counter starts at 0.
|
// Verify that our counter starts at 0.
|
||||||
expect(find.text('0'), findsOneWidget);
|
expect(find.text('0'), findsOneWidget);
|
||||||
|
|