diff --git a/wallet/lib/pages/detailed_asset_view/owner_view_view_model.dart b/wallet/lib/pages/detailed_asset_view/owner_view_view_model.dart index ee03cbcfa2..09068e32fd 100644 --- a/wallet/lib/pages/detailed_asset_view/owner_view_view_model.dart +++ b/wallet/lib/pages/detailed_asset_view/owner_view_view_model.dart @@ -518,11 +518,17 @@ class OwnerViewViewModel extends ChangeNotifier { } } - Future stampTicket({required bool enabled}) async { + Future stampTicket({ + required bool enabled, + required String cookbookId, + required String recipeId, + required String challenge, + }) async { final response = await repository.stampTicket( - cookBookId: CookbookId(events.cookbookID), - recipeId: RecipeId(events.recipeID), + cookBookId: CookbookId(cookbookId), + recipeId: RecipeId(recipeId), creatorAddress: Address(events.ownerAddress), + challenge: challenge, ); if (response.isLeft()) { @@ -533,6 +539,7 @@ class OwnerViewViewModel extends ChangeNotifier { notifyListeners(); } + void logEvent() { repository.logUserJourney(screenName: AnalyticsScreenEvents.ownerView); } diff --git a/wallet/lib/pages/events/event_qr_code_screen.dart b/wallet/lib/pages/events/event_qr_code_screen.dart index 062bd2f615..fdb8c41199 100644 --- a/wallet/lib/pages/events/event_qr_code_screen.dart +++ b/wallet/lib/pages/events/event_qr_code_screen.dart @@ -12,7 +12,6 @@ import 'package:pylons_wallet/model/event.dart'; import 'package:pylons_wallet/pages/detailed_asset_view/widgets/nft_image_asset.dart'; import 'package:pylons_wallet/utils/constants.dart'; import 'package:qr_flutter/qr_flutter.dart'; -import 'package:pylons_wallet/utils/extension.dart'; import '../../providers/account_provider.dart'; @@ -29,7 +28,7 @@ class EventQrCodeScreen extends StatefulWidget { } class _EventQrCodeScreenState extends State { - String link = ""; + String qrdata = ""; GlobalKey renderObjectKey = GlobalKey(); @@ -48,7 +47,10 @@ class _EventQrCodeScreenState extends State { if (wallet == null) { return; } - link = widget.events.recipeID; + qrdata = jsonEncode({ + 'cookbookId': widget.events.cookbookID, + 'recipeId': widget.events.recipeID, + }); } @override @@ -83,7 +85,7 @@ class _EventQrCodeScreenState extends State { key: renderObjectKey, child: QrImageView( padding: EdgeInsets.zero, - data: link, + data: qrdata, size: 200, dataModuleStyle: const QrDataModuleStyle( color: AppColors.kWhite), diff --git a/wallet/lib/pages/events/mobile_scanner.dart b/wallet/lib/pages/events/mobile_scanner.dart index 0333e89047..319517b3b1 100644 --- a/wallet/lib/pages/events/mobile_scanner.dart +++ b/wallet/lib/pages/events/mobile_scanner.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:get_it/get_it.dart'; import 'package:mobile_scanner/mobile_scanner.dart'; +import '../../utils/string_utils.dart'; import '../detailed_asset_view/owner_view_view_model.dart'; class MobileQrScanner extends StatefulWidget { @@ -39,22 +40,39 @@ class _MobileQrScannerState extends State { _barcode = barcodes.barcodes.firstOrNull; if (_barcode != null) { + final qrData = _barcode!.displayValue ?? ''; + final dataParts = qrData.split(','); - final String eventId = _barcode!.displayValue ?? ''; + if (dataParts.length >= 2) { + final cookbookId = dataParts[0]; + final recipeId = dataParts[1]; - // Use the QR code data to stamp the ticket - _stampTicket(eventId); + + final challenge = StringUtils.generateRandomString(16); + + + stampTicket(cookbookId, recipeId, challenge); + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Invalid QR code data.')), + ); + } } }); } } - Future stampTicket(String eventId) async { + Future stampTicket(String cookbookId, String recipeId, String challenge) async { try { + await ownerViewViewModel.stampTicket( + enabled: true, + cookbookId: cookbookId, + recipeId: recipeId, + challenge: challenge, + ); - await ownerViewViewModel.stampTicket(enabled: true); ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Ticket $eventId stamped successfully!')), + SnackBar(content: Text('Ticket with challenge $challenge stamped successfully!')), ); } catch (e) { ScaffoldMessenger.of(context).showSnackBar( @@ -63,6 +81,7 @@ class _MobileQrScannerState extends State { } } + @override Widget build(BuildContext context) { return Scaffold( diff --git a/wallet/lib/services/data_stores/remote_data_store.dart b/wallet/lib/services/data_stores/remote_data_store.dart index a2d610c3a7..7e0a97f259 100644 --- a/wallet/lib/services/data_stores/remote_data_store.dart +++ b/wallet/lib/services/data_stores/remote_data_store.dart @@ -361,6 +361,13 @@ abstract class RemoteDataStore { /// Input : [address] the address & [Event] against which the invite link to be generated /// Output: [String] return the generated dynamic link else will throw error Future createDynamicLinkForRecipeEventShare({required String address, required Events events}); + + Future stampTicket({ + required CookbookId cookBookId, + required RecipeId recipeId, + required Address creatorAddress, + required String challenge, + }); } class RemoteDataStoreImp implements RemoteDataStore { @@ -1305,25 +1312,31 @@ class RemoteDataStoreImp implements RemoteDataStore { return _signAndBroadcast(msgUpdateRecipe); } + @override Future stampTicket({ required CookbookId cookBookId, required RecipeId recipeId, required Address creatorAddress, + required String challenge, }) async { final recipe = await getRecipe(cookBookId: cookBookId, recipeId: recipeId); final recipeProto3Json = recipe.toProto3Json()! as Map; recipeProto3Json.remove(kCreatedAtCamelCase); recipeProto3Json.remove(kUpdatedAtCamelCase); - recipeProto3Json.putIfAbsent("isStamped", true as Function()); + recipeProto3Json.putIfAbsent("isStamped", () => true); - final msgUpdateRecipe = pylons.MsgUpdateRecipe.create()..mergeFromProto3Json(recipeProto3Json); + final msgUpdateRecipe = pylons.MsgUpdateRecipe.create() + ..mergeFromProto3Json(recipeProto3Json); + + msgUpdateRecipe.extraInfo = challenge; msgUpdateRecipe.version = msgUpdateRecipe.version.incrementRecipeVersion(); msgUpdateRecipe.creator = creatorAddress.toString(); return _signAndBroadcast(msgUpdateRecipe); } + @override Future createDynamicLinkForItemNftShare({ required String address, diff --git a/wallet/lib/services/repository/repository.dart b/wallet/lib/services/repository/repository.dart index 6a53e86768..3bda2ea60b 100644 --- a/wallet/lib/services/repository/repository.dart +++ b/wallet/lib/services/repository/repository.dart @@ -581,6 +581,7 @@ abstract class Repository { required CookbookId cookBookId, required RecipeId recipeId, required Address creatorAddress, + required String challenge, }); Future> createTrade({required pylons.MsgCreateTrade msgCreateTrade}); @@ -2428,6 +2429,28 @@ class RepositoryImp implements Repository { } } + @override + Future> stampTicket({ + required CookbookId cookBookId, + required RecipeId recipeId, + required Address creatorAddress, + required String challenge, + }) async { + try { + await remoteDataStore.stampTicket( + cookBookId: cookBookId, + recipeId: recipeId, + creatorAddress: creatorAddress, + challenge: challenge, + ); + return const Right(null); + } on Exception catch (e) { + recordErrorInCrashlytics(e); + return Left(ServerFailure(e.toString())); + } + } + + @override Future> createTrade({required pylons.MsgCreateTrade msgCreateTrade}) async { try { diff --git a/wallet/lib/utils/string_utils.dart b/wallet/lib/utils/string_utils.dart new file mode 100644 index 0000000000..9dfef46148 --- /dev/null +++ b/wallet/lib/utils/string_utils.dart @@ -0,0 +1,15 @@ +import 'dart:math'; + +class StringUtils { + static String generateRandomString(int length) { + const String _chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + Random _rnd = Random(); + + return String.fromCharCodes( + Iterable.generate( + length, + (_) => _chars.codeUnitAt(_rnd.nextInt(_chars.length)), + ), + ); + } +}