diff --git a/.github/actions/prepare-flutter/action.yml b/.github/actions/prepare-flutter/action.yml new file mode 100644 index 0000000..96398ef --- /dev/null +++ b/.github/actions/prepare-flutter/action.yml @@ -0,0 +1,13 @@ +name: "Prepare flutter runner" +description: "Prepares a flutter project by installing dependencies and running build_runner" +inputs: + artifactory_key: + description: "Artifactory key" + required: true +runs: + using: "composite" + steps: + - name: Install Flutter + uses: subosito/flutter-action@v2 + with: + flutter-version: "3.32.1" \ No newline at end of file diff --git a/.github/workflows/build_dev.yaml b/.github/workflows/build_dev.yaml index 887f9b8..d17352d 100644 --- a/.github/workflows/build_dev.yaml +++ b/.github/workflows/build_dev.yaml @@ -67,7 +67,7 @@ jobs: uses: dart-lang/setup-dart@v1 - name: Prepare Flutter - uses: emdgroup/mtrust-urp/.github/actions/prepare-flutter@main + uses: ./.github/actions/prepare-flutter with: directory: "." diff --git a/.github/workflows/pr_dev.yaml b/.github/workflows/pr_dev.yaml index e9f47aa..dbbcdc9 100644 --- a/.github/workflows/pr_dev.yaml +++ b/.github/workflows/pr_dev.yaml @@ -24,7 +24,7 @@ jobs: uses: dart-lang/setup-dart@v1 - name: Prepare Flutter - uses: emdgroup/mtrust-urp/.github/actions/prepare-flutter@main + uses: ./.github/actions/prepare-flutter with: directory: example @@ -52,7 +52,7 @@ jobs: uses: dart-lang/setup-dart@v1 - name: Prepare Flutter - uses: emdgroup/mtrust-urp/.github/actions/prepare-flutter@main + uses: ./.github/actions/prepare-flutter with: directory: . diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index 1e8ec45..3f8d2b8 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -1,4 +1,13 @@ + + + + + + + + diff --git a/example/lib/main.dart b/example/lib/main.dart index 46006e1..ba543b6 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,4 +1,5 @@ import 'package:example/virtual_strategy.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:mtrust_sec_kit/mtrust_sec_kit.dart'; @@ -35,23 +36,30 @@ class MainApp extends StatefulWidget { class _MainAppState extends State { bool _canDismiss = true; - final UrpBleStrategy _bleStrategy = UrpBleStrategy(); + late final UrpBleStrategy _bleStrategy; - bool _useVirtual = false; + bool _useVirtual = kIsWeb; @override void initState() { + if (!kIsWeb) { + _bleStrategy = UrpBleStrategy(); + } + virtualStrategy.createVirtualReader(FoundDevice( name: "SEC-000123", type: UrpDeviceType.urpSec, address: "00:00:00:00:00:00", )); + super.initState(); } @override void dispose() { - _bleStrategy.dispose(); + if (!kIsWeb) { + _bleStrategy.dispose(); + } super.dispose(); } @@ -59,9 +67,8 @@ class _MainAppState extends State { Widget build(BuildContext context) { return LdPortal( child: Scaffold( - appBar: LdAppBar( - context: context, - title: const Text( + appBar: const LdAppBar( + title: Text( "SEC Kit Example", ), ), diff --git a/example/lib/virtual_strategy.dart b/example/lib/virtual_strategy.dart index 6ddec50..9fd908f 100644 --- a/example/lib/virtual_strategy.dart +++ b/example/lib/virtual_strategy.dart @@ -6,6 +6,16 @@ final virtualStrategy = UrpVirtualStrategy((UrpRequest request) async { final payload = UrpSecCommandWrapper.fromBuffer(request.payload); final result = switch (payload.deviceCommand.command) { (UrpSecCommand.urpSecPrime) => UrpResponse(), + (UrpSecCommand.urpSecGetModelInfo) => UrpResponse( + payload: UrpSecModels( + models: [ + UrpSecModelInfo( + modelId: 'Virtual Model', + version: '0.0.1', + ), + ], + ).writeToBuffer(), + ), (UrpSecCommand.urpSecStartMeasurement) => UrpResponse( payload: UrpSecSecureMeasurement( measurement: UrpSecMeasurement( diff --git a/example/pubspec.lock b/example/pubspec.lock index 41591da..1b1421f 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -21,26 +21,26 @@ packages: dependency: transitive description: name: async - sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" url: "https://pub.dev" source: hosted - version: "2.11.0" + version: "2.13.0" boolean_selector: dependency: transitive description: name: boolean_selector - sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" characters: dependency: transitive description: name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.0" checked_yaml: dependency: transitive description: @@ -61,18 +61,18 @@ packages: dependency: transitive description: name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" collection: dependency: transitive description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.19.1" crypto: dependency: transitive description: @@ -101,10 +101,10 @@ packages: dependency: transitive description: name: fake_async - sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.3" ffi: dependency: transitive description: @@ -191,10 +191,10 @@ packages: dependency: transitive description: name: flutter_sticky_header - sha256: "7f76d24d119424ca0c95c146b8627a457e8de8169b0d584f766c2c545db8f8be" + sha256: fb4fda6164ef3e5fc7ab73aba34aad253c17b7c6ecf738fa26f1a905b7d2d1e2 url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.8.0" flutter_svg: dependency: transitive description: @@ -229,14 +229,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.5.1" + go_router: + dependency: transitive + description: + name: go_router + sha256: f02fd7d2a4dc512fec615529824fdd217fecb3a3d3de68360293a551f21634b3 + url: "https://pub.dev" + source: hosted + version: "14.8.1" haptic_feedback: dependency: transitive description: name: haptic_feedback - sha256: ef6df60bd92ac75c87774c3300dd19732f5fbd81734371ea62196c3f55013408 + sha256: d3b5ad41858d18a35d317636c9cdc1cb8aef23c7b861ca5896273acc68e1a98a url: "https://pub.dev" source: hosted - version: "0.4.2" + version: "0.5.1+1" http: dependency: transitive description: @@ -265,10 +273,10 @@ packages: dependency: transitive description: name: intl - sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" url: "https://pub.dev" source: hosted - version: "0.19.0" + version: "0.20.2" jiffy: dependency: transitive description: @@ -289,18 +297,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" + sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" url: "https://pub.dev" source: hosted - version: "10.0.5" + version: "10.0.9" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.0.9" leak_tracker_testing: dependency: transitive description: @@ -321,10 +329,10 @@ packages: dependency: "direct main" description: name: liquid_flutter - sha256: "0cffcff4333789c569f400b04bf04009ba4a1cd2b4b733bb81cc3a8b112c902c" + sha256: "87b33e5262be709e0c61fa9c54e0fbb1736fdb7da5a5d8aed7c144605ed30eb9" url: "https://pub.dev" source: hosted - version: "19.1.0" + version: "22.0.4" logger: dependency: transitive description: @@ -341,14 +349,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" + lucide_icons_flutter: + dependency: transitive + description: + name: lucide_icons_flutter + sha256: "98b5935ab5caeeadfc6efc6649c776b8bb9ffeb4879a1e24d0b78e4b104f40a1" + url: "https://pub.dev" + source: hosted + version: "3.0.3" matcher: dependency: transitive description: name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 url: "https://pub.dev" source: hosted - version: "0.12.16+1" + version: "0.12.17" material_color_utilities: dependency: transitive description: @@ -361,10 +377,10 @@ packages: dependency: transitive description: name: meta - sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted - version: "1.15.0" + version: "1.16.0" mtrust_sec_kit: dependency: "direct main" description: @@ -376,18 +392,18 @@ packages: dependency: "direct main" description: name: mtrust_urp_ble_strategy - sha256: "4b12926daf476d9b3aa70f6761c32787e98990c2e9dbb9dd89e323b8f436f6a4" + sha256: "5100452a073f9af8597ceaf76424038de60077b3e268da996338f6d63b7f2d5a" url: "https://pub.dev" source: hosted - version: "9.0.1" + version: "9.1.0-7" mtrust_urp_core: dependency: transitive description: name: mtrust_urp_core - sha256: "3bbc807e10f5218433469e94b7e48cf78464514d16f725643b794c9a80968355" + sha256: a9a42ac29f1a7e2121b00c29b0e9dc4b161fab706be8abc3647a1e3d4cc91ae3 url: "https://pub.dev" source: hosted - version: "9.0.1" + version: "9.1.0-7" mtrust_urp_types: dependency: "direct main" description: @@ -400,26 +416,26 @@ packages: dependency: "direct main" description: name: mtrust_urp_ui - sha256: "8f5c92d294088d43ea0e44d2a7caa54b04b79912cfed21e523982e4cce9aefa5" + sha256: "1070003615f95d9bc5c16143c34e81614164794837bb29dd1612d458ea3acfa5" url: "https://pub.dev" source: hosted - version: "9.0.1" + version: "9.1.0-7" mtrust_urp_virtual_strategy: dependency: "direct main" description: name: mtrust_urp_virtual_strategy - sha256: "44bd27e7d26232f1d82744821b1554a230d026806112204fc3d3d55fe28dbc93" + sha256: "3686e02ad12854f9a10d189c6499a21734cd347c81170561beca97ef7ad80006" url: "https://pub.dev" source: hosted - version: "9.0.1" + version: "9.1.0-7" multi_split_view: dependency: transitive description: name: multi_split_view - sha256: d68e129bff71fc9e6b66de59e1b79deaf4b91f30940130bfbd2d704c1c713499 + sha256: "99c02f128e7423818d13b8f2e01e3027e953b35508019dcee214791bd0525db5" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "3.6.0" nested: dependency: transitive description: @@ -432,10 +448,10 @@ packages: dependency: transitive description: name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.9.1" path_parsing: dependency: transitive description: @@ -600,39 +616,39 @@ packages: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" source_span: dependency: transitive description: name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.10.1" stack_trace: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.12.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" string_scanner: dependency: transitive description: name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.4.1" stringr: dependency: transitive description: @@ -645,18 +661,18 @@ packages: dependency: transitive description: name: term_glyph - sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" test_api: dependency: transitive description: name: test_api - sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd url: "https://pub.dev" source: hosted - version: "0.7.2" + version: "0.7.4" typed_data: dependency: transitive description: @@ -669,10 +685,10 @@ packages: dependency: transitive description: name: value_layout_builder - sha256: c02511ea91ca5c643b514a33a38fa52536f74aa939ec367d02938b5ede6807fa + sha256: ab4b7d98bac8cefeb9713154d43ee0477490183f5aa23bb4ffa5103d9bbf6275 url: "https://pub.dev" source: hosted - version: "0.4.0" + version: "0.5.0" vector_graphics: dependency: transitive description: @@ -709,10 +725,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 url: "https://pub.dev" source: hosted - version: "14.2.5" + version: "15.0.0" web: dependency: transitive description: @@ -770,5 +786,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.5.0 <4.0.0" - flutter: ">=3.24.0" + dart: ">=3.7.0-0 <4.0.0" + flutter: ">=3.32.0" diff --git a/example/pubspec.yaml b/example/pubspec.yaml index f563f88..85d9917 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -12,10 +12,10 @@ dependencies: mtrust_sec_kit: path: ../ mtrust_urp_types: ^6.0.0 - mtrust_urp_ble_strategy: ^9.0.1 - mtrust_urp_virtual_strategy: ^9.0.1 - mtrust_urp_ui: ^9.0.1 - liquid_flutter: ^19.0.0 + mtrust_urp_ble_strategy: ^9.1.0-7 + mtrust_urp_virtual_strategy: ^9.1.0-7 + mtrust_urp_ui: ^9.1.0-7 + liquid_flutter: ^22.0.2 dev_dependencies: flutter_test: diff --git a/lib/src/sec_reader.dart b/lib/src/sec_reader.dart index 4a74569..f3e10d2 100644 --- a/lib/src/sec_reader.dart +++ b/lib/src/sec_reader.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:io'; +import 'package:flutter/foundation.dart'; import 'package:mtrust_sec_kit/mtrust_sec_kit.dart'; /// [SECReader] is a class that provides a high-level API to interact with @@ -16,36 +17,36 @@ class SECReader extends CmdWrapper { required this.connectionStrategy, UrpDeviceIdentifier? target, UrpDeviceIdentifier? origin, - }) : target = target ?? - UrpDeviceIdentifier( - deviceClass: UrpDeviceClass.urpReader, - deviceType: UrpDeviceType.urpSec, - ), + }) : target = target ?? + UrpDeviceIdentifier( + deviceClass: UrpDeviceClass.urpReader, + deviceType: UrpDeviceType.urpSec, + ), origin = origin ?? UrpDeviceIdentifier( deviceClass: UrpDeviceClass.urpHost, - deviceType: (Platform.isAndroid || Platform.isIOS) + deviceType: (kIsWeb || Platform.isAndroid || Platform.isIOS) ? UrpDeviceType.urpMobile : UrpDeviceType.urpDesktop, ); - /// The connectionStrategy used to connect the device. - final ConnectionStrategy connectionStrategy; + /// The connectionStrategy used to connect the device. + final ConnectionStrategy connectionStrategy; - /// The target device. - final UrpDeviceIdentifier target; + /// The target device. + final UrpDeviceIdentifier target; - /// The origin device. - final UrpDeviceIdentifier origin; + /// The origin device. + final UrpDeviceIdentifier origin; - int _requestTokenAmount = 10; + int _requestTokenAmount = 10; - /// Sets the amount of tokens to be requested if the device has no more - /// tokens available. The default is 10. - void setTokenAmount(int amount) { - _requestTokenAmount = amount; - notifyListeners(); - } + /// Sets the amount of tokens to be requested if the device has no more + /// tokens available. The default is 10. + void setTokenAmount(int amount) { + _requestTokenAmount = amount; + notifyListeners(); + } /// Find and connect to a SEC reader using the given [connectionStrategy]. /// If [deviceAddress] is provided, it will try to connect to the reader @@ -68,7 +69,9 @@ class SECReader extends CmdWrapper { /// Returns a list of all available [UrpDeviceType.urpSec] and /// [UrpDeviceType.urpSecQc] reader. - static Stream findReaders(ConnectionStrategy connectionStrategy){ + static Stream findReaders( + ConnectionStrategy connectionStrategy, + ) { return connectionStrategy.findDevices({ UrpDeviceType.urpSec, UrpDeviceType.urpSecQc, @@ -95,15 +98,15 @@ class SECReader extends CmdWrapper { } Future _addCommandToQueue({ - UrpCoreCommand? coreCommand, + UrpCoreCommand? coreCommand, UrpSecDeviceCommand? deviceCommand, }) async { return connectionStrategy.addQueue( UrpSecCommandWrapper( coreCommand: coreCommand, deviceCommand: deviceCommand, - ).writeToBuffer(), - target, + ).writeToBuffer(), + target, origin, ); } @@ -139,7 +142,7 @@ class SECReader extends CmdWrapper { ); final res = await _addCommandToQueue(coreCommand: cmd); - if(!res.hasPayload()) { + if (!res.hasPayload()) { throw SecReaderException(message: 'Failed to get power state'); } return UrpPowerState.fromBuffer(res.payload); @@ -163,7 +166,7 @@ class SECReader extends CmdWrapper { ); final res = await _addCommandToQueue(coreCommand: cmd); - if(!res.hasPayload()) { + if (!res.hasPayload()) { throw SecReaderException(message: 'Failed to get name'); } return UrpDeviceName.fromBuffer(res.payload); @@ -249,7 +252,7 @@ class SECReader extends CmdWrapper { ); final res = await _addCommandToQueue(coreCommand: cmd); - if(!res.hasPayload()) { + if (!res.hasPayload()) { throw SecReaderException(message: 'Failed to get public key'); } return UrpPublicKey.fromBuffer(res.payload); @@ -263,7 +266,7 @@ class SECReader extends CmdWrapper { ); final res = await _addCommandToQueue(coreCommand: cmd); - if(!res.hasPayload()) { + if (!res.hasPayload()) { throw SecReaderException(message: 'Failed to get public key'); } return UrpDeviceId.fromBuffer(res.payload); @@ -287,12 +290,12 @@ class SECReader extends CmdWrapper { ); final res = await _addCommandToQueue(coreCommand: cmd); - if(!res.hasPayload()) { + if (!res.hasPayload()) { throw SecReaderException(message: 'Failed to connect to AP'); } return UrpWifiState.fromBuffer(res.payload); } - + /// Disconnect AP. @override Future disconnectAP() async { @@ -301,7 +304,7 @@ class SECReader extends CmdWrapper { ); await _addCommandToQueue(coreCommand: cmd); } - + /// Start AP. Throws an error if failed. @override Future startAP(String ssid, String apk) async { @@ -311,12 +314,12 @@ class SECReader extends CmdWrapper { ); final res = await _addCommandToQueue(coreCommand: cmd); - if(!res.hasPayload()) { + if (!res.hasPayload()) { throw SecReaderException(message: 'Failed to start AP'); } return UrpWifiState.fromBuffer(res.payload); } - + /// Stop AP. @override Future stopAP() async { @@ -336,26 +339,26 @@ class SECReader extends CmdWrapper { final res = await _addCommandToQueue(deviceCommand: cmd); return UrpSecPrimeResponse.fromBuffer(res.payload); } catch (e) { - if(e is DeviceError) { - if(e.errorCode == 4) { - final publicKey = await getPublicKey(); - final oldToken = await requestToken(); - try { - final newToken = await getToken(oldToken, publicKey); - if(newToken == null) { - throw SecReaderException( - message: 'Failed to get new token!', - type: SecReaderExceptionType.tokenFailed, - ); - } - await setToken(newToken); - return prime(payload); - } catch (e) { - rethrow; - } - } else { + if (e is DeviceError) { + if (e.errorCode != 4) { rethrow; } + final publicKey = await getPublicKey(); + final oldToken = await requestToken(); + + UrpSecureToken? newToken; + + try { + newToken = await getToken(oldToken, publicKey); + } catch (e) { + throw SecReaderException( + message: 'Failed to get new token!', + type: SecReaderExceptionType.tokenFailed, + ); + } + + await setToken(newToken); + return prime(payload); } return null; } @@ -373,7 +376,7 @@ class SECReader extends CmdWrapper { deviceCommand: cmd, ); - if(!res.hasPayload()) { + if (!res.hasPayload()) { throw SecReaderException( message: 'Failed to request token!', type: SecReaderExceptionType.tokenFailed, @@ -400,7 +403,7 @@ class SECReader extends CmdWrapper { deviceCommand: cmd, ); - if(!res.hasPayload()) { + if (!res.hasPayload()) { throw SecReaderException( message: 'Failed to get current token!', type: SecReaderExceptionType.tokenFailed, @@ -463,5 +466,4 @@ class SECReader extends CmdWrapper { final urpSecModels = UrpSecModels.fromBuffer(res.payload); return urpSecModels.models; } - } diff --git a/lib/src/sec_reader_exception.dart b/lib/src/sec_reader_exception.dart index 7e20f4b..f8ede29 100644 --- a/lib/src/sec_reader_exception.dart +++ b/lib/src/sec_reader_exception.dart @@ -2,21 +2,23 @@ enum SecReaderExceptionType { /// Firmware version installed on the device is incompatible incompatibleFirmware, + /// Measurement failed measurementFailed, + /// Failed to get or install new token tokenFailed, + /// Unspecified error unspecified, } - /// Exception thrown to indicate errors related to the SEC reader. /// /// This exception extends the [Error] class and is designed /// to be used specifically for handling errors in the context of SEC /// reading. -class SecReaderException extends Error { +class SecReaderException implements Exception { /// Creates a new instance of [SecReaderException]. /// /// The [message] parameter can be used to provide additional details @@ -33,14 +35,14 @@ class SecReaderException extends Error { /// If not specified during the exception creation, a default message /// ("Unspecified SEC reader error") will be used. final String? message; - /// The type of exception that was thrown. + + /// The type of exception that was thrown. /// The default value is [SecReaderExceptionType.unspecified]. - final SecReaderExceptionType? type; + final SecReaderExceptionType type; } /// Exception thrown when a SEC reader is not found. class SecConnectionFailedException extends SecReaderException { /// Creates a new instance of [SecConnectionFailedException]. - SecConnectionFailedException() - : super(message: 'SEC reader not found'); + SecConnectionFailedException() : super(message: 'SEC reader not found'); } diff --git a/lib/src/ui/l10n/sec_de.arb b/lib/src/ui/l10n/sec_de.arb index 6c6c3cb..9f196fe 100644 --- a/lib/src/ui/l10n/sec_de.arb +++ b/lib/src/ui/l10n/sec_de.arb @@ -19,5 +19,5 @@ "searchingHint": "Stellen Sie sicher, dass die LED \n am Reader blau blinkt.", "incompatibleFirmware": "Inkompatible Firmware. Bitte Update durchführen!", "tokenFailed": "Die Vorbereitung für den Scan ist fehlgeschlagen. Bitte stellen Sie sicher, dass Sie eine funktionierende Internetverbindung haben.", - "readingsLeft": "Verbleibende Messungen für aktuellen Token:" + "readingsLeft": "Internetverbindung in {n} Messungen notwendig" } \ No newline at end of file diff --git a/lib/src/ui/l10n/sec_en.arb b/lib/src/ui/l10n/sec_en.arb index 50cb4b5..ee1588f 100644 --- a/lib/src/ui/l10n/sec_en.arb +++ b/lib/src/ui/l10n/sec_en.arb @@ -1,6 +1,6 @@ { "@@locale": "en", - "successfullyVerfied": "Successfully verified with ", + "successfullyVerified": "Successfully verified", "primeFailed": "Failed to prepare for scan", "verificationFailed": "Verification failed", "verificationFailedMessage": "Could not detect security pigment.", @@ -21,5 +21,5 @@ "secondsLeft": "{seconds}s\nleft", "incompatibleFirmware": "Firmware incompatible. Please update!", "tokenFailed": "Failed to prepare for scan. Please make sure you have a working internet connection", - "readingsLeft": "Readings left with current token:" + "readingsLeft": "Internet connection required in {n} measurements" } \ No newline at end of file diff --git a/lib/src/ui/l10n/sec_locale.dart b/lib/src/ui/l10n/sec_locale.dart index da4a21c..1cb3abb 100644 --- a/lib/src/ui/l10n/sec_locale.dart +++ b/lib/src/ui/l10n/sec_locale.dart @@ -62,7 +62,8 @@ import 'sec_locale_en.dart'; /// be consistent with the languages listed in the SecLocalizations.supportedLocales /// property. abstract class SecLocalizations { - SecLocalizations(String locale) : localeName = intl.Intl.canonicalizedLocale(locale.toString()); + SecLocalizations(String locale) + : localeName = intl.Intl.canonicalizedLocale(locale.toString()); final String localeName; @@ -70,7 +71,8 @@ abstract class SecLocalizations { return Localizations.of(context, SecLocalizations)!; } - static const LocalizationsDelegate delegate = _SecLocalizationsDelegate(); + static const LocalizationsDelegate delegate = + _SecLocalizationsDelegate(); /// A list of this localizations delegate along with the default localizations /// delegates. @@ -82,7 +84,8 @@ abstract class SecLocalizations { /// Additional delegates can be added by appending to this list in /// MaterialApp. This list does not have to be used at all if a custom list /// of delegates is preferred or required. - static const List> localizationsDelegates = >[ + static const List> localizationsDelegates = + >[ delegate, GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate, @@ -95,11 +98,11 @@ abstract class SecLocalizations { Locale('en') ]; - /// No description provided for @successfullyVerfied. + /// No description provided for @successfullyVerified. /// /// In en, this message translates to: - /// **'Successfully verified with '** - String get successfullyVerfied; + /// **'Successfully verified'** + String get successfullyVerified; /// No description provided for @primeFailed. /// @@ -224,11 +227,12 @@ abstract class SecLocalizations { /// No description provided for @readingsLeft. /// /// In en, this message translates to: - /// **'Readings left with current token:'** - String get readingsLeft; + /// **'Internet connection required in {n} measurements'** + String readingsLeft(Object n); } -class _SecLocalizationsDelegate extends LocalizationsDelegate { +class _SecLocalizationsDelegate + extends LocalizationsDelegate { const _SecLocalizationsDelegate(); @override @@ -237,25 +241,25 @@ class _SecLocalizationsDelegate extends LocalizationsDelegate } @override - bool isSupported(Locale locale) => ['de', 'en'].contains(locale.languageCode); + bool isSupported(Locale locale) => + ['de', 'en'].contains(locale.languageCode); @override bool shouldReload(_SecLocalizationsDelegate old) => false; } SecLocalizations lookupSecLocalizations(Locale locale) { - - // Lookup logic when only language code is specified. switch (locale.languageCode) { - case 'de': return SecLocalizationsDe(); - case 'en': return SecLocalizationsEn(); + case 'de': + return SecLocalizationsDe(); + case 'en': + return SecLocalizationsEn(); } throw FlutterError( - 'SecLocalizations.delegate failed to load unsupported locale "$locale". This is likely ' - 'an issue with the localizations generation tool. Please file an issue ' - 'on GitHub with a reproducible sample app and the gen-l10n configuration ' - 'that was used.' - ); + 'SecLocalizations.delegate failed to load unsupported locale "$locale". This is likely ' + 'an issue with the localizations generation tool. Please file an issue ' + 'on GitHub with a reproducible sample app and the gen-l10n configuration ' + 'that was used.'); } diff --git a/lib/src/ui/l10n/sec_locale_de.dart b/lib/src/ui/l10n/sec_locale_de.dart index 92dd51b..a639319 100644 --- a/lib/src/ui/l10n/sec_locale_de.dart +++ b/lib/src/ui/l10n/sec_locale_de.dart @@ -1,3 +1,5 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; import 'sec_locale.dart'; // ignore_for_file: type=lint @@ -7,7 +9,7 @@ class SecLocalizationsDe extends SecLocalizations { SecLocalizationsDe([String locale = 'de']) : super(locale); @override - String get successfullyVerfied => 'Verifizierung erfolgreich mit '; + String get successfullyVerified => 'Successfully verified'; @override String get primeFailed => 'Failed to prepare for scan'; @@ -34,7 +36,8 @@ class SecLocalizationsDe extends SecLocalizations { String get turnOnPrompt => 'Zum Einschalten Taste drücken'; @override - String get timeHint => 'Sobald der Vorgang gestartet wurde, haben Sie 30 Sekunden Zeit zum Scannen'; + String get timeHint => + 'Sobald der Vorgang gestartet wurde, haben Sie 30 Sekunden Zeit zum Scannen'; @override String get readyToScan => 'Bereit zum Scannen'; @@ -43,7 +46,8 @@ class SecLocalizationsDe extends SecLocalizations { String get startScan => 'Scan starten'; @override - String get distanceHint => 'Approach the surface parallel to the reader at a distance of 8-12mm'; + String get distanceHint => + 'Approach the surface parallel to the reader at a distance of 8-12mm'; @override String get primingTitle => 'Scannen vorbereiten...'; @@ -66,11 +70,15 @@ class SecLocalizationsDe extends SecLocalizations { } @override - String get incompatibleFirmware => 'Inkompatible Firmware. Bitte Update durchführen!'; + String get incompatibleFirmware => + 'Inkompatible Firmware. Bitte Update durchführen!'; @override - String get tokenFailed => 'Die Vorbereitung für den Scan ist fehlgeschlagen. Bitte stellen Sie sicher, dass Sie eine funktionierende Internetverbindung haben.'; + String get tokenFailed => + 'Die Vorbereitung für den Scan ist fehlgeschlagen. Bitte stellen Sie sicher, dass Sie eine funktionierende Internetverbindung haben.'; @override - String get readingsLeft => 'Verbleibende Messungen für aktuellen Token:'; + String readingsLeft(Object n) { + return 'Internetverbindung in $n Messungen notwendig'; + } } diff --git a/lib/src/ui/l10n/sec_locale_en.dart b/lib/src/ui/l10n/sec_locale_en.dart index 4f86888..d9aad73 100644 --- a/lib/src/ui/l10n/sec_locale_en.dart +++ b/lib/src/ui/l10n/sec_locale_en.dart @@ -1,3 +1,5 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; import 'sec_locale.dart'; // ignore_for_file: type=lint @@ -7,7 +9,7 @@ class SecLocalizationsEn extends SecLocalizations { SecLocalizationsEn([String locale = 'en']) : super(locale); @override - String get successfullyVerfied => 'Successfully verified with '; + String get successfullyVerified => 'Successfully verified'; @override String get primeFailed => 'Failed to prepare for scan'; @@ -43,7 +45,8 @@ class SecLocalizationsEn extends SecLocalizations { String get startScan => 'Start scan'; @override - String get distanceHint => 'Approach the surface parallel to the reader at a distance of 8-12mm'; + String get distanceHint => + 'Approach the surface parallel to the reader at a distance of 8-12mm'; @override String get primingTitle => 'Getting ready to scan...'; @@ -69,8 +72,11 @@ class SecLocalizationsEn extends SecLocalizations { String get incompatibleFirmware => 'Firmware incompatible. Please update!'; @override - String get tokenFailed => 'Failed to prepare for scan. Please make sure you have a working internet connection'; + String get tokenFailed => + 'Failed to prepare for scan. Please make sure you have a working internet connection'; @override - String get readingsLeft => 'Readings left with current token:'; + String readingsLeft(Object n) { + return 'Internet connection required in $n measurements'; + } } diff --git a/lib/src/ui/scanning_instruction.dart b/lib/src/ui/scanning_instruction.dart index 5f190bd..7aec07d 100644 --- a/lib/src/ui/scanning_instruction.dart +++ b/lib/src/ui/scanning_instruction.dart @@ -97,29 +97,6 @@ class _ScanningInstructionState extends State width: 2, color: color, ), - Expanded( - child: Container( - height: 2, - color: color, - ), - ), - ldSpacerS, - Expanded( - flex: 2, - child: LdTextL( - '${(12 + 12 * (1 - value)).toStringAsFixed(2)}mm', - color: color, - textAlign: TextAlign.center, - maxLines: 2, - ), - ), - ldSpacerS, - Expanded( - child: Container( - height: 2, - color: color, - ), - ), Container( height: 16, width: 2, @@ -144,10 +121,10 @@ class _ScanningInstructionState extends State decoration: BoxDecoration( gradient: LinearGradient( colors: [ - theme.background, - theme.background.withAlpha(0), - theme.background.withAlpha(0), - theme.background, + theme.surface, + theme.surface.withAlpha(0), + theme.surface.withAlpha(0), + theme.surface, ], stops: const [0, 0.1, 0.9, 1], ), diff --git a/lib/src/ui/scanning_view.dart b/lib/src/ui/scanning_view.dart new file mode 100644 index 0000000..3c6c541 --- /dev/null +++ b/lib/src/ui/scanning_view.dart @@ -0,0 +1,211 @@ +// ignore_for_file: avoid_dynamic_calls + +import 'package:flutter/material.dart'; +import 'package:liquid_flutter/liquid_flutter.dart'; +import 'package:mtrust_sec_kit/mtrust_sec_kit.dart'; +import 'package:mtrust_sec_kit/src/ui/count_down_progress.dart'; +import 'package:mtrust_sec_kit/src/ui/scanning_instruction.dart'; + +/// [ScanningView] is used when the user is performing a measurement +/// Responsible for starting the measurement , showing the progress indicator +/// and displaying the result. +class ScanningView extends StatelessWidget { + /// Creates a new [ScanningView]. + const ScanningView({ + required this.strategy, + required this.onVerificationDone, + required this.onVerificationFailed, + super.key, + this.remainingScans, + }); + + /// The number of remaining scans. + final int? remainingScans; + + /// The strategy to use for the connection. + final ConnectionStrategy strategy; + + /// The function to call when the verification is done. + final Future Function( + UrpSecSecureMeasurement measurement, + ) onVerificationDone; + + /// Function to call when the verification fails. + final Future Function() onVerificationFailed; + + @override + Widget build(BuildContext context) { + return Center( + child: LdSubmit( + config: LdSubmitConfig( + loadingText: SecLocalizations.of(context).scanning, + submitText: SecLocalizations.of(context).startScan, + timeout: const Duration(seconds: 35), + action: () async { + final reader = SECReader( + connectionStrategy: strategy, + ); + final result = await reader.startMeasurement(); + + return result; + }, + ), + builder: LdSubmitCustomBuilder( + builder: (context, measurementController, measurementStateType) { + switch (measurementStateType) { + case (LdSubmitStateType.loading): + return LdAutoSpace( + key: const Key('loading-scanning-view'), + crossAxisAlignment: CrossAxisAlignment.center, + animate: true, + children: [ + LdTextHs( + SecLocalizations.of(context).scanning, + textAlign: TextAlign.center, + ), + LdTextP( + SecLocalizations.of(context).distanceHint, + textAlign: TextAlign.center, + ), + ldSpacerL, + const Expanded( + child: ScanningInstruction(), + ), + ldSpacerL, + const CountDownProgress(), + ldSpacerL, + ], + ); + case (LdSubmitStateType.result): + final result = measurementController.state.result!; + + return LdAutoSpace( + key: const Key('result-scanning-view'), + crossAxisAlignment: CrossAxisAlignment.center, + animate: true, + children: [ + LdTextHs( + SecLocalizations.of(context).successfullyVerified, + textAlign: TextAlign.center, + ), + ldSpacerL, + Expanded( + child: SecReaderVisualization( + ledColor: Colors.green, + screenContent: Center( + child: Text( + SecLocalizations.of(context).successfullyVerified, + ), + ), + ), + ), + ldSpacerL, + LdButtonVague( + width: double.infinity, + borderRadius: LdTheme.of(context).radius(LdSize.l), + size: LdSize.l, + onPressed: () async { + await onVerificationDone( + result, + ); + }, + loadingText: SecLocalizations.of(context).disconnecting, + child: Text( + SecLocalizations.of(context).done, + ), + ), + ], + ); + case (LdSubmitStateType.idle): + return LdAutoSpace( + key: const Key('idle-scanning-view'), + animate: true, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + LdTextHs( + SecLocalizations.of(context).readyToScan, + textAlign: TextAlign.center, + ), + LdTextP( + SecLocalizations.of(context).timeHint, + textAlign: TextAlign.center, + ), + Expanded( + child: SecReaderVisualization( + ledColor: Colors.yellow, + screenContent: Center( + child: Text( + SecLocalizations.of(context).readyToScan, + ), + ), + ), + ), + LdMute( + child: LdTextPs( + SecLocalizations.of(context) + .readingsLeft(remainingScans ?? 0), + textAlign: TextAlign.center, + ), + ), + LdButtonVague( + onPressed: measurementController.trigger, + borderRadius: LdTheme.of(context).radius(LdSize.l), + width: double.infinity, + size: LdSize.l, + child: Text( + SecLocalizations.of(context).startScan, + ), + ), + ], + ); + case (LdSubmitStateType.error): + var message = + SecLocalizations.of(context).verificationFailedMessage; + if (measurementController.state.error?.exception.runtimeType == + SecReaderExceptionType) { + final error = measurementController.state.error?.exception + as SecReaderException; + if (error.type == + SecReaderExceptionType.incompatibleFirmware) { + message = SecLocalizations.of(context).incompatibleFirmware; + } + } + return LdAutoSpace( + key: const Key('failed-scanning-view'), + animate: true, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + LdTextHs( + SecLocalizations.of(context).verificationFailed, + textAlign: TextAlign.center, + ), + LdTextP( + message, + textAlign: TextAlign.center, + ), + Expanded( + child: SecReaderVisualization( + ledColor: Colors.red, + screenContent: Container(), + ), + ), + LdButtonWarning( + width: double.infinity, + borderRadius: LdTheme.of(context).radius(LdSize.l), + size: LdSize.l, + onPressed: onVerificationFailed, + loadingText: SecLocalizations.of(context).disconnecting, + context: context, + child: Text( + SecLocalizations.of(context).done, + ), + ), + ], + ); + } + }, + ), + ), + ); + } +} diff --git a/lib/src/ui/sec_modal.dart b/lib/src/ui/sec_modal.dart index 11723d3..924c732 100644 --- a/lib/src/ui/sec_modal.dart +++ b/lib/src/ui/sec_modal.dart @@ -13,7 +13,7 @@ import 'package:mtrust_sec_kit/src/ui/sec_result.dart'; /// Provide a [builder] that renders some UI with a callback to open the sheet. class SecModalBuilder extends StatelessWidget { /// Creates a new instance of [SecModalBuilder] - const SecModalBuilder({ + const SecModalBuilder({ required this.strategy, required this.payload, required this.onVerificationDone, @@ -75,6 +75,7 @@ class SecModalBuilder extends StatelessWidget { } Future handleClose() async { + await Future.delayed(const Duration(milliseconds: 500)); if (turnOffOnClose && strategy.status == ConnectionStatus.connected) { await SECReader(connectionStrategy: strategy).off(); } @@ -157,7 +158,6 @@ LdModal secModal({ }) { return LdModal( disableScrolling: true, - padding: EdgeInsets.zero, noHeader: true, showDismissButton: canDismiss, userCanDismiss: canDismiss, @@ -165,19 +165,24 @@ LdModal secModal({ bottomRadius: bottomRadius, useSafeArea: useSafeArea, insets: insets, + contentPadding: EdgeInsets.zero, + fixedDialogSize: const Size(400, 400), size: LdSize.s, modalContent: (context) => AspectRatio( aspectRatio: 1, - child: SecWidget( - strategy: strategy, - payload: payload, - onVerificationDone: (UrpSecSecureMeasurement measurement) async { - Navigator.of(context).pop(SecResultSuccess(measurement)); - }, - onVerificationFailed: () async { - Navigator.of(context).pop(SecResultFailed()); - }, - tokenAmount: tokenAmount, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 32), + child: SecWidget( + strategy: strategy, + payload: payload, + onVerificationDone: (UrpSecSecureMeasurement measurement) async { + Navigator.of(context).pop(SecResultSuccess(measurement)); + }, + onVerificationFailed: () async { + Navigator.of(context).pop(SecResultFailed()); + }, + tokenAmount: tokenAmount, + ), ), ).padL(), ); diff --git a/lib/src/ui/sec_widget.dart b/lib/src/ui/sec_widget.dart index ba4a6b1..2f9bd41 100644 --- a/lib/src/ui/sec_widget.dart +++ b/lib/src/ui/sec_widget.dart @@ -3,8 +3,7 @@ import 'package:flutter/material.dart'; import 'package:liquid_flutter/liquid_flutter.dart'; import 'package:mtrust_sec_kit/mtrust_sec_kit.dart'; -import 'package:mtrust_sec_kit/src/ui/count_down_progress.dart'; -import 'package:mtrust_sec_kit/src/ui/scanning_instruction.dart'; +import 'package:mtrust_sec_kit/src/ui/scanning_view.dart'; /// [SecWidget] is a widget that guides the user through the SEC /// workflow. @@ -43,297 +42,147 @@ class SecWidget extends StatelessWidget { @override Widget build(BuildContext context) { - - var models = []; - - return DeviceConnector( - connectionStrategy: strategy, - storageAdapter: storageAdapter, - connectedBuilder: (BuildContext context) { - return LdSubmit( - config: LdSubmitConfig( - loadingText: SecLocalizations.of(context).primingTitle, - autoTrigger: true, - action: () async { - final reader = SECReader( - connectionStrategy: strategy, - ); - models = await reader.getModelInfo(); - if(tokenAmount != null) { - reader.setTokenAmount(tokenAmount!); - } - return reader.prime(payload); - }, - ), - builder: LdSubmitCustomBuilder( - builder: (context, controller, stateType) { - if (stateType == LdSubmitStateType.error) { - var message = controller.state.error?.message - ?? 'Unknown error'; - if(controller.state.error?.exception.runtimeType - == SecReaderException) { - final error = controller.state.error?.exception - as SecReaderException; - if(error.type == SecReaderExceptionType.tokenFailed) { - message = SecLocalizations.of(context).tokenFailed; - } - } - if(controller.state.error?.exception.runtimeType is ApiException) { - message = SecLocalizations.of(context).tokenFailed; - } - return LdAutoSpace( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - LdTextHs( - SecLocalizations.of(context).primeFailed, - textAlign: TextAlign.center, - ), - Expanded( - child: SecReaderVisualization( - ledColor: Colors.red, - screenContent: Container(), - ), - ), - LdTextP( - message, - textAlign: TextAlign.center, - ), - LdButtonWarning( - onPressed: controller.trigger, - context: context, - child: Text( - SecLocalizations.of(context).retry, - ), - ), - ], + final locale = SecLocalizations.of(context); + return LdExceptionMapperProvider( + exceptionMapper: _SecExceptionMapper( + secLocalizations: locale, + localizations: LiquidLocalizations.of(context), + ), + child: DeviceConnector( + connectionStrategy: strategy, + storageAdapter: storageAdapter, + connectedBuilder: (BuildContext context) { + return LdSubmit( + config: LdSubmitConfig( + loadingText: locale.primingTitle, + autoTrigger: true, + action: () async { + final reader = SECReader( + connectionStrategy: strategy, ); - } - if (stateType == LdSubmitStateType.loading) { - return Center( - child: LdAutoSpace( + if (tokenAmount != null) { + reader.setTokenAmount(tokenAmount!); + } + return reader.prime(payload); + }, + ), + builder: LdSubmitCustomBuilder( + builder: (context, controller, stateType) { + if (stateType == LdSubmitStateType.error) { + var message = + controller.state.error?.message ?? 'Unknown error'; + + if (controller.state.error?.exception.runtimeType + is ApiException) { + message = locale.tokenFailed; + } + return LdAutoSpace( crossAxisAlignment: CrossAxisAlignment.center, - animate: true, children: [ - const LdLoader(), - LdTextL( - SecLocalizations.of(context).primingTitle, + LdTextHs( + locale.primeFailed, textAlign: TextAlign.center, ), + Expanded( + child: SecReaderVisualization( + ledColor: Colors.red, + screenContent: Container(), + ), + ), + LdTextP( + message, + textAlign: TextAlign.center, + ), + if (controller.canRetry) + LdButtonWarning( + onPressed: controller.trigger, + context: context, + child: Text( + locale.retry, + ), + ) + else + LdButtonWarning( + onPressed: onVerificationFailed, + context: context, + child: Text( + locale.done, + ), + ), ], - ), - ); - } + ); + } + + if (stateType == LdSubmitStateType.loading) { + return Center( + child: LdAutoSpace( + crossAxisAlignment: CrossAxisAlignment.center, + animate: true, + children: [ + const LdLoader(), + LdTextL( + locale.primingTitle, + textAlign: TextAlign.center, + ), + ], + ), + ); + } - return _ScanningView( - strategy: strategy, - onVerificationDone: ( - UrpSecSecureMeasurement measurement, - ) async { - controller.reset(); - await onVerificationDone(measurement); - }, - onVerificationFailed: () async { - controller.reset(); - await onVerificationFailed(); - }, - remainingScans: controller.state.result?.gsa, - models: models, - ); - }, - ), - ); - }, - deviceTypes: const {UrpDeviceType.urpSec}, + return ScanningView( + strategy: strategy, + onVerificationDone: ( + UrpSecSecureMeasurement measurement, + ) async { + controller.reset(); + await onVerificationDone(measurement); + }, + onVerificationFailed: () async { + controller.reset(); + await onVerificationFailed(); + }, + remainingScans: controller.state.result?.gsa, + ); + }, + ), + ); + }, + deviceTypes: const {UrpDeviceType.urpSec}, + ), ); } } -class _ScanningView extends StatelessWidget { - const _ScanningView({ - required this.strategy, - required this.onVerificationDone, - required this.onVerificationFailed, - this.remainingScans, - this.models, +class _SecExceptionMapper extends LdExceptionMapper { + _SecExceptionMapper({ + required this.secLocalizations, + required super.localizations, }); - final int? remainingScans; - final ConnectionStrategy strategy; - final Future Function( - UrpSecSecureMeasurement measurement, - ) onVerificationDone; - final Future Function() onVerificationFailed; - final List? models; + final SecLocalizations secLocalizations; @override - Widget build(BuildContext context) { - return Center( - child: LdSubmit( - config: LdSubmitConfig( - loadingText: SecLocalizations.of(context).scanning, - submitText: SecLocalizations.of(context).startScan, - timeout: const Duration(seconds: 35), - action: () async { - final reader = SECReader( - connectionStrategy: strategy, - ); - return reader.startMeasurement(); - }, - ), - builder: LdSubmitCustomBuilder( - builder: (context, measurementController, measurementStateType) { - - final installedModels = models != null - ? models!.map((model) => '${model.modelId} ${model.version}').join(', ') - : ''; - - switch (measurementStateType) { - case (LdSubmitStateType.loading): - return LdAutoSpace( - crossAxisAlignment: CrossAxisAlignment.center, - animate: true, - children: [ - LdTextHs( - SecLocalizations.of(context).scanning, - textAlign: TextAlign.center, - ), - LdTextP( - installedModels, - textAlign: TextAlign.center, - ), - LdTextP( - SecLocalizations.of(context).distanceHint, - textAlign: TextAlign.center, - ), - ldSpacerL, - const Expanded( - child: ScanningInstruction(), - ), - ldSpacerL, - const CountDownProgress(), - ldSpacerL, - ], - ); - case (LdSubmitStateType.result): - - final result = measurementController.state.result!; - final model = result.measurement.result.first.modelId; - - return LdAutoSpace( - crossAxisAlignment: CrossAxisAlignment.center, - animate: true, - children: [ - LdTextHs( - SecLocalizations.of(context).successfullyVerfied + model, - textAlign: TextAlign.center, - ), - ldSpacerL, - ldSpacerL, - Expanded( - child: SecReaderVisualization( - ledColor: Colors.green, - screenContent: Center( - child: Text( - SecLocalizations.of(context).successfullyVerfied, - ), - ), - ), - ), - ldSpacerL, - LdButton( - onPressed: () async { - await onVerificationDone( - result, - ); - }, - loadingText: SecLocalizations.of(context).disconnecting, - child: Text( - SecLocalizations.of(context).done, - ), - ), - ldSpacerL, - ], - ); - case (LdSubmitStateType.idle): - return LdAutoSpace( - animate: true, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - LdTextHs( - SecLocalizations.of(context).readyToScan, - textAlign: TextAlign.center, - ), - LdTextP( - installedModels, - textAlign: TextAlign.center, - ), - ldSpacerM, - LdTextP( - """${SecLocalizations.of(context).readingsLeft} ${remainingScans ?? 'Unknown'}""", - textAlign: TextAlign.center, - ), - LdTextP( - SecLocalizations.of(context).timeHint, - textAlign: TextAlign.center, - ), - Expanded( - child: SecReaderVisualization( - ledColor: Colors.yellow, - screenContent: Container(), - ), - ), - LdButton( - onPressed: measurementController.trigger, - child: Text( - SecLocalizations.of(context).startScan, - ), - ), - ], - ).padL(); - case (LdSubmitStateType.error): - var message = SecLocalizations.of(context) - .verificationFailedMessage; - if(measurementController.state.error?.exception.runtimeType - == SecReaderExceptionType) { - final error = measurementController.state.error?.exception - as SecReaderException; - if(error.type == SecReaderExceptionType.incompatibleFirmware){ - message = SecLocalizations.of(context).incompatibleFirmware; - } - } - return LdAutoSpace( - animate: true, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - LdTextHs( - SecLocalizations.of(context).verificationFailed, - textAlign: TextAlign.center, - ), - LdTextP( - message, - textAlign: TextAlign.center, - ), - Expanded( - child: SecReaderVisualization( - ledColor: Colors.red, - screenContent: Container(), - ), - ), - LdButtonWarning( - onPressed: onVerificationFailed, - loadingText: SecLocalizations.of(context).disconnecting, - context: context, - child: Text( - SecLocalizations.of(context).done, - ), - ), - ], - ).padL(); - } - }, - ), - ), - ); + LdException handle(dynamic e, {StackTrace? stackTrace}) { + if (e is SecReaderException) { + final retriable = { + SecReaderExceptionType.tokenFailed, + SecReaderExceptionType.measurementFailed, + SecReaderExceptionType.unspecified, + }; + return LdException( + message: switch (e.type) { + SecReaderExceptionType.tokenFailed => secLocalizations.tokenFailed, + SecReaderExceptionType.incompatibleFirmware => + secLocalizations.incompatibleFirmware, + SecReaderExceptionType.measurementFailed => + secLocalizations.verificationFailedMessage, + SecReaderExceptionType.unspecified => localizations.unknownError, + }, + canRetry: retriable.contains(e.type), + ); + } + + return super.handle(e, stackTrace: stackTrace); } } diff --git a/pubspec.yaml b/pubspec.yaml index ae1cfee..dbe605d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,17 +11,18 @@ dependencies: sdk: flutter flutter_localizations: sdk: flutter - intl: ^0.19.0 - liquid_flutter: ^19.0.0 - mtrust_urp_core: ^9.0.1 - mtrust_urp_types: ^6.0.0 - mtrust_urp_ui: ^9.0.1 + intl: ^0.20.2 + liquid_flutter: ^22.0.4 + mtrust_urp_core: ^9.1.0-9 + mtrust_urp_types: ^6.2.0 + mtrust_urp_ui: ^9.1.0-9 dev_dependencies: flutter_test: sdk: flutter golden_toolkit: ^0.15.0 - mtrust_urp_virtual_strategy: ^9.0.1 + mtrust_urp_virtual_strategy: ^9.1.0-9 very_good_analysis: ^5.1.0 + flutter: uses-material-design: true generate: true diff --git a/test/goldens/SecWidget/Idle/l-dark.png b/test/goldens/SecWidget/Idle/l-dark.png index 26df00f..a2a70ef 100644 Binary files a/test/goldens/SecWidget/Idle/l-dark.png and b/test/goldens/SecWidget/Idle/l-dark.png differ diff --git a/test/goldens/SecWidget/Idle/l-light.png b/test/goldens/SecWidget/Idle/l-light.png index ee61c18..6b69e9a 100644 Binary files a/test/goldens/SecWidget/Idle/l-light.png and b/test/goldens/SecWidget/Idle/l-light.png differ diff --git a/test/goldens/SecWidget/Idle/m-dark.png b/test/goldens/SecWidget/Idle/m-dark.png index edba02e..71660ae 100644 Binary files a/test/goldens/SecWidget/Idle/m-dark.png and b/test/goldens/SecWidget/Idle/m-dark.png differ diff --git a/test/goldens/SecWidget/Idle/m-light.png b/test/goldens/SecWidget/Idle/m-light.png index 92c5de4..c9ab33f 100644 Binary files a/test/goldens/SecWidget/Idle/m-light.png and b/test/goldens/SecWidget/Idle/m-light.png differ diff --git a/test/goldens/SecWidget/Idle/s-dark.png b/test/goldens/SecWidget/Idle/s-dark.png index 446ceaa..7732a05 100644 Binary files a/test/goldens/SecWidget/Idle/s-dark.png and b/test/goldens/SecWidget/Idle/s-dark.png differ diff --git a/test/goldens/SecWidget/Idle/s-light.png b/test/goldens/SecWidget/Idle/s-light.png index d3ccbc4..ecfed43 100644 Binary files a/test/goldens/SecWidget/Idle/s-light.png and b/test/goldens/SecWidget/Idle/s-light.png differ diff --git a/test/goldens/SecWidget/Measure Complete/l-dark.png b/test/goldens/SecWidget/Measure Complete/l-dark.png index 727f542..5617b83 100644 Binary files a/test/goldens/SecWidget/Measure Complete/l-dark.png and b/test/goldens/SecWidget/Measure Complete/l-dark.png differ diff --git a/test/goldens/SecWidget/Measure Complete/l-light.png b/test/goldens/SecWidget/Measure Complete/l-light.png index 5bc8675..ea9e8be 100644 Binary files a/test/goldens/SecWidget/Measure Complete/l-light.png and b/test/goldens/SecWidget/Measure Complete/l-light.png differ diff --git a/test/goldens/SecWidget/Measure Complete/m-dark.png b/test/goldens/SecWidget/Measure Complete/m-dark.png index 4b224d9..5659563 100644 Binary files a/test/goldens/SecWidget/Measure Complete/m-dark.png and b/test/goldens/SecWidget/Measure Complete/m-dark.png differ diff --git a/test/goldens/SecWidget/Measure Complete/m-light.png b/test/goldens/SecWidget/Measure Complete/m-light.png index 69c3aaa..d23fffd 100644 Binary files a/test/goldens/SecWidget/Measure Complete/m-light.png and b/test/goldens/SecWidget/Measure Complete/m-light.png differ diff --git a/test/goldens/SecWidget/Measure Complete/s-dark.png b/test/goldens/SecWidget/Measure Complete/s-dark.png index 7df6f92..e1f426c 100644 Binary files a/test/goldens/SecWidget/Measure Complete/s-dark.png and b/test/goldens/SecWidget/Measure Complete/s-dark.png differ diff --git a/test/goldens/SecWidget/Measure Complete/s-light.png b/test/goldens/SecWidget/Measure Complete/s-light.png index 3c74a06..02b16ff 100644 Binary files a/test/goldens/SecWidget/Measure Complete/s-light.png and b/test/goldens/SecWidget/Measure Complete/s-light.png differ diff --git a/test/goldens/SecWidget/Measure Fail/l-dark.png b/test/goldens/SecWidget/Measure Fail/l-dark.png index c4ffcf6..14d8f4e 100644 Binary files a/test/goldens/SecWidget/Measure Fail/l-dark.png and b/test/goldens/SecWidget/Measure Fail/l-dark.png differ diff --git a/test/goldens/SecWidget/Measure Fail/l-light.png b/test/goldens/SecWidget/Measure Fail/l-light.png index 6a51269..ea3a852 100644 Binary files a/test/goldens/SecWidget/Measure Fail/l-light.png and b/test/goldens/SecWidget/Measure Fail/l-light.png differ diff --git a/test/goldens/SecWidget/Measure Fail/m-dark.png b/test/goldens/SecWidget/Measure Fail/m-dark.png index 3d5afa5..8dd79d7 100644 Binary files a/test/goldens/SecWidget/Measure Fail/m-dark.png and b/test/goldens/SecWidget/Measure Fail/m-dark.png differ diff --git a/test/goldens/SecWidget/Measure Fail/m-light.png b/test/goldens/SecWidget/Measure Fail/m-light.png index 75f4ec9..d0cc821 100644 Binary files a/test/goldens/SecWidget/Measure Fail/m-light.png and b/test/goldens/SecWidget/Measure Fail/m-light.png differ diff --git a/test/goldens/SecWidget/Measure Fail/s-dark.png b/test/goldens/SecWidget/Measure Fail/s-dark.png index 459bc8e..42af985 100644 Binary files a/test/goldens/SecWidget/Measure Fail/s-dark.png and b/test/goldens/SecWidget/Measure Fail/s-dark.png differ diff --git a/test/goldens/SecWidget/Measure Fail/s-light.png b/test/goldens/SecWidget/Measure Fail/s-light.png index 9cd98ef..9193450 100644 Binary files a/test/goldens/SecWidget/Measure Fail/s-light.png and b/test/goldens/SecWidget/Measure Fail/s-light.png differ diff --git a/test/goldens/SecWidget/Measuring/l-dark.png b/test/goldens/SecWidget/Measuring/l-dark.png index c4ffcf6..3bafbfd 100644 Binary files a/test/goldens/SecWidget/Measuring/l-dark.png and b/test/goldens/SecWidget/Measuring/l-dark.png differ diff --git a/test/goldens/SecWidget/Measuring/l-light.png b/test/goldens/SecWidget/Measuring/l-light.png index 6a51269..7b90db5 100644 Binary files a/test/goldens/SecWidget/Measuring/l-light.png and b/test/goldens/SecWidget/Measuring/l-light.png differ diff --git a/test/goldens/SecWidget/Measuring/m-dark.png b/test/goldens/SecWidget/Measuring/m-dark.png index 3d5afa5..bd82ceb 100644 Binary files a/test/goldens/SecWidget/Measuring/m-dark.png and b/test/goldens/SecWidget/Measuring/m-dark.png differ diff --git a/test/goldens/SecWidget/Measuring/m-light.png b/test/goldens/SecWidget/Measuring/m-light.png index 75f4ec9..eb12aa1 100644 Binary files a/test/goldens/SecWidget/Measuring/m-light.png and b/test/goldens/SecWidget/Measuring/m-light.png differ diff --git a/test/goldens/SecWidget/Measuring/s-dark.png b/test/goldens/SecWidget/Measuring/s-dark.png index 459bc8e..4d5af99 100644 Binary files a/test/goldens/SecWidget/Measuring/s-dark.png and b/test/goldens/SecWidget/Measuring/s-dark.png differ diff --git a/test/goldens/SecWidget/Measuring/s-light.png b/test/goldens/SecWidget/Measuring/s-light.png index 9cd98ef..e74bc7a 100644 Binary files a/test/goldens/SecWidget/Measuring/s-light.png and b/test/goldens/SecWidget/Measuring/s-light.png differ diff --git a/test/goldens/SecWidget/Priming/l-dark.png b/test/goldens/SecWidget/Priming/l-dark.png index 9b63091..a7cfb4a 100644 Binary files a/test/goldens/SecWidget/Priming/l-dark.png and b/test/goldens/SecWidget/Priming/l-dark.png differ diff --git a/test/goldens/SecWidget/Priming/l-light.png b/test/goldens/SecWidget/Priming/l-light.png index 258638a..2eaea54 100644 Binary files a/test/goldens/SecWidget/Priming/l-light.png and b/test/goldens/SecWidget/Priming/l-light.png differ diff --git a/test/goldens/SecWidget/Priming/m-dark.png b/test/goldens/SecWidget/Priming/m-dark.png index de19d5a..2ed272c 100644 Binary files a/test/goldens/SecWidget/Priming/m-dark.png and b/test/goldens/SecWidget/Priming/m-dark.png differ diff --git a/test/goldens/SecWidget/Priming/m-light.png b/test/goldens/SecWidget/Priming/m-light.png index fa14660..dd44394 100644 Binary files a/test/goldens/SecWidget/Priming/m-light.png and b/test/goldens/SecWidget/Priming/m-light.png differ diff --git a/test/goldens/SecWidget/Priming/s-dark.png b/test/goldens/SecWidget/Priming/s-dark.png index eb92024..83f0584 100644 Binary files a/test/goldens/SecWidget/Priming/s-dark.png and b/test/goldens/SecWidget/Priming/s-dark.png differ diff --git a/test/goldens/SecWidget/Priming/s-light.png b/test/goldens/SecWidget/Priming/s-light.png index 6af3bfd..63b2d33 100644 Binary files a/test/goldens/SecWidget/Priming/s-light.png and b/test/goldens/SecWidget/Priming/s-light.png differ diff --git a/test/goldens/SecWidget/Waiting for measurement/l-dark.png b/test/goldens/SecWidget/Waiting for measurement/l-dark.png index 7cb1279..63ed3b1 100644 Binary files a/test/goldens/SecWidget/Waiting for measurement/l-dark.png and b/test/goldens/SecWidget/Waiting for measurement/l-dark.png differ diff --git a/test/goldens/SecWidget/Waiting for measurement/l-light.png b/test/goldens/SecWidget/Waiting for measurement/l-light.png index 3d6b00d..ba9abb9 100644 Binary files a/test/goldens/SecWidget/Waiting for measurement/l-light.png and b/test/goldens/SecWidget/Waiting for measurement/l-light.png differ diff --git a/test/goldens/SecWidget/Waiting for measurement/m-dark.png b/test/goldens/SecWidget/Waiting for measurement/m-dark.png index 52ff6e4..d85572f 100644 Binary files a/test/goldens/SecWidget/Waiting for measurement/m-dark.png and b/test/goldens/SecWidget/Waiting for measurement/m-dark.png differ diff --git a/test/goldens/SecWidget/Waiting for measurement/m-light.png b/test/goldens/SecWidget/Waiting for measurement/m-light.png index ef9d600..95fce76 100644 Binary files a/test/goldens/SecWidget/Waiting for measurement/m-light.png and b/test/goldens/SecWidget/Waiting for measurement/m-light.png differ diff --git a/test/goldens/SecWidget/Waiting for measurement/s-dark.png b/test/goldens/SecWidget/Waiting for measurement/s-dark.png index 5012ce5..fbd59fc 100644 Binary files a/test/goldens/SecWidget/Waiting for measurement/s-dark.png and b/test/goldens/SecWidget/Waiting for measurement/s-dark.png differ diff --git a/test/goldens/SecWidget/Waiting for measurement/s-light.png b/test/goldens/SecWidget/Waiting for measurement/s-light.png index 67b4d80..5d35478 100644 Binary files a/test/goldens/SecWidget/Waiting for measurement/s-light.png and b/test/goldens/SecWidget/Waiting for measurement/s-light.png differ diff --git a/test/sec_widget_test.dart b/test/sec_widget_test.dart index c8218fe..c78f63d 100644 --- a/test/sec_widget_test.dart +++ b/test/sec_widget_test.dart @@ -159,14 +159,15 @@ void main() { await tester.tap(find.text('Start scan')); - strategy.startMeasurementCompleter.completeError(''); + await tester.pump(const Duration(seconds: 36)); await tester.pumpAndSettle(); return () async {}; }, 'Measure Complete': (tester, place) async { - final strategy = CompleterStrategy(withReaders: true); + final strategy = + CompleterStrategy(withReaders: true, useDelays: true); final storageAdapter = MockStorageAdapter(); @@ -189,16 +190,20 @@ void main() { await tester.pumpAndSettle(); - strategy.primeCompleter.complete(); + await tester.pumpAndSettle(); + + await tester.pump(const Duration(seconds: 1)); await tester.pumpAndSettle(); await tester.tap(find.text('Start scan')); - strategy.startMeasurementCompleter.complete(); + await tester.pump(const Duration(seconds: 1)); await tester.pumpAndSettle(); + await tester.pump(const Duration(seconds: 2)); + return () async {}; }, }, diff --git a/test/test_utils.dart b/test/test_utils.dart index 7988cd5..54a6ad3 100644 --- a/test/test_utils.dart +++ b/test/test_utils.dart @@ -24,13 +24,17 @@ final reader3 = FoundDevice( ); class CompleterStrategy { - CompleterStrategy({bool withReaders = false}) { + CompleterStrategy({bool withReaders = false, this.useDelays = false}) { strategy = UrpVirtualStrategy((UrpRequest request) async { final payload = UrpSecCommandWrapper.fromBuffer(request.payload); switch (payload.deviceCommand.command) { case (UrpSecCommand.urpSecPrime): primeCompleter = Completer(); - await primeCompleter.future; + if (useDelays) { + await Future.delayed(const Duration(seconds: 1)); + } else { + await primeCompleter.future; + } return UrpResponse(); @@ -48,10 +52,11 @@ class CompleterStrategy { case UrpSecCommand.urpSecStartMeasurement: startMeasurementCompleter = Completer(); - try { + + if (useDelays) { + await Future.delayed(const Duration(seconds: 1)); + } else { await startMeasurementCompleter.future; - } catch (e) { - return null; } return UrpResponse( @@ -89,6 +94,7 @@ class CompleterStrategy { ); } } + final bool useDelays; Completer primeCompleter = Completer(); Completer startMeasurementCompleter = Completer(); late UrpVirtualStrategy strategy;