From 8205b923c9c349daa352e1f1bb56eb210d922612 Mon Sep 17 00:00:00 2001 From: Talwinder kaur Date: Mon, 24 Jul 2023 20:00:47 -0400 Subject: [PATCH] feat(app): Update the invalid pin error handling scenario Signed-off-by: Talwinder kaur --- .../walletsdk/flutter/app/MainActivity.kt | 29 +- demo/app/ios/Runner/flutterPlugin.swift | 25 +- demo/app/lib/views/credential_shared.dart | 5 +- demo/app/lib/views/custom_error.dart | 2 +- demo/app/lib/views/otp.dart | 318 +++++++++++------- .../app/lib/wallet_sdk/wallet_sdk_mobile.dart | 7 + demo/app/lib/wallet_sdk/wallet_sdk_model.dart | 39 +++ 7 files changed, 285 insertions(+), 140 deletions(-) diff --git a/demo/app/android/app/src/main/kotlin/walletsdk/flutter/app/MainActivity.kt b/demo/app/android/app/src/main/kotlin/walletsdk/flutter/app/MainActivity.kt index 08f7ee917..215401ce8 100644 --- a/demo/app/android/app/src/main/kotlin/walletsdk/flutter/app/MainActivity.kt +++ b/demo/app/android/app/src/main/kotlin/walletsdk/flutter/app/MainActivity.kt @@ -118,14 +118,6 @@ class MainActivity : FlutterActivity() { } } - "fetchDID" -> { - try { - val didID = call.argument("didID") - } catch (e: Exception) { - result.error("Exception", "Error while setting fetched DID", e) - } - } - "serializeDisplayData" -> { try { val credentialDisplay = serializeDisplayData(call) @@ -164,6 +156,15 @@ class MainActivity : FlutterActivity() { } } + "parseWalletError" -> { + try { + val parsedWalletError = parseWalletSDKError(call) + result.success(parsedWalletError) + } catch (e: Exception) { + result.error("Exception", "Error while parsing wallet sdk error", e) + } + } + "getIssuerID" -> { try { val issuerID = getIssuerID(call) @@ -400,7 +401,19 @@ class MainActivity : FlutterActivity() { } + private fun parseWalletSDKError(call: MethodCall): MutableMap { + val localizedErrorMessage = call.argument("localizedErrorMessage") ?: throw java.lang.Exception("localizedErrorMessage is missed") + val parsedError = Walleterror.parse(localizedErrorMessage) + + val parsedErrResp: MutableMap = mutableMapOf() + parsedErrResp["category"] = parsedError.category + parsedErrResp["details"] = parsedError.details + parsedErrResp["code"] = parsedError.code + parsedErrResp["traceID"] = parsedError.traceID + + return parsedErrResp + } /** * ResolveDisplay resolves display information for issued credentials based on an issuer's metadata, which is fetched using the issuer's (base) URI. The CredentialDisplays returns DisplayData object correspond to the VCs passed in and are in the diff --git a/demo/app/ios/Runner/flutterPlugin.swift b/demo/app/ios/Runner/flutterPlugin.swift index a19cacbbf..d5832a18f 100644 --- a/demo/app/ios/Runner/flutterPlugin.swift +++ b/demo/app/ios/Runner/flutterPlugin.swift @@ -43,6 +43,9 @@ public class SwiftWalletSDKPlugin: NSObject, FlutterPlugin { let otp = fetchArgsKeyValue(call, key: "otp") requestCredential(otp: otp!, result: result) + case "parseWalletSDKError": + parseWalletSDKError(arguments: arguments!, result: result) + case "requestCredentialWithAuth": let redirectURIWithParams = fetchArgsKeyValue(call, key: "redirectURIWithParams") requestCredentialWithAuth(redirectURIWithParams: redirectURIWithParams!, result: result) @@ -528,12 +531,32 @@ public class SwiftWalletSDKPlugin: NSObject, FlutterPlugin { vcCredentials: convertToVerifiableCredentialsArray(credentials: vcCredentials)) result(displayDataResp) } catch let error as NSError { + let parsedError = WalleterrorParse(error.localizedDescription) return result(FlutterError.init(code: "Exception", message: "error while resolving credential", - details: error.localizedDescription)) + details: parsedError)) + } } + public func parseWalletSDKError(arguments: Dictionary, result: @escaping FlutterResult){ + guard let localizedErrorMessage = arguments["localizedErrorMessage"] as? String else{ + return result(FlutterError.init(code: "NATIVE_ERR", + message: "error while parsing Wallet-SDK error", + details: "localizedErrorMessage is missing")) + } + let parsedError = WalleterrorParse(localizedErrorMessage)! + + var parsedErrorResult :[String:Any] = [ + "category": parsedError.category, + "details": parsedError.details, + "code": parsedError.code, + "traceID": parsedError.traceID + ] + result(parsedErrorResult) + } + + public func resolveCredentialDisplay(arguments: Dictionary, result: @escaping FlutterResult){ diff --git a/demo/app/lib/views/credential_shared.dart b/demo/app/lib/views/credential_shared.dart index 5eea54f2f..cc6295819 100644 --- a/demo/app/lib/views/credential_shared.dart +++ b/demo/app/lib/views/credential_shared.dart @@ -1,4 +1,3 @@ -import 'package:app/widgets/success_card.dart'; import 'package:flutter/material.dart'; import 'package:app/models/credential_data.dart'; import 'package:app/widgets/credential_card.dart'; @@ -57,12 +56,12 @@ class CredentialSharedState extends State { Image.asset('lib/assets/images/success.png') ], ), - title: Text('Success',textAlign: TextAlign.left, style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), + title: const Text('Success',textAlign: TextAlign.left, style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), subtitle: Text("Credentials have been shared with ${widget.verifierName}", style: const TextStyle(fontSize: 14, fontWeight: FontWeight.normal)), ), Expanded( child: ListView.builder( - padding: EdgeInsets.only(left: 24, right:24, top: 24, bottom: 8), + padding: const EdgeInsets.only(left: 24, right:24, top: 24, bottom: 8), itemCount: widget.credentialData.length, itemBuilder: (BuildContext context, int index) { return CredentialCard(credentialData: widget.credentialData[index], isDashboardWidget: true, isDetailArrowRequired: false,); diff --git a/demo/app/lib/views/custom_error.dart b/demo/app/lib/views/custom_error.dart index 9add05fd4..2ccc13acf 100644 --- a/demo/app/lib/views/custom_error.dart +++ b/demo/app/lib/views/custom_error.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; -import '../widgets/common_title_appbar.dart'; +import 'package:app/widgets/common_title_appbar.dart'; class CustomError extends StatefulWidget { final String requestErrorTitleMsg; diff --git a/demo/app/lib/views/otp.dart b/demo/app/lib/views/otp.dart index 333fd5ef3..5a31612b5 100644 --- a/demo/app/lib/views/otp.dart +++ b/demo/app/lib/views/otp.dart @@ -1,7 +1,6 @@ import 'dart:convert'; import 'dart:developer'; -import 'package:app/main.dart'; import 'package:app/models/activity_data_object.dart'; import 'package:app/models/credential_data.dart'; import 'package:flutter/material.dart'; @@ -11,6 +10,7 @@ import 'package:app/services/storage_service.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:app/widgets/primary_button.dart'; import 'package:app/widgets/primary_input_field.dart'; +import 'package:app/wallet_sdk/wallet_sdk_mobile.dart'; import 'credential_preview.dart'; class OTP extends StatefulWidget { @@ -19,6 +19,7 @@ class OTP extends StatefulWidget { @override State createState() => _OTPPage(); } + class _OTPPage extends State { final StorageService _storageService = StorageService(); final Future prefs = SharedPreferences.getInstance(); @@ -26,6 +27,8 @@ class _OTPPage extends State { var userDIDId = ''; var userDIDDoc = ''; + var WalletSDKPlugin = WalletSDK(); + Future _createDid() async { final SharedPreferences pref = await prefs; var didType = pref.getString('didType'); @@ -44,15 +47,22 @@ class _OTPPage extends State { return didID; } - // This is the entered code // It will be displayed in a Text widget String? _otp; String _requestErrorSubTitleMsg = ''; String _requestErrorTitleMsg = ''; + String _requestErrorDetailMsg = ''; String? actionText = 'Submit'; late double topPadding; bool show = false; + bool showDetail = false; + + void detailToggle() { + setState(() { + showDetail = !showDetail; + }); + } @override Widget build(BuildContext context) { @@ -60,140 +70,194 @@ class _OTPPage extends State { final width = MediaQuery.of(context).size.width; return GestureDetector( onTap: () { - FocusManager.instance.primaryFocus?.unfocus(); - }, - child: Scaffold( - appBar: const CustomTitleAppBar(pageTitle: 'Enter OTP', addCloseIcon: true, height: 60,), - backgroundColor: const Color(0xffF4F1F5), - body: Center( - child: ListView( - padding: const EdgeInsets.all(24), - children: [ - Container( - padding: const EdgeInsets.fromLTRB(14, 4, 14, 16), + FocusManager.instance.primaryFocus?.unfocus(); + }, + child: Scaffold( + appBar: const CustomTitleAppBar( + pageTitle: 'Enter OTP', + addCloseIcon: true, + height: 60, ), - Container( - padding: const EdgeInsets.fromLTRB(14, 4, 14, 16), - child: PrimaryInputField(textController: otpController, maxLength: 6, labelText: 'Enter OTP Code', textInputFormatter: FilteringTextInputFormatter.digitsOnly), - ), - Column( - children: [ - Column( - children: [ - Visibility( - visible: show, - child: Container ( - padding: const EdgeInsets.all(12), - alignment: Alignment.center, - child: ListTile( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - tileColor: const Color(0xffFBF8FC), - title: SelectableText( - _requestErrorTitleMsg ?? '', - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Color(0xff190C21), + backgroundColor: const Color(0xffF4F1F5), + body: Center( + child: ListView( + padding: const EdgeInsets.all(24), + children: [ + Container( + padding: const EdgeInsets.fromLTRB(14, 4, 14, 16), + ), + Container( + padding: const EdgeInsets.fromLTRB(14, 4, 14, 16), + child: PrimaryInputField( + textController: otpController, + maxLength: 6, + labelText: 'Enter OTP Code', + textInputFormatter: FilteringTextInputFormatter.digitsOnly), + ), + Column( + children: [ + Column( + children: [ + Visibility( + visible: show, + child: Container( + padding: const EdgeInsets.all(12), + alignment: Alignment.center, + child: ListTile( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + tileColor: const Color(0xffFBF8FC), + title: SelectableText( + _requestErrorTitleMsg ?? '', + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Color(0xff190C21), + ), + textAlign: TextAlign.start, + ), + subtitle: SelectableText( + _requestErrorSubTitleMsg ?? '', + style: const TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + color: Color(0xff6C6D7C), + ), + textAlign: TextAlign.start, + ), + trailing: IconButton( + icon: const Icon( + Icons.more_vert, + size: 20.0, + ), + onPressed: () { + detailToggle(); + }, + ), + //TODO need to add fallback and network image url + leading: const SizedBox( + height: 24, + width: 24, + child: Image( + image: AssetImage('lib/assets/images/errorVector.png'), + width: 24, + height: 24, + fit: BoxFit.cover, + )), + ), ), - textAlign: TextAlign.start, ), - subtitle: SelectableText( - _requestErrorSubTitleMsg ?? '', - style: const TextStyle( - fontSize: 12, - fontWeight: FontWeight.bold, - color: Color(0xff6C6D7C), - ), - textAlign: TextAlign.start, + Visibility( + visible: showDetail, + child: Padding( + padding: const EdgeInsets.all(16), + child: SelectableText( + _requestErrorDetailMsg ?? '', + style: const TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + color: Color(0xff6C6D7C), + ), + textAlign: TextAlign.start, + ), + )), + Padding( + padding: EdgeInsets.only(top: height - width), + ), + PrimaryButton( + onPressed: () async { + setState(() { + _otp = otpController.text; + }); + + String? credentials; + String? serializeDisplayData; + try { + final SharedPreferences pref = await prefs; + await _createDid(); + pref.setString('userDID', userDIDId); + pref.setString('userDIDDoc', userDIDDoc); + credentials = await WalletSDKPlugin.requestCredential(_otp!); + String? issuerURI = await WalletSDKPlugin.issuerURI(); + serializeDisplayData = + await WalletSDKPlugin.serializeDisplayData([credentials], issuerURI!); + log("serializeDisplayData -> $serializeDisplayData"); + var activities = await WalletSDKPlugin.storeActivityLogger(); + var credID = await WalletSDKPlugin.getCredID([credentials]); + log("activities and credID -$activities and $credID"); + _storageService.addActivities(ActivityDataObj(credID!, activities)); + pref.setString("credID", credID); + _navigateToCredPreviewScreen( + credentials, issuerURI, serializeDisplayData!, userDIDId, credID); + } catch (err) { + String errorMessage = err.toString(); + log("errorMessage-> $errorMessage"); + if (err is PlatformException && err.message != null && err.message!.isNotEmpty) { + log("err.details-> ${err.details}"); + var resp = + await WalletSDKPlugin.parseWalletSDKError(localizedErrorMessage: err.details); + (resp.category == "INVALID_GRANT") + ? { + errorMessage = "Try re-entering the PIN or scan a new QR code", + _requestErrorDetailMsg = resp.details + } + : (resp.category == "INVALID_TOKEN") + ? { + errorMessage = "Try scanning a new QR code", + _requestErrorDetailMsg = resp.details + } + : {errorMessage = resp.details, _requestErrorDetailMsg = resp.traceID}; + } + setState(() { + _requestErrorSubTitleMsg = errorMessage; + _requestErrorTitleMsg = 'Oops! Something went wrong!'; + actionText = 'Re-enter'; + show = true; + topPadding = height * 0.20; + _clearOTPInput(); + }); + } + }, + width: MediaQuery.of(context).size.width, + child: Text(actionText!, style: const TextStyle(fontSize: 16, color: Colors.white))), + const Padding( + padding: EdgeInsets.only(top: 8), ), - //TODO need to add fallback and network image url - leading: const SizedBox( - height: 24, - width: 24, - child: Image( - image: AssetImage('lib/assets/images/errorVector.png'), - width: 24, - height: 24, - fit: BoxFit.cover, - ) + PrimaryButton( + onPressed: () {}, + width: double.infinity, + gradient: const LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Color(0xffFFFFFF), Color(0xffFFFFFF)]), + child: const Text('Cancel', style: TextStyle(fontSize: 16, color: Color(0xff6C6D7C))), ), - ), + ], ), - ), - Padding( - padding: EdgeInsets.only(top: height-width), - ), - PrimaryButton( - onPressed: () async { - setState(() { - _otp = otpController.text; - }); - - String? credentials; - String? serializeDisplayData; - try { - final SharedPreferences pref = await prefs; - await _createDid(); - pref.setString('userDID',userDIDId); - pref.setString('userDIDDoc',userDIDDoc); - credentials = await WalletSDKPlugin.requestCredential(_otp!); - String? issuerURI = await WalletSDKPlugin.issuerURI(); - serializeDisplayData = await WalletSDKPlugin.serializeDisplayData([credentials], issuerURI!); - log("serializeDisplayData -> $serializeDisplayData"); - var activities = await WalletSDKPlugin.storeActivityLogger(); - var credID = await WalletSDKPlugin.getCredID([credentials]); - log("activities and credID -$activities and $credID"); - _storageService.addActivities(ActivityDataObj(credID!, activities)); - pref.setString("credID", credID); - _navigateToCredPreviewScreen(credentials, issuerURI, serializeDisplayData!, userDIDId, credID); - } catch (err) { - String errorMessage = err.toString(); - if (err is PlatformException && - err.message != null && - err.message!.isNotEmpty) { - errorMessage = err.details!; - } - setState(() { - _requestErrorSubTitleMsg = errorMessage; - _requestErrorTitleMsg = 'Error'; - actionText = 'Re-enter'; - show = true; - topPadding = height*0.20; - _clearOTPInput(); - }); - } - }, - width: double.infinity, - child: Text(actionText!, style: const TextStyle(fontSize: 16, color: Colors.white)) - ), - const Padding( - padding: EdgeInsets.only(top: 8), - ), - PrimaryButton( - onPressed: (){}, - width: double.infinity, - gradient: const LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [Color(0xffFFFFFF), Color(0xffFFFFFF)]), - child: const Text('Cancel', style: TextStyle(fontSize: 16, color: Color(0xff6C6D7C))), - ), - ], - ), - ], - ), - ], - ), - ))); + ], + ), + ], + ), + ))); } - _navigateToCredPreviewScreen(String credentialResp, String issuerURI, String credentialDisplayData, String didID, String credID) async { - Navigator.push(context, MaterialPageRoute(builder: (context) => CredentialPreview(credentialData: CredentialData(rawCredential: credentialResp, issuerURL: issuerURI, credentialDisplayData: credentialDisplayData, credentialDID: didID, credID: credID),))); + + _navigateToCredPreviewScreen( + String credentialResp, String issuerURI, String credentialDisplayData, String didID, String credID) async { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => CredentialPreview( + credentialData: CredentialData( + rawCredential: credentialResp, + issuerURL: issuerURI, + credentialDisplayData: credentialDisplayData, + credentialDID: didID, + credID: credID), + ))); } - _clearOTPInput(){ + _clearOTPInput() { otpController.clear(); } } - diff --git a/demo/app/lib/wallet_sdk/wallet_sdk_mobile.dart b/demo/app/lib/wallet_sdk/wallet_sdk_mobile.dart index 3fda418c1..c4e0f09c5 100644 --- a/demo/app/lib/wallet_sdk/wallet_sdk_mobile.dart +++ b/demo/app/lib/wallet_sdk/wallet_sdk_mobile.dart @@ -4,6 +4,7 @@ Copyright Gen Digital Inc. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ +import 'dart:convert'; import 'dart:developer'; import 'package:app/wallet_sdk/wallet_sdk_model.dart'; @@ -65,6 +66,12 @@ class WalletSDK extends WalletPlatform { } } + Future parseWalletSDKError({required String localizedErrorMessage}) async { + var parsedWalletError = await methodChannel.invokeMethod( + 'parseWalletSDKError', {'localizedErrorMessage': localizedErrorMessage}); + return WalletSDKError.fromJson(jsonDecode(json.encode(parsedWalletError))); + } + Future requestCredentialWithAuth(String redirectURIWithParams) async { try { var credentialResponse = await methodChannel.invokeMethod( diff --git a/demo/app/lib/wallet_sdk/wallet_sdk_model.dart b/demo/app/lib/wallet_sdk/wallet_sdk_model.dart index e31de6082..e08ec4a0e 100644 --- a/demo/app/lib/wallet_sdk/wallet_sdk_model.dart +++ b/demo/app/lib/wallet_sdk/wallet_sdk_model.dart @@ -95,6 +95,45 @@ class SupportedCredentials { } } +class WalletSDKError { + final String code; + final String category; + final String details; + final String traceID; + + const WalletSDKError({ + required this.code, + required this.category, + required this.details, + required this.traceID, + }); + + @override + String toString() { + return 'WalletSDKError { code: $code, category: $category, details: $details, traceID: $traceID }'; + } + + Map toJson() { + final Map data = {}; + data['code'] = code; + data['category'] = category; + data['details'] = details; + data['traceID'] = traceID; + return data; + } + + factory WalletSDKError.fromJson(Map json) { + return WalletSDKError( + code: json['code'], + category: json['category'], + details: json['details'], + traceID: json['traceID'], + ); + } + + +} + class CredentialDisplayData { final String issuerName; final String overviewName;