@@ -7,6 +7,76 @@ import 'package:qonversion_flutter/qonversion_flutter.dart';
77import '../app_state.dart' ;
88import '../theme.dart' ;
99
10+ /// Sample PurchaseDelegate implementation for testing purposes.
11+ ///
12+ /// In a real app, you would implement your own purchase system here
13+ /// (e.g., RevenueCat, custom backend, StoreKit directly, etc.).
14+ ///
15+ /// For this sample, we use Qonversion SDK to perform purchases,
16+ /// just to demonstrate how the delegate works.
17+ class SamplePurchaseDelegate implements NoCodesPurchaseDelegate {
18+ final void Function (String message) onEvent;
19+
20+ SamplePurchaseDelegate ({required this .onEvent});
21+
22+ @override
23+ Future <void > purchase (QProduct product) async {
24+ onEvent ('🛒 [PurchaseDelegate] purchase() called for: ${product .qonversionId }' );
25+ debugPrint ('🛒 [PurchaseDelegate] purchase() called for product: ${product .qonversionId }' );
26+ debugPrint (' Store ID: ${product .storeId }' );
27+ debugPrint (' Type: ${product .type }' );
28+ debugPrint (' Price: ${product .prettyPrice }' );
29+
30+ try {
31+ // For testing purposes, we use Qonversion SDK here.
32+ // In a real app, you would use your own purchase system.
33+ onEvent ('🔄 [PurchaseDelegate] Performing purchase...' );
34+ final result = await Qonversion .getSharedInstance ().purchaseWithResult (product);
35+
36+ if (result.status == QPurchaseResultStatus .success) {
37+ onEvent ('✅ [PurchaseDelegate] Purchase successful!' );
38+ debugPrint ('✅ [PurchaseDelegate] Purchase successful' );
39+ } else if (result.status == QPurchaseResultStatus .userCanceled) {
40+ onEvent ('⚠️ [PurchaseDelegate] Purchase canceled by user' );
41+ debugPrint ('⚠️ [PurchaseDelegate] Purchase canceled' );
42+ throw Exception ('Purchase was canceled by user' );
43+ } else if (result.status == QPurchaseResultStatus .pending) {
44+ onEvent ('⏳ [PurchaseDelegate] Purchase pending' );
45+ debugPrint ('⏳ [PurchaseDelegate] Purchase pending' );
46+ } else {
47+ final errorMessage = result.error? .message ?? 'Unknown error' ;
48+ onEvent ('❌ [PurchaseDelegate] Purchase failed: $errorMessage ' );
49+ debugPrint ('❌ [PurchaseDelegate] Purchase failed: $errorMessage ' );
50+ throw Exception (errorMessage);
51+ }
52+ } catch (e) {
53+ onEvent ('❌ [PurchaseDelegate] Purchase error: $e ' );
54+ debugPrint ('❌ [PurchaseDelegate] Purchase error: $e ' );
55+ rethrow ;
56+ }
57+ }
58+
59+ @override
60+ Future <void > restore () async {
61+ onEvent ('🔄 [PurchaseDelegate] restore() called' );
62+ debugPrint ('🔄 [PurchaseDelegate] restore() called' );
63+
64+ try {
65+ // For testing purposes, we use Qonversion SDK here.
66+ // In a real app, you would use your own restore logic.
67+ onEvent ('🔄 [PurchaseDelegate] Performing restore...' );
68+ final entitlements = await Qonversion .getSharedInstance ().restore ();
69+
70+ onEvent ('✅ [PurchaseDelegate] Restore successful! Entitlements: ${entitlements .length }' );
71+ debugPrint ('✅ [PurchaseDelegate] Restore successful. Entitlements count: ${entitlements .length }' );
72+ } catch (e) {
73+ onEvent ('❌ [PurchaseDelegate] Restore error: $e ' );
74+ debugPrint ('❌ [PurchaseDelegate] Restore error: $e ' );
75+ rethrow ;
76+ }
77+ }
78+ }
79+
1080class NoCodesScreen extends StatefulWidget {
1181 const NoCodesScreen ({super .key});
1282
@@ -20,6 +90,8 @@ class _NoCodesScreenState extends State<NoCodesScreen> {
2090
2191 NoCodesPresentationStyle _presentationStyle = NoCodesPresentationStyle .fullScreen;
2292 bool _animated = true ;
93+ bool _purchaseDelegateEnabled = false ;
94+ SamplePurchaseDelegate ? _purchaseDelegate;
2395
2496 @override
2597 void dispose () {
@@ -53,6 +125,8 @@ class _NoCodesScreenState extends State<NoCodesScreen> {
53125 children: [
54126 _buildShowScreenSection (),
55127 const SizedBox (height: 16 ),
128+ _buildPurchaseDelegateSection (appState),
129+ const SizedBox (height: 16 ),
56130 _buildPresentationConfigSection (),
57131 const SizedBox (height: 16 ),
58132 _buildLocaleSection (),
@@ -94,6 +168,95 @@ class _NoCodesScreenState extends State<NoCodesScreen> {
94168 );
95169 }
96170
171+ Widget _buildPurchaseDelegateSection (AppState appState) {
172+ return SectionCard (
173+ title: 'Purchase Delegate' ,
174+ child: Column (
175+ crossAxisAlignment: CrossAxisAlignment .stretch,
176+ children: [
177+ Container (
178+ padding: const EdgeInsets .all (12 ),
179+ decoration: BoxDecoration (
180+ color: _purchaseDelegateEnabled
181+ ? Colors .green.shade50
182+ : Colors .grey.shade100,
183+ borderRadius: BorderRadius .circular (8 ),
184+ border: Border .all (
185+ color: _purchaseDelegateEnabled
186+ ? Colors .green.shade200
187+ : Colors .grey.shade300,
188+ ),
189+ ),
190+ child: Row (
191+ children: [
192+ Icon (
193+ _purchaseDelegateEnabled
194+ ? Icons .check_circle
195+ : Icons .circle_outlined,
196+ color: _purchaseDelegateEnabled
197+ ? Colors .green
198+ : Colors .grey,
199+ ),
200+ const SizedBox (width: 12 ),
201+ Expanded (
202+ child: Column (
203+ crossAxisAlignment: CrossAxisAlignment .start,
204+ children: [
205+ Text (
206+ _purchaseDelegateEnabled
207+ ? 'Custom Purchase Handling ENABLED'
208+ : 'Custom Purchase Handling DISABLED' ,
209+ style: TextStyle (
210+ fontWeight: FontWeight .w600,
211+ color: _purchaseDelegateEnabled
212+ ? Colors .green.shade700
213+ : Colors .grey.shade700,
214+ ),
215+ ),
216+ const SizedBox (height: 4 ),
217+ Text (
218+ _purchaseDelegateEnabled
219+ ? 'Purchases from No-Code screens will be handled by the custom delegate'
220+ : 'Purchases from No-Code screens will use default SDK flow' ,
221+ style: TextStyle (
222+ fontSize: 12 ,
223+ color: Colors .grey.shade600,
224+ ),
225+ ),
226+ ],
227+ ),
228+ ),
229+ ],
230+ ),
231+ ),
232+ const SizedBox (height: 12 ),
233+ SizedBox (
234+ width: double .infinity,
235+ child: ElevatedButton .icon (
236+ onPressed: _purchaseDelegateEnabled ? null : () => _togglePurchaseDelegate (appState),
237+ icon: const Icon (Icons .play_arrow),
238+ label: const Text ('Enable Custom Purchase Delegate' ),
239+ style: ElevatedButton .styleFrom (
240+ backgroundColor: Colors .green,
241+ foregroundColor: Colors .white,
242+ ),
243+ ),
244+ ),
245+ const SizedBox (height: 8 ),
246+ Text (
247+ 'When enabled, purchase() and restore() calls from No-Code screens '
248+ 'will be logged in the Events section below. Once enabled, delegate stays active.' ,
249+ style: TextStyle (
250+ fontSize: 11 ,
251+ fontStyle: FontStyle .italic,
252+ color: Colors .grey.shade500,
253+ ),
254+ ),
255+ ],
256+ ),
257+ );
258+ }
259+
97260 Widget _buildPresentationConfigSection () {
98261 return SectionCard (
99262 title: 'Presentation Config' ,
@@ -217,14 +380,27 @@ class _NoCodesScreenState extends State<NoCodesScreen> {
217380 itemCount: appState.noCodesEvents.length,
218381 itemBuilder: (context, index) {
219382 final event = appState.noCodesEvents[index];
383+ // Color-code different event types
384+ Color textColor = Colors .grey.shade800;
385+ if (event.contains ('[PurchaseDelegate]' )) {
386+ if (event.contains ('✅' )) {
387+ textColor = Colors .green.shade700;
388+ } else if (event.contains ('❌' )) {
389+ textColor = Colors .red.shade700;
390+ } else if (event.contains ('🛒' ) || event.contains ('🔄' )) {
391+ textColor = Colors .blue.shade700;
392+ } else if (event.contains ('⚠️' )) {
393+ textColor = Colors .orange.shade700;
394+ }
395+ }
220396 return Padding (
221397 padding: const EdgeInsets .symmetric (vertical: 2 ),
222398 child: Text (
223399 event,
224400 style: TextStyle (
225401 fontSize: 12 ,
226402 fontFamily: 'monospace' ,
227- color: Colors .grey.shade800 ,
403+ color: textColor ,
228404 ),
229405 ),
230406 );
@@ -234,6 +410,29 @@ class _NoCodesScreenState extends State<NoCodesScreen> {
234410 );
235411 }
236412
413+ void _togglePurchaseDelegate (AppState appState) async {
414+ // Create and set the purchase delegate
415+ _purchaseDelegate = SamplePurchaseDelegate (
416+ onEvent: (message) {
417+ appState.addNoCodesEvent (message);
418+ },
419+ );
420+
421+ try {
422+ debugPrint ('🔄 [NoCodes] Enabling custom purchase delegate...' );
423+ await NoCodes .getSharedInstance ().setPurchaseDelegate (_purchaseDelegate! );
424+ setState (() {
425+ _purchaseDelegateEnabled = true ;
426+ });
427+ debugPrint ('✅ [NoCodes] Custom purchase delegate enabled' );
428+ appState.addNoCodesEvent ('✅ Custom Purchase Delegate ENABLED' );
429+ _showSuccess ('Custom purchase handling enabled' );
430+ } catch (e) {
431+ debugPrint ('❌ [NoCodes] Failed to set purchase delegate: $e ' );
432+ _showError ('Failed to enable purchase delegate: $e ' );
433+ }
434+ }
435+
237436 void _showScreen () {
238437 final contextKey = _contextKeyController.text.trim ();
239438
0 commit comments