diff --git a/.fvm/fvm_config.json b/.fvm/fvm_config.json index 2bb33481b..e3ffe0b4a 100644 --- a/.fvm/fvm_config.json +++ b/.fvm/fvm_config.json @@ -1,4 +1,4 @@ { - "flutterSdkVersion": "3.13.3", + "flutterSdkVersion": "3.13.4", "flavors": {} } \ No newline at end of file diff --git a/ios/Podfile b/ios/Podfile index a353ca412..6b6ac2fb6 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -38,17 +38,31 @@ target 'Runner' do flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) end + +$iOSVersion = '14.0' + post_install do |installer| + # Google's ML Kit Barcode Scanning setup + installer.pods_project.build_configurations.each do |config| + config.build_settings["EXCLUDED_ARCHS[sdk=*]"] = "armv7" + config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = $iOSVersion + end + installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) - # polygonid-setup + # polygonid-setup target.build_configurations.each do |config| cflags = config.build_settings['OTHER_CFLAGS'] || ['$(inherited)'] cflags << '-fembed-bitcode' config.build_settings['OTHER_CFLAGS'] = cflags config.build_settings['SWIFT_VERSION'] = '5.0' config.build_settings['ENABLE_BITCODE'] = 'NO' - config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '14.0' + config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = $iOSVersion + + # Google's ML Kit Barcode Scanning setup + if Gem::Version.new($iOSVersion) > Gem::Version.new(config.build_settings['IPHONEOS_DEPLOYMENT_TARGET']) + config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = $iOSVersion + end end if target.name == "Pods-Runner" puts "Updating #{target.name} OTHER_LDFLAGS" diff --git a/lib/app/shared/constants/secure_storage_keys.dart b/lib/app/shared/constants/secure_storage_keys.dart index 29d2ffd63..0cf02e515 100644 --- a/lib/app/shared/constants/secure_storage_keys.dart +++ b/lib/app/shared/constants/secure_storage_keys.dart @@ -29,6 +29,8 @@ class SecureStorageKeys { static const String userPINCodeForAuthentication = 'userPINCodeForAuthentication'; + static const String isSecurityLow = 'isSecurityLow'; + static const String pinCode = 'pinCode'; static const String data = 'data'; static const String rsaKeyJson = 'RSAKeyJson'; diff --git a/lib/app/shared/enum/message/response_string/response_string.dart b/lib/app/shared/enum/message/response_string/response_string.dart index 9b0c2b236..58cf642c3 100644 --- a/lib/app/shared/enum/message/response_string/response_string.dart +++ b/lib/app/shared/enum/message/response_string/response_string.dart @@ -153,4 +153,6 @@ enum ResponseString { RESPONSE_STRING_youcanSelectOnlyXCredential, RESPONSE_STRING_theCredentialIsNotReady, RESPONSE_STRING_theCredentialIsNoMoreReady, + RESPONSE_STRING_theRequestIsRejected, + RESPONSE_STRING_userPinIsIncorrect, } diff --git a/lib/app/shared/enum/message/response_string/response_string_extension.dart b/lib/app/shared/enum/message/response_string/response_string_extension.dart index ef6d0f472..49937ae84 100644 --- a/lib/app/shared/enum/message/response_string/response_string_extension.dart +++ b/lib/app/shared/enum/message/response_string/response_string_extension.dart @@ -483,6 +483,12 @@ extension ResponseStringX on ResponseString { case ResponseString.RESPONSE_STRING_theCredentialIsNoMoreReady: return globalMessage.RESPONSE_STRING_theCredentialIsNoMoreReady; + + case ResponseString.RESPONSE_STRING_theRequestIsRejected: + return globalMessage.RESPONSE_STRING_theRequestIsRejected; + + case ResponseString.RESPONSE_STRING_userPinIsIncorrect: + return globalMessage.RESPONSE_STRING_userPinIsIncorrect; } } } diff --git a/lib/app/shared/helper_functions/helper_functions.dart b/lib/app/shared/helper_functions/helper_functions.dart index 8d511f732..c4b0b0938 100644 --- a/lib/app/shared/helper_functions/helper_functions.dart +++ b/lib/app/shared/helper_functions/helper_functions.dart @@ -439,7 +439,6 @@ List sortedPublcJwk(Map privateKey) { bool isUriAsValueValid(List keys) => keys.contains('response_type') && keys.contains('client_id') && - keys.contains('redirect_uri') && keys.contains('nonce'); bool isPolygonIdUrl(String url) => @@ -577,9 +576,9 @@ Future getHost({ return Uri.parse(decodedResponse['redirect_uri'].toString()).host; } else { - return Uri.parse( - uri.queryParameters['redirect_uri'] ?? '', - ).host; + final String? redirectUri = getRedirectUri(uri); + if (redirectUri == null) return ''; + return Uri.parse(redirectUri).host; } } } @@ -628,3 +627,26 @@ Future<(String?, String)> getIssuerAndPreAuthorizedCode({ return (preAuthorizedCode, issuer); } + +bool isURL(String input) { + final Uri? uri = Uri.tryParse(input); + return uri != null && uri.hasScheme; +} + +String? getRedirectUri(Uri uri) { + final clientId = uri.queryParameters['client_id'] ?? ''; + final redirectUri = uri.queryParameters['redirect_uri']; + + /// if redirectUri is not provided and client_id is url then + /// redirectUri = client_id + if (redirectUri == null) { + final isUrl = isURL(clientId); + if (isUrl) { + return clientId; + } else { + return null; + } + } else { + return redirectUri; + } +} diff --git a/lib/app/shared/message_handler/global_message.dart b/lib/app/shared/message_handler/global_message.dart index 459df0984..4e6ba3078 100644 --- a/lib/app/shared/message_handler/global_message.dart +++ b/lib/app/shared/message_handler/global_message.dart @@ -385,4 +385,6 @@ class GlobalMessage { l10n.theCredentialIsNotReady; String get RESPONSE_STRING_theCredentialIsNoMoreReady => l10n.theCredentialIsNoMoreReady; + String get RESPONSE_STRING_theRequestIsRejected => l10n.theRequestIsRejected; + String get RESPONSE_STRING_userPinIsIncorrect => l10n.userPinIsIncorrect; } diff --git a/lib/app/shared/message_handler/response_message.dart b/lib/app/shared/message_handler/response_message.dart index 56feaa96b..889bf47ea 100644 --- a/lib/app/shared/message_handler/response_message.dart +++ b/lib/app/shared/message_handler/response_message.dart @@ -718,6 +718,16 @@ class ResponseMessage with MessageHandler { .localise( context, ); + + case ResponseString.RESPONSE_STRING_theRequestIsRejected: + return ResponseString.RESPONSE_STRING_theRequestIsRejected.localise( + context, + ); + + case ResponseString.RESPONSE_STRING_userPinIsIncorrect: + return ResponseString.RESPONSE_STRING_userPinIsIncorrect.localise( + context, + ); } } return ''; diff --git a/lib/credentials/cubit/credentials_cubit.dart b/lib/credentials/cubit/credentials_cubit.dart index 320f6a4d2..9ec6a1bd4 100644 --- a/lib/credentials/cubit/credentials_cubit.dart +++ b/lib/credentials/cubit/credentials_cubit.dart @@ -329,13 +329,18 @@ class CredentialsCubit extends Cubit { if (iteratedCredentialSubjectModel.credentialSubjectType == CredentialSubjectType.emailPass) { + /// check if email is same if (email == (iteratedCredentialSubjectModel as EmailPassModel).email) { - await deleteById( - id: storedCredential.id, - showMessage: false, - ); - break; + /// format should be same ldp_vc or jwt_vc + if ((credential.jwt == null && storedCredential.jwt == null) || + (credential.jwt != null && storedCredential.jwt != null)) { + await deleteById( + id: storedCredential.id, + showMessage: false, + ); + break; + } } } } diff --git a/lib/dashboard/drawer/wallet_security/security/security.dart b/lib/dashboard/drawer/wallet_security/security/security.dart new file mode 100644 index 000000000..eedd33b9e --- /dev/null +++ b/lib/dashboard/drawer/wallet_security/security/security.dart @@ -0,0 +1 @@ +export 'view/security_page.dart'; diff --git a/lib/dashboard/drawer/wallet_security/security/view/security_page.dart b/lib/dashboard/drawer/wallet_security/security/view/security_page.dart new file mode 100644 index 000000000..dd99dc3d9 --- /dev/null +++ b/lib/dashboard/drawer/wallet_security/security/view/security_page.dart @@ -0,0 +1,116 @@ +import 'package:altme/app/app.dart'; +import 'package:altme/dashboard/profile/profile.dart'; +import 'package:altme/l10n/l10n.dart'; +import 'package:altme/theme/theme.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class SecurityPage extends StatelessWidget { + const SecurityPage({super.key}); + + static Route route() { + return MaterialPageRoute( + builder: (_) => const SecurityPage(), + settings: const RouteSettings(name: '/SecurityPage'), + ); + } + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + return BasePage( + title: l10n.security, + useSafeArea: true, + scrollView: false, + titleAlignment: Alignment.topCenter, + padding: const EdgeInsets.symmetric(horizontal: Sizes.spaceSmall), + titleLeading: const BackLeadingButton(), + body: BlocBuilder( + builder: (context, state) { + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisSize: MainAxisSize.max, + children: [ + Container( + padding: const EdgeInsets.all(Sizes.spaceSmall), + margin: const EdgeInsets.all(Sizes.spaceXSmall), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.cardHighlighted, + borderRadius: const BorderRadius.all( + Radius.circular(Sizes.largeRadius), + ), + ), + child: Column( + children: [ + ListTile( + onTap: () { + context + .read() + .setSecurityLevel(isSecurityLow: true); + }, + shape: const RoundedRectangleBorder( + side: BorderSide( + color: Color(0xFFDDDDEE), + width: 0.5, + ), + ), + title: Text( + l10n.lowSecurity, + style: Theme.of(context).textTheme.bodyLarge?.copyWith( + color: Theme.of(context).colorScheme.onPrimary, + ), + ), + trailing: Icon( + state.model.isSecurityLow + ? Icons.radio_button_checked + : Icons.radio_button_unchecked, + size: Sizes.icon2x, + color: Theme.of(context).colorScheme.onPrimary, + ), + ), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: Sizes.spaceSmall, + vertical: Sizes.spaceXSmall, + ), + child: Divider( + height: 0, + color: Theme.of(context).colorScheme.borderColor, + ), + ), + ListTile( + onTap: () { + context + .read() + .setSecurityLevel(isSecurityLow: false); + }, + shape: const RoundedRectangleBorder( + side: BorderSide( + color: Color(0xFFDDDDEE), + width: 0.5, + ), + ), + title: Text( + l10n.highSecurity, + style: Theme.of(context).textTheme.bodyLarge?.copyWith( + color: Theme.of(context).colorScheme.onPrimary, + ), + ), + trailing: Icon( + !state.model.isSecurityLow + ? Icons.radio_button_checked + : Icons.radio_button_unchecked, + size: Sizes.icon2x, + color: Theme.of(context).colorScheme.onPrimary, + ), + ), + ], + ), + ), + ], + ); + }, + ), + ); + } +} diff --git a/lib/dashboard/drawer/wallet_security/wallet_security.dart b/lib/dashboard/drawer/wallet_security/wallet_security.dart index 2404ae08d..7112498e5 100644 --- a/lib/dashboard/drawer/wallet_security/wallet_security.dart +++ b/lib/dashboard/drawer/wallet_security/wallet_security.dart @@ -1,2 +1,3 @@ export 'recovery_key/recovery_key.dart'; +export 'security/security.dart'; export 'wallet_security/wallet_security.dart'; diff --git a/lib/dashboard/drawer/wallet_security/wallet_security/view/wallet_security_menu.dart b/lib/dashboard/drawer/wallet_security/wallet_security/view/wallet_security_menu.dart index d1e2d4e23..0d720a6f9 100644 --- a/lib/dashboard/drawer/wallet_security/wallet_security/view/wallet_security_menu.dart +++ b/lib/dashboard/drawer/wallet_security/wallet_security/view/wallet_security_menu.dart @@ -231,6 +231,12 @@ class WalletSecurityView extends StatelessWidget { } }, ), + DrawerItem( + title: l10n.security, + onTap: () { + Navigator.of(context).push(SecurityPage.route()); + }, + ), ], ), ), diff --git a/lib/dashboard/profile/cubit/profile_cubit.dart b/lib/dashboard/profile/cubit/profile_cubit.dart index ba0d42217..e23805ae9 100644 --- a/lib/dashboard/profile/cubit/profile_cubit.dart +++ b/lib/dashboard/profile/cubit/profile_cubit.dart @@ -102,6 +102,12 @@ class ProfileCubit extends Cubit { .get(SecureStorageKeys.userConsentForVerifierAccess)) == 'true'; + final isSecurityLowValue = + await secureStorageProvider.get(SecureStorageKeys.isSecurityLow); + + final isSecurityLow = + isSecurityLowValue == null || isSecurityLowValue == 'true'; + final userPINCodeForAuthenticationValue = await secureStorageProvider .get(SecureStorageKeys.userPINCodeForAuthentication); final userPINCodeForAuthentication = @@ -139,6 +145,7 @@ class ProfileCubit extends Cubit { userConsentForVerifierAccess: userConsentForVerifierAccess, userPINCodeForAuthentication: userPINCodeForAuthentication, oidc4vcType: oidc4vcType, + isSecurityLow: isSecurityLow, ); emit( @@ -240,6 +247,11 @@ class ProfileCubit extends Cubit { profileModel.oidc4vcType.name, ); + await secureStorageProvider.set( + SecureStorageKeys.isSecurityLow, + profileModel.isSecurityLow.toString(), + ); + emit( state.copyWith( model: profileModel, @@ -310,6 +322,11 @@ class ProfileCubit extends Cubit { await update(profileModel); } + Future setSecurityLevel({bool isSecurityLow = true}) async { + final profileModel = state.model.copyWith(isSecurityLow: isSecurityLow); + await update(profileModel); + } + @override Future close() async { _timer?.cancel(); diff --git a/lib/dashboard/profile/models/profile.dart b/lib/dashboard/profile/models/profile.dart index 6c77b14e7..c140c34b4 100644 --- a/lib/dashboard/profile/models/profile.dart +++ b/lib/dashboard/profile/models/profile.dart @@ -24,6 +24,7 @@ class ProfileModel extends Equatable { this.companyWebsite = '', this.jobTitle = '', required this.oidc4vcType, + required this.isSecurityLow, }); factory ProfileModel.fromJson(Map json) => @@ -47,6 +48,7 @@ class ProfileModel extends Equatable { userPINCodeForAuthentication: true, tezosNetwork: TezosNetwork.mainNet(), oidc4vcType: OIDC4VCType.EBSIV3, + isSecurityLow: true, ); final String firstName; @@ -66,6 +68,7 @@ class ProfileModel extends Equatable { final bool userConsentForVerifierAccess; final bool userPINCodeForAuthentication; final OIDC4VCType oidc4vcType; + final bool isSecurityLow; @override List get props => [ @@ -86,6 +89,7 @@ class ProfileModel extends Equatable { userConsentForVerifierAccess, userPINCodeForAuthentication, oidc4vcType, + isSecurityLow, ]; Map toJson() => _$ProfileModelToJson(this); @@ -108,6 +112,7 @@ class ProfileModel extends Equatable { bool? userConsentForVerifierAccess, bool? userPINCodeForAuthentication, OIDC4VCType? oidc4vcType, + bool? isSecurityLow, }) { return ProfileModel( firstName: firstName ?? this.firstName, @@ -130,6 +135,7 @@ class ProfileModel extends Equatable { userPINCodeForAuthentication: userPINCodeForAuthentication ?? this.userPINCodeForAuthentication, oidc4vcType: oidc4vcType ?? this.oidc4vcType, + isSecurityLow: isSecurityLow ?? this.isSecurityLow, ); } } diff --git a/lib/dashboard/qr_code/qr_code_scan/cubit/qr_code_scan_cubit.dart b/lib/dashboard/qr_code/qr_code_scan/cubit/qr_code_scan_cubit.dart index eda837212..54eb2e125 100644 --- a/lib/dashboard/qr_code/qr_code_scan/cubit/qr_code_scan_cubit.dart +++ b/lib/dashboard/qr_code/qr_code_scan/cubit/qr_code_scan_cubit.dart @@ -292,18 +292,30 @@ class QRCodeScanCubit extends Cubit { response['presentation_definition_uri']; final queryJson = {}; - if (redirectUri != null) { + if (clientId != null) { + queryJson['client_id'] = clientId; + } + + /// if redirectUri is not provided and client_id is url then + /// redirectUri = client_id + if (redirectUri == null) { + if (clientId == null) throw Exception(); + final isUrl = isURL(clientId.toString()); + if (isUrl) { + queryJson['redirect_uri'] = clientId; + } else { + throw Exception(); + } + } else { queryJson['redirect_uri'] = redirectUri; } + if (nonce != null) { queryJson['nonce'] = nonce; } if (stateValue != null) { queryJson['state'] = stateValue; } - if (clientId != null) { - queryJson['client_id'] = clientId; - } if (responseType != null) { queryJson['response_type'] = responseType; } @@ -804,48 +816,65 @@ class QRCodeScanCubit extends Cubit { /// verify jwt Future verifyJWTBeforeLaunchingOIDC4VCANDSIOPV2Flow() async { - final String? requestUri = state.uri?.queryParameters['request_uri']; - final String? request = state.uri?.queryParameters['request']; + final isSecurityLow = profileCubit.state.model.isSecurityLow; - if (requestUri != null) { - encodedData = await fetchRequestUriPayload(url: requestUri); + if (isSecurityLow) { + emit(state.acceptHost()); } else { - encodedData = request; - } + final String? requestUri = state.uri?.queryParameters['request_uri']; + final String? request = state.uri?.queryParameters['request']; - final Map payload = - decodePayload(jwtDecode: jwtDecode, token: encodedData as String); + if (requestUri != null) { + encodedData = await fetchRequestUriPayload(url: requestUri); + } else { + encodedData = request; + } - final Map header = - decodeHeader(jwtDecode: jwtDecode, token: encodedData as String); + final Map payload = + decodePayload(jwtDecode: jwtDecode, token: encodedData as String); - final String issuerDid = jsonEncode(payload['client_id']); - final String issuerKid = jsonEncode(header['kid']); + final Map header = + decodeHeader(jwtDecode: jwtDecode, token: encodedData as String); - //check Signature - try { - final VerificationType isVerified = await verifyEncodedData( - issuerDid, - issuerKid, - encodedData.toString(), - ); + final String issuerDid = jsonEncode(payload['client_id']); + final String issuerKid = jsonEncode(header['kid']); - if (isVerified == VerificationType.verified) { - emit(state.acceptHost()); - } else { - emit(state.acceptHost(isRequestVerified: false)); + //check Signature + try { + final VerificationType isVerified = await verifyEncodedData( + issuerDid, + issuerKid, + encodedData.toString(), + ); + + if (isVerified == VerificationType.verified) { + emit(state.acceptHost()); + } else { + emitError( + ResponseMessage( + ResponseString.RESPONSE_STRING_theRequestIsRejected, + ), + ); + } + } catch (e) { + emitError( + ResponseMessage( + ResponseString.RESPONSE_STRING_theRequestIsRejected, + ), + ); } - } catch (e) { - emit(state.acceptHost()); } } /// complete SIOPV2 Flow Future completeSiopV2Flow() async { try { - final redirectUri = state.uri?.queryParameters['redirect_uri'] ?? ''; + final clientId = state.uri!.queryParameters['client_id'] ?? ''; + final String? redirectUri = getRedirectUri(state.uri!); + + if (redirectUri == null) throw Exception(); + final nonce = state.uri?.queryParameters['nonce'] ?? ''; - final clientId = state.uri?.queryParameters['client_id'] ?? ''; final stateValue = state.uri?.queryParameters['state']; final keys = []; @@ -986,17 +1015,43 @@ class QRCodeScanCubit extends Cubit { oidc4vc.resetNonceAndAccessTokenAndAuthorizationDetails(); goBack(); } catch (e) { - if (e is MessageHandler) { - emit(state.copyWith(message: StateMessage.error(messageHandler: e))); - } else { - emit( - state.copyWith( - message: StateMessage.error( - messageHandler: ResponseMessage( + if (e is DioException) { + final error = NetworkException.getDioException(error: e); + + if (error.message == NetworkError.NETWORK_ERROR_BAD_REQUEST) { + final data = error.data; + + if (data != null && + data is Map && + data.containsKey('error') && + data['error'] == 'invalid_grant') { + emitError( + ResponseMessage( + ResponseString.RESPONSE_STRING_userPinIsIncorrect, + ), + ); + } else { + emitError( + ResponseMessage( ResponseString .RESPONSE_STRING_SOMETHING_WENT_WRONG_TRY_AGAIN_LATER, ), + ); + } + } else { + emitError( + ResponseMessage( + ResponseString + .RESPONSE_STRING_SOMETHING_WENT_WRONG_TRY_AGAIN_LATER, ), + ); + } + } else if (e is MessageHandler) { + emit(state.copyWith(message: StateMessage.error(messageHandler: e))); + } else { + emitError( + ResponseMessage( + ResponseString.RESPONSE_STRING_SOMETHING_WENT_WRONG_TRY_AGAIN_LATER, ), ); } diff --git a/lib/dashboard/qr_code/qr_code_scan/cubit/qr_code_scan_state.dart b/lib/dashboard/qr_code/qr_code_scan/cubit/qr_code_scan_state.dart index 3d25ae4cb..1308501d2 100644 --- a/lib/dashboard/qr_code/qr_code_scan/cubit/qr_code_scan_state.dart +++ b/lib/dashboard/qr_code/qr_code_scan/cubit/qr_code_scan_state.dart @@ -7,7 +7,6 @@ class QRCodeScanState extends Equatable { this.uri, this.route, this.isScan = false, - this.isRequestVerified = true, this.message, }); @@ -19,7 +18,7 @@ class QRCodeScanState extends Equatable { @JsonKey(includeFromJson: false, includeToJson: false) final Route? route; final bool isScan; - final bool isRequestVerified; + final StateMessage? message; Map toJson() => _$QRCodeScanStateToJson(this); @@ -29,16 +28,14 @@ class QRCodeScanState extends Equatable { status: QrScanStatus.loading, isScan: isScan ?? this.isScan, uri: uri, - isRequestVerified: isRequestVerified, ); } - QRCodeScanState acceptHost({bool isRequestVerified = true}) { + QRCodeScanState acceptHost() { return QRCodeScanState( status: QrScanStatus.acceptHost, isScan: isScan, uri: uri, - isRequestVerified: isRequestVerified, ); } @@ -48,7 +45,6 @@ class QRCodeScanState extends Equatable { message: message, isScan: isScan, uri: uri, - isRequestVerified: isRequestVerified, ); } @@ -65,11 +61,9 @@ class QRCodeScanState extends Equatable { isScan: isScan ?? this.isScan, uri: uri ?? this.uri, route: route, // route should be cleared when one route is done - isRequestVerified: isRequestVerified, ); } @override - List get props => - [status, uri, route, isScan, message, isRequestVerified]; + List get props => [status, uri, route, isScan, message]; } diff --git a/lib/dashboard/qr_code/qr_code_scan/qr_code_scan.dart b/lib/dashboard/qr_code/qr_code_scan/qr_code_scan.dart index c13aee289..21882df77 100644 --- a/lib/dashboard/qr_code/qr_code_scan/qr_code_scan.dart +++ b/lib/dashboard/qr_code/qr_code_scan/qr_code_scan.dart @@ -1,3 +1,4 @@ export 'cubit/qr_code_scan_cubit.dart'; +export 'view/qr_camera_view.dart'; export 'view/qr_code_scan_page.dart'; export 'view/qr_scanner_page.dart'; diff --git a/lib/dashboard/qr_code/qr_code_scan/view/qr_camera_view.dart b/lib/dashboard/qr_code/qr_code_scan/view/qr_camera_view.dart new file mode 100644 index 000000000..53c5ba0bd --- /dev/null +++ b/lib/dashboard/qr_code/qr_code_scan/view/qr_camera_view.dart @@ -0,0 +1,263 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:altme/app/app.dart'; +import 'package:altme/theme/theme.dart'; +import 'package:camera/camera.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:google_mlkit_commons/google_mlkit_commons.dart'; + +class QrCameraView extends StatefulWidget { + const QrCameraView({ + super.key, + required this.title, + required this.onImage, + this.onCameraFeedReady, + this.onDetectorViewModeChanged, + this.onCameraLensDirectionChanged, + this.initialCameraLensDirection = CameraLensDirection.back, + }); + + final String title; + final Function(InputImage inputImage) onImage; + final VoidCallback? onCameraFeedReady; + final VoidCallback? onDetectorViewModeChanged; + final Function(CameraLensDirection direction)? onCameraLensDirectionChanged; + final CameraLensDirection initialCameraLensDirection; + + @override + State createState() => _QrCameraViewState(); +} + +class _QrCameraViewState extends State { + static List _cameras = []; + CameraController? _controller; + int _cameraIndex = -1; + double _currentZoomLevel = 1.0; + double _minAvailableZoom = 1.0; + double _maxAvailableZoom = 1.0; + double _minAvailableExposureOffset = 0.0; + double _maxAvailableExposureOffset = 0.0; + double _currentExposureOffset = 0.0; + bool _changingCameraLens = false; + + @override + void initState() { + super.initState(); + _initialize(); + } + + void _initialize() async { + if (_cameras.isEmpty) { + _cameras = await availableCameras(); + } + for (var i = 0; i < _cameras.length; i++) { + if (_cameras[i].lensDirection == widget.initialCameraLensDirection) { + _cameraIndex = i; + break; + } + } + if (_cameraIndex != -1) { + unawaited(_startLiveFeed()); + } + } + + @override + void dispose() { + _stopLiveFeed(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + if (_cameras.isEmpty) return Container(); + if (_controller == null) return Container(); + if (_controller?.value.isInitialized == false) return Container(); + return Scaffold( + backgroundColor: Theme.of(context).colorScheme.background, + appBar: AppBar( + title: Text( + widget.title, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.appBar, + ), + leading: const BackLeadingButton(), + backgroundColor: Theme.of(context).colorScheme.background, + ), + body: ColoredBox( + color: Theme.of(context).colorScheme.background, + child: Center( + child: _changingCameraLens + ? Container() + : CameraPreview( + _controller!, + child: CustomPaint( + painter: BlurPainter(), + child: Container(), + ), + ), + ), + ), + ); + } + + Future _startLiveFeed() async { + final camera = _cameras[_cameraIndex]; + _controller = CameraController( + camera, + // Set to ResolutionPreset.high. Do NOT set it to ResolutionPreset.max because for some phones does NOT work. + ResolutionPreset.high, + enableAudio: false, + imageFormatGroup: Platform.isAndroid + ? ImageFormatGroup.nv21 + : ImageFormatGroup.bgra8888, + ); + await _controller?.initialize().then((_) { + if (!mounted) { + return; + } + _controller?.getMinZoomLevel().then((value) { + _currentZoomLevel = value; + _minAvailableZoom = value; + }); + _controller?.getMaxZoomLevel().then((value) { + _maxAvailableZoom = value; + }); + _currentExposureOffset = 0.0; + _controller?.getMinExposureOffset().then((value) { + _minAvailableExposureOffset = value; + }); + _controller?.getMaxExposureOffset().then((value) { + _maxAvailableExposureOffset = value; + }); + _controller?.startImageStream(_processCameraImage).then((value) { + if (widget.onCameraFeedReady != null) { + widget.onCameraFeedReady!(); + } + if (widget.onCameraLensDirectionChanged != null) { + widget.onCameraLensDirectionChanged!(camera.lensDirection); + } + }); + setState(() {}); + }); + } + + Future _stopLiveFeed() async { + await _controller?.stopImageStream(); + await _controller?.dispose(); + _controller = null; + } + + void _processCameraImage(CameraImage image) { + final inputImage = _inputImageFromCameraImage(image); + if (inputImage == null) return; + widget.onImage(inputImage); + } + + final _orientations = { + DeviceOrientation.portraitUp: 0, + DeviceOrientation.landscapeLeft: 90, + DeviceOrientation.portraitDown: 180, + DeviceOrientation.landscapeRight: 270, + }; + + InputImage? _inputImageFromCameraImage(CameraImage image) { + if (_controller == null) return null; + + // get image rotation + // it is used in android to convert the InputImage from Dart to Java: https://github.com/flutter-ml/google_ml_kit_flutter/blob/master/packages/google_mlkit_commons/android/src/main/java/com/google_mlkit_commons/InputImageConverter.java + // `rotation` is not used in iOS to convert the InputImage from Dart to Obj-C: https://github.com/flutter-ml/google_ml_kit_flutter/blob/master/packages/google_mlkit_commons/ios/Classes/MLKVisionImage%2BFlutterPlugin.m + // in both platforms `rotation` and `camera.lensDirection` can be used to compensate `x` and `y` coordinates on a canvas: https://github.com/flutter-ml/google_ml_kit_flutter/blob/master/packages/example/lib/vision_detector_views/painters/coordinates_translator.dart + final camera = _cameras[_cameraIndex]; + final sensorOrientation = camera.sensorOrientation; + // print( + // 'lensDirection: ${camera.lensDirection}, sensorOrientation: $sensorOrientation, ${_controller?.value.deviceOrientation} ${_controller?.value.lockedCaptureOrientation} ${_controller?.value.isCaptureOrientationLocked}'); + InputImageRotation? rotation; + if (Platform.isIOS) { + rotation = InputImageRotationValue.fromRawValue(sensorOrientation); + } else if (Platform.isAndroid) { + var rotationCompensation = + _orientations[_controller!.value.deviceOrientation]; + if (rotationCompensation == null) return null; + if (camera.lensDirection == CameraLensDirection.front) { + // front-facing + rotationCompensation = (sensorOrientation + rotationCompensation) % 360; + } else { + // back-facing + rotationCompensation = + (sensorOrientation - rotationCompensation + 360) % 360; + } + rotation = InputImageRotationValue.fromRawValue(rotationCompensation); + // print('rotationCompensation: $rotationCompensation'); + } + if (rotation == null) return null; + // print('final rotation: $rotation'); + + // get image format + final format = InputImageFormatValue.fromRawValue(image.format.raw as int); + // validate format depending on platform + // only supported formats: + // * nv21 for Android + // * bgra8888 for iOS + if (format == null || + (Platform.isAndroid && format != InputImageFormat.nv21) || + (Platform.isIOS && format != InputImageFormat.bgra8888)) return null; + + // since format is constraint to nv21 or bgra8888, both only have one plane + if (image.planes.length != 1) return null; + final plane = image.planes.first; + + // compose InputImage using bytes + return InputImage.fromBytes( + bytes: plane.bytes, + metadata: InputImageMetadata( + size: Size(image.width.toDouble(), image.height.toDouble()), + rotation: rotation, // used only in Android + format: format, // used only in iOS + bytesPerRow: plane.bytesPerRow, // used only in iOS + ), + ); + } +} + +class BlurPainter extends CustomPainter { + @override + void paint(Canvas canvas, Size size) { + // Create a Rect to represent the entire canvas + final Rect rect = Offset.zero & size; + + // Create a Paint object for the blur effect + final Paint blurPaint = Paint(); + blurPaint.color = Colors.black.withOpacity(0.5); + + // Calculate the size of the middle square + final double squareSize = size.width * 0.8; + final double squareOffsetX = (size.width - squareSize) / 2; + final double squareOffsetY = (size.height - squareSize) / 2; + final Rect middleSquare = Rect.fromLTWH( + squareOffsetX, + squareOffsetY, + squareSize, + squareSize, + ); + + // Create a Path object for the middle square + final Path squarePath = Path()..addRect(middleSquare); + + // Create a Path object for the entire canvas + final Path canvasPath = Path()..addRect(rect); + + // Subtract the middle square path from the canvas path + final Path blurredPath = + Path.combine(PathOperation.difference, canvasPath, squarePath); + + // Draw the blurred path on the canvas + canvas.drawPath(blurredPath, blurPaint); + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) { + return false; + } +} diff --git a/lib/dashboard/qr_code/qr_code_scan/view/qr_code_scan_page.dart b/lib/dashboard/qr_code/qr_code_scan/view/qr_code_scan_page.dart index 27f0ad512..da56cebb3 100644 --- a/lib/dashboard/qr_code/qr_code_scan/view/qr_code_scan_page.dart +++ b/lib/dashboard/qr_code/qr_code_scan/view/qr_code_scan_page.dart @@ -3,9 +3,10 @@ import 'package:altme/dashboard/dashboard.dart'; import 'package:altme/l10n/l10n.dart'; import 'package:altme/route/route_name.dart'; import 'package:altme/scan/scan.dart'; +import 'package:camera/camera.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:mobile_scanner/mobile_scanner.dart'; +import 'package:google_mlkit_barcode_scanning/google_mlkit_barcode_scanning.dart'; class QrCodeScanPage extends StatefulWidget { const QrCodeScanPage({super.key}); @@ -20,20 +21,18 @@ class QrCodeScanPage extends StatefulWidget { } class _QrCodeScanPageState extends State { - final qrKey = GlobalKey(debugLabel: 'QR'); - - MobileScannerController scannerController = MobileScannerController( - formats: [BarcodeFormat.qrCode], - ); - - bool isScanned = false; + final BarcodeScanner _barcodeScannerController = + BarcodeScanner(formats: [BarcodeFormat.qrCode]); @override void dispose() { + _barcodeScannerController.close(); super.dispose(); - scannerController.dispose(); } + bool isScanned = false; + var _cameraLensDirection = CameraLensDirection.back; + @override Widget build(BuildContext context) { final l10n = context.l10n; @@ -68,71 +67,30 @@ class _QrCodeScanPageState extends State { }, ), ], - child: BasePage( - padding: EdgeInsets.zero, + child: QrCameraView( title: l10n.scanTitle, - titleAlignment: Alignment.topCenter, - scrollView: false, - extendBelow: true, - titleLeading: const BackLeadingButton(), - titleTrailing: IconButton( - color: Colors.white, - icon: ValueListenableBuilder( - valueListenable: scannerController.torchState, - builder: (context, state, child) { - switch (state) { - case TorchState.off: - return const Icon(Icons.flash_off, color: Colors.grey); - case TorchState.on: - return const Icon(Icons.flash_on, color: Colors.yellow); - } - }, - ), - iconSize: 32, - onPressed: () => scannerController.toggleTorch(), - ), - body: SafeArea( - child: Center( - child: Container( - margin: const EdgeInsets.only(bottom: Sizes.appBarHeight), - child: ClipRRect( - borderRadius: BorderRadius.circular(15), - child: SizedBox.square( - dimension: MediaQuery.of(context).size.shortestSide * 0.8, - child: MobileScanner( - key: qrKey, - controller: scannerController, - onDetect: (capture) { - final List qrcodes = capture.barcodes; - final Barcode qrcode = qrcodes[0]; - for (final barcode in qrcodes) { - debugPrint('Barcode found! ${barcode.rawValue}'); - } - if (qrcode.rawValue == null) { - context.read().emitError( - ResponseMessage( - ResponseString - .RESPONSE_STRING_SOMETHING_WENT_WRONG_TRY_AGAIN_LATER, // ignore: lines_longer_than_80_chars - ), - ); - } else { - if (!isScanned) { - isScanned = true; - final String code = qrcode.rawValue!; - context - .read() - .process(scannedResponse: code); - scannerController.stop(); - } - } - }, - ), - ), - ), - ), - ), - ), + onImage: (InputImage inputImage) async { + if (!isScanned) { + final barcodes = + await _barcodeScannerController.processImage(inputImage); + if (barcodes.isEmpty) { + return; + } + + if (isScanned) return; + isScanned = true; + + await context + .read() + .process(scannedResponse: barcodes.first.rawValue); + } + }, + initialCameraLensDirection: _cameraLensDirection, + onCameraLensDirectionChanged: (value) => _cameraLensDirection = value, ), ); } } + + +//qr is scanning twice diff --git a/lib/dashboard/qr_code/qr_code_scan/view/qr_scanner_page.dart b/lib/dashboard/qr_code/qr_code_scan/view/qr_scanner_page.dart index e301b4602..3989c4f79 100644 --- a/lib/dashboard/qr_code/qr_code_scan/view/qr_scanner_page.dart +++ b/lib/dashboard/qr_code/qr_code_scan/view/qr_scanner_page.dart @@ -1,7 +1,9 @@ import 'package:altme/app/app.dart'; +import 'package:altme/dashboard/dashboard.dart'; import 'package:altme/l10n/l10n.dart'; +import 'package:camera/camera.dart'; import 'package:flutter/material.dart'; -import 'package:mobile_scanner/mobile_scanner.dart'; +import 'package:google_mlkit_barcode_scanning/google_mlkit_barcode_scanning.dart'; class QrScannerPage extends StatefulWidget { const QrScannerPage({super.key}); @@ -16,72 +18,40 @@ class QrScannerPage extends StatefulWidget { } class _QrScannerPageState extends State { - final qrKey = GlobalKey(debugLabel: 'TokenQR'); - - MobileScannerController scannerController = MobileScannerController( - formats: [BarcodeFormat.qrCode], - ); + final BarcodeScanner _barcodeScannerController = + BarcodeScanner(formats: [BarcodeFormat.qrCode]); @override void dispose() { + _barcodeScannerController.close(); super.dispose(); - scannerController.dispose(); } + bool isScanned = false; + var _cameraLensDirection = CameraLensDirection.back; + @override Widget build(BuildContext context) { final l10n = context.l10n; - return BasePage( - padding: EdgeInsets.zero, + return QrCameraView( title: l10n.scanTitle, - scrollView: false, - extendBelow: true, - titleLeading: const BackLeadingButton(), - titleTrailing: IconButton( - color: Colors.white, - icon: ValueListenableBuilder( - valueListenable: scannerController.torchState, - builder: (context, state, child) { - switch (state) { - case TorchState.off: - return const Icon(Icons.flash_off, color: Colors.grey); - case TorchState.on: - return const Icon(Icons.flash_on, color: Colors.yellow); - } - }, - ), - iconSize: 32, - onPressed: () => scannerController.toggleTorch(), - ), - body: SafeArea( - child: Center( - child: Container( - margin: const EdgeInsets.only(bottom: Sizes.appBarHeight), - child: ClipRRect( - borderRadius: BorderRadius.circular(15), - child: SizedBox.square( - dimension: MediaQuery.of(context).size.shortestSide * 0.8, - child: MobileScanner( - key: qrKey, - fit: BoxFit.cover, - controller: scannerController, - onDetect: (capture) { - final List qrcodes = capture.barcodes; - final Barcode qrcode = qrcodes[0]; - if (qrcode.rawValue == null) { - Navigator.of(context).pop(); - } else { - final String code = qrcode.rawValue!; - scannerController.stop(); - Navigator.of(context).pop(code); - } - }, - ), - ), - ), - ), - ), - ), + onImage: (InputImage inputImage) async { + if (!isScanned) { + final barcodes = + await _barcodeScannerController.processImage(inputImage); + if (barcodes.isEmpty) { + return; + } + + if (isScanned) return; + isScanned = true; + + await _barcodeScannerController.close(); + Navigator.of(context).pop(barcodes.first.rawValue); + } + }, + initialCameraLensDirection: _cameraLensDirection, + onCameraLensDirectionChanged: (value) => _cameraLensDirection = value, ); } } diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 4a1fa6047..cb55eec6a 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -826,8 +826,7 @@ "welDone": "Well done!", "mnemonicsVerifiedMessage": "Your revovery phrase is saved correctly.", "chatWith": "Chat with", - "sendAnEmail": "Send an email", - "service_not_registered_message": "This service is not registered.", + "sendAnEmail": "Send an email", "bloometaPassHowToGetIt": "You need to present an Email proof and an Over 18 proof. If you don’t have those credentials already, claim them directly in Altme “Discover” section.", "bloometaPassExpirationDate": "This card will remain active and reusable for 1 YEAR.", "bloometaPassWhyGetThisCard": "Be among the few to get access to limited edition mints, gaming highlights and future airdrops. Claim your card today and enter the Bloometaverse !", @@ -945,5 +944,9 @@ } }, "theCredentialIsNotReady": "The credential is not ready.", - "theCredentialIsNoMoreReady": "The ceredential is no more available." + "theCredentialIsNoMoreReady": "The ceredential is no more available.", + "lowSecurity": "Low Security", + "highSecurity": "High Security", + "theRequestIsRejected": "The request is rejected.", + "userPinIsIncorrect": "User pin is incorrect" } \ No newline at end of file diff --git a/lib/l10n/untranslated.json b/lib/l10n/untranslated.json index 95f2b290c..193e9373d 100644 --- a/lib/l10n/untranslated.json +++ b/lib/l10n/untranslated.json @@ -763,7 +763,6 @@ "mnemonicsVerifiedMessage", "chatWith", "sendAnEmail", - "service_not_registered_message", "bloometaPassHowToGetIt", "bloometaPassExpirationDate", "bloometaPassWhyGetThisCard", @@ -851,7 +850,11 @@ "userPINCodeForAuthentication", "youcanSelectOnlyXCredential", "theCredentialIsNotReady", - "theCredentialIsNoMoreReady" + "theCredentialIsNoMoreReady", + "lowSecurity", + "highSecurity", + "theRequestIsRejected", + "userPinIsIncorrect" ], "es": [ @@ -1618,7 +1621,6 @@ "mnemonicsVerifiedMessage", "chatWith", "sendAnEmail", - "service_not_registered_message", "bloometaPassHowToGetIt", "bloometaPassExpirationDate", "bloometaPassWhyGetThisCard", @@ -1706,7 +1708,11 @@ "userPINCodeForAuthentication", "youcanSelectOnlyXCredential", "theCredentialIsNotReady", - "theCredentialIsNoMoreReady" + "theCredentialIsNoMoreReady", + "lowSecurity", + "highSecurity", + "theRequestIsRejected", + "userPinIsIncorrect" ], "fr": [ @@ -1776,7 +1782,6 @@ "mnemonicsVerifiedMessage", "chatWith", "sendAnEmail", - "service_not_registered_message", "bloometaPassHowToGetIt", "bloometaPassExpirationDate", "bloometaPassWhyGetThisCard", @@ -1864,7 +1869,11 @@ "userPINCodeForAuthentication", "youcanSelectOnlyXCredential", "theCredentialIsNotReady", - "theCredentialIsNoMoreReady" + "theCredentialIsNoMoreReady", + "lowSecurity", + "highSecurity", + "theRequestIsRejected", + "userPinIsIncorrect" ], "it": [ @@ -2631,7 +2640,6 @@ "mnemonicsVerifiedMessage", "chatWith", "sendAnEmail", - "service_not_registered_message", "bloometaPassHowToGetIt", "bloometaPassExpirationDate", "bloometaPassWhyGetThisCard", @@ -2719,6 +2727,10 @@ "userPINCodeForAuthentication", "youcanSelectOnlyXCredential", "theCredentialIsNotReady", - "theCredentialIsNoMoreReady" + "theCredentialIsNoMoreReady", + "lowSecurity", + "highSecurity", + "theRequestIsRejected", + "userPinIsIncorrect" ] } diff --git a/lib/scan/cubit/scan_cubit.dart b/lib/scan/cubit/scan_cubit.dart index 1fa5e68b3..88a548901 100644 --- a/lib/scan/cubit/scan_cubit.dart +++ b/lib/scan/cubit/scan_cubit.dart @@ -535,8 +535,12 @@ class ScanCubit extends Cubit { getLogger('ScanCubit - presentCredentialToOIDC4VPAndSiopV2Request'); final nonce = uri.queryParameters['nonce'] ?? ''; - final redirectUri = uri.queryParameters['redirect_uri'] ?? ''; final clientId = uri.queryParameters['client_id'] ?? ''; + + final String? redirectUri = getRedirectUri(uri); + + if (redirectUri == null) throw Exception(); + final stateValue = uri.queryParameters['state']; final credentialList = @@ -610,7 +614,8 @@ class ScanCubit extends Cubit { await Future.delayed(const Duration(milliseconds: 500)); try { - final redirectUri = uri.queryParameters['redirect_uri'] ?? ''; + final String? redirectUri = getRedirectUri(uri); + if (redirectUri == null) throw Exception(); final String vpToken = await createVpToken( credentialsToBePresented: credentialsToBePresented!, @@ -706,7 +711,8 @@ class ScanCubit extends Cubit { await Future.delayed(const Duration(milliseconds: 500)); try { - final redirectUri = uri.queryParameters['redirect_uri'] ?? ''; + final String? redirectUri = getRedirectUri(uri); + if (redirectUri == null) throw Exception(); final String idToken = await createIdToken( credentialsToBePresented: credentialsToBePresented!, diff --git a/lib/splash/bloclisteners/blocklisteners.dart b/lib/splash/bloclisteners/blocklisteners.dart index 062bb9616..06c292a6b 100644 --- a/lib/splash/bloclisteners/blocklisteners.dart +++ b/lib/splash/bloclisteners/blocklisteners.dart @@ -252,11 +252,7 @@ final qrCodeBlocListener = BlocListener( } if (showPrompt) { - String title = l10n.scanPromptHost; - if (!state.isRequestVerified) { - title = '${l10n.service_not_registered_message} ' - '${l10n.scanPromptHost}'; - } + final String title = l10n.scanPromptHost; String subtitle = (approvedIssuer.did.isEmpty) ? state.uri!.host diff --git a/lib/splash/view/splash_page.dart b/lib/splash/view/splash_page.dart index 889f8afe3..7e92a07ec 100644 --- a/lib/splash/view/splash_page.dart +++ b/lib/splash/view/splash_page.dart @@ -95,7 +95,8 @@ class _SplashViewState extends State { final url = uri.toString().split('${Parameters.oidc4vcUniversalLink}?uri=')[1]; - final List parts = url.split('?'); + final String formattedUrl = Uri.decodeFull(url); + final List parts = formattedUrl.split('?'); final String modifiedUrl = '${parts[0]}?${parts.sublist(1).join('&')}'; diff --git a/packages/oidc4vc/lib/src/oidc4vc.dart b/packages/oidc4vc/lib/src/oidc4vc.dart index e34ca65c3..3ad72abd8 100644 --- a/packages/oidc4vc/lib/src/oidc4vc.dart +++ b/packages/oidc4vc/lib/src/oidc4vc.dart @@ -709,21 +709,17 @@ class OIDC4VC { String tokenEndPoint, Map tokenData, ) async { - try { - /// getting token - final tokenHeaders = { - 'Content-Type': 'application/x-www-form-urlencoded', - }; + /// getting token + final tokenHeaders = { + 'Content-Type': 'application/x-www-form-urlencoded', + }; - final dynamic tokenResponse = await client.post>( - tokenEndPoint, - options: Options(headers: tokenHeaders), - data: tokenData, - ); - return tokenResponse.data; - } catch (e) { - throw Exception(e); - } + final dynamic tokenResponse = await client.post>( + tokenEndPoint, + options: Options(headers: tokenHeaders), + data: tokenData, + ); + return tokenResponse.data; } Future sendPresentation({ diff --git a/pubspec.lock b/pubspec.lock index c9df1b387..2efe54fb1 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -37,10 +37,10 @@ packages: dependency: transitive description: name: archive - sha256: "0c8368c9b3f0abbc193b9d6133649a614204b528982bebc7026372d61677ce3a" + sha256: e0902a06f0e00414e4e3438a084580161279f137aeb862274710f29ec10cf01e url: "https://pub.dev" source: hosted - version: "3.3.7" + version: "3.3.9" args: dependency: transitive description: @@ -222,10 +222,10 @@ packages: dependency: transitive description: name: build_resolvers - sha256: a7417cc44d9edb3f2c8760000270c99dba8c72ff66d0146772b8326565780745 + sha256: d912852cce27c9e80a93603db721c267716894462e7033165178b91138587972 url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.3.2" build_runner: dependency: "direct dev" description: @@ -468,10 +468,10 @@ packages: dependency: transitive description: name: cryptography - sha256: df156c5109286340817d21fa7b62f9140f17915077127dd70f8bd7a2a0997a35 + sha256: "11d083541666d80bba21190d35ff70b2497c81e064e82d1b8a07d801f7c7e282" url: "https://pub.dev" source: hosted - version: "2.5.0" + version: "2.6.0" csslib: dependency: transitive description: @@ -683,10 +683,10 @@ packages: dependency: transitive description: name: encrypt - sha256: "4fd4e4fdc21b9d7d4141823e1e6515cd94e7b8d84749504c232999fba25d9bbb" + sha256: c1f224c9a65df6f2cd111baaf31edd2f31d3f094abdb22a3b0bbcc64698ef80d url: "https://pub.dev" source: hosted - version: "5.0.1" + version: "5.0.2" enhanced_enum: dependency: transitive description: @@ -1103,10 +1103,10 @@ packages: dependency: transitive description: name: get_it - sha256: "529de303c739fca98cd7ece5fca500d8ff89649f1bb4b4e94fb20954abcd7468" + sha256: f79870884de16d689cf9a7d15eedf31ed61d750e813c538a6efb92660fea83c3 url: "https://pub.dev" source: hosted - version: "7.6.0" + version: "7.6.4" glob: dependency: transitive description: @@ -1123,6 +1123,22 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.5" + google_mlkit_barcode_scanning: + dependency: "direct main" + description: + name: google_mlkit_barcode_scanning + sha256: "99f5e35e104da1da4ee1cfd02e2ecddecb4ec7a2a1c2bbae4117982ce57d1cea" + url: "https://pub.dev" + source: hosted + version: "0.8.0" + google_mlkit_commons: + dependency: transitive + description: + name: google_mlkit_commons + sha256: "42173a8ba89f386486cc5b8249e84da4a4b861daa70498373627d985eb418689" + url: "https://pub.dev" + source: hosted + version: "0.5.0" graphs: dependency: transitive description: @@ -1223,10 +1239,10 @@ packages: dependency: transitive description: name: image_picker_android - sha256: d32a997bcc4ee135aebca8e272b7c517927aa65a74b9c60a81a2764ef1a0462d + sha256: "47da2161c2e9f8f8a9cbbd89d466d174333fbdd769aeed848912e0b16d9cb369" url: "https://pub.dev" source: hosted - version: "0.8.7+5" + version: "0.8.8" image_picker_for_web: dependency: transitive description: @@ -1421,10 +1437,10 @@ packages: dependency: "direct overridden" description: name: logger - sha256: "66cb048220ca51cf9011da69fa581e4ee2bed4be6e82870d9e9baae75739da49" + sha256: ba3bc83117b2b49bdd723c0ea7848e8285a0fbc597ba09203b20d329d020c24a url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.0.2" logging: dependency: transitive description: @@ -1700,18 +1716,18 @@ packages: dependency: "direct main" description: name: permission_handler - sha256: "63e5216aae014a72fe9579ccd027323395ce7a98271d9defa9d57320d001af81" + sha256: bc56bfe9d3f44c3c612d8d393bd9b174eb796d706759f9b495ac254e4294baa5 url: "https://pub.dev" source: hosted - version: "10.4.3" + version: "10.4.5" permission_handler_android: dependency: transitive description: name: permission_handler_android - sha256: d74e77a5ecd38649905db0a7d05ef16bed42ff263b9efb73ed794317c5764ec3 + sha256: "59c6322171c29df93a22d150ad95f3aa19ed86542eaec409ab2691b8f35f9a47" url: "https://pub.dev" source: hosted - version: "10.3.4" + version: "10.3.6" permission_handler_apple: dependency: transitive description: @@ -1724,10 +1740,10 @@ packages: dependency: transitive description: name: permission_handler_platform_interface - sha256: "7c6b1500385dd1d2ca61bb89e2488ca178e274a69144d26bbd65e33eae7c02a9" + sha256: f2343e9fa9c22ae4fd92d4732755bfe452214e7189afcc097380950cf567b4b2 url: "https://pub.dev" source: hosted - version: "3.11.3" + version: "3.11.5" permission_handler_windows: dependency: transitive description: @@ -2361,18 +2377,18 @@ packages: dependency: "direct main" description: name: url_launcher - sha256: "38d8e783681bc342e92dc9799cbe4421d08c4d210a67ee9d61d0f7310491a465" + sha256: "47e208a6711459d813ba18af120d9663c20bdf6985d6ad39fe165d2538378d27" url: "https://pub.dev" source: hosted - version: "6.1.13" + version: "6.1.14" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: ef7e34951ffa963fb7a65928deeb38d40fb3c5975baf93c1d631341ff7f2650a + sha256: b04af59516ab45762b2ca6da40fa830d72d0f6045cd97744450b73493fa76330 url: "https://pub.dev" source: hosted - version: "6.0.39" + version: "6.1.0" url_launcher_ios: dependency: transitive description: @@ -2401,10 +2417,10 @@ packages: dependency: transitive description: name: url_launcher_platform_interface - sha256: "4d0dae953f80dc06fa24c58d0f8381302139c22c2dad301417787ad96f5f73bd" + sha256: "95465b39f83bfe95fcb9d174829d6476216f2d548b79c38ab2506e0458787618" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.1.5" url_launcher_web: dependency: transitive description: @@ -2537,18 +2553,18 @@ packages: dependency: transitive description: name: webkit_inspection_protocol - sha256: "67d3a8b6c79e1987d19d848b0892e582dbb0c66c57cc1fef58a177dd2aa2823d" + sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.2.1" webrtc_interface: dependency: transitive description: name: webrtc_interface - sha256: faec2b578f7cd588766843a8c59d4a0137c44de10b83341ce7bec05e104614d7 + sha256: "2efbd3e4e5ebeb2914253bcc51dafd3053c4b87b43f3076c74835a9deecbae3a" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" webview_flutter: dependency: "direct main" description: @@ -2561,18 +2577,18 @@ packages: dependency: "direct main" description: name: webview_flutter_android - sha256: "8545719bbf06f5c4b850f0d5f86da1fe837b1953c56b9af579a26be73627c98d" + sha256: "9427774649fd3c8b7ff53523051395d13aed2ca355822b822e6493d79f5fc05a" url: "https://pub.dev" source: hosted - version: "3.9.4" + version: "3.10.0" webview_flutter_platform_interface: dependency: transitive description: name: webview_flutter_platform_interface - sha256: "9d32a63a5ee111b37482cb3eac3379b9f0992afd27a52ee30279dbf06f41918b" + sha256: "6d9213c65f1060116757a7c473247c60f3f7f332cac33dc417c9e362a9a13e4f" url: "https://pub.dev" source: hosted - version: "2.5.1" + version: "2.6.0" webview_flutter_wkwebview: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 662de9b86..020e3ddc2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: altme description: AltMe Flutter App -version: 1.20.19+268 +version: 1.20.20+269 publish_to: none environment: @@ -56,13 +56,14 @@ dependencies: flutter_local_notifications: ^14.1.0 flutter_localizations: sdk: flutter - flutter_markdown: ^0.6.9 #lutter_markdown ^0.6.14 requires markdown ^7.0.0 + flutter_markdown: ^0.6.9 #flutter_markdown ^0.6.14 requires markdown ^7.0.0 flutter_native_timezone: ^2.0.0 flutter_olm: ^1.2.0 flutter_openssl_crypto: ^0.1.0 flutter_svg: ^2.0.6 google_fonts: ^4.0.5 #google_mlkit_face_detection: ^0.5.0 + google_mlkit_barcode_scanning: ^0.8.0 image: ^4.0.17 image_picker: ^0.8.7+5 jose: ^0.3.3 diff --git a/script.sh b/script.sh index 526f6a762..7983327a4 100755 --- a/script.sh +++ b/script.sh @@ -7,9 +7,11 @@ function pub { cd "packages/$d" fvm flutter clean fvm flutter pub get + fvm flutter pub upgrade ) done fvm flutter pub get + fvm flutter pub upgrade } function buildRunner {