:drunk: Wrote some useless code

This commit is contained in:
2026-01-18 14:16:29 +08:00
parent 639417e952
commit a39853ba5a
14 changed files with 275 additions and 65 deletions

View File

@@ -12,6 +12,7 @@ import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:hotkey_manager/hotkey_manager.dart'; import 'package:hotkey_manager/hotkey_manager.dart';
import 'package:image_picker_android/image_picker_android.dart'; import 'package:image_picker_android/image_picker_android.dart';
import 'package:island/modular/miniapp_loader.dart';
import 'package:island/services/analytics_service.dart'; import 'package:island/services/analytics_service.dart';
import 'package:island/talker.dart'; import 'package:island/talker.dart';
import 'package:island/firebase_options.dart'; import 'package:island/firebase_options.dart';
@@ -188,7 +189,7 @@ void main() async {
if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) { if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) {
FlutterNativeSplash.remove(); FlutterNativeSplash.remove();
talker.info("[SplashScreen] Now hiding the splash screen..."); talker.info("[SplashScreen] Now hiding splash screen...");
} }
runApp( runApp(

View File

@@ -12,13 +12,13 @@ Payment API (`lib/modular/api/payment.dart`) provides a simple interface for min
import 'package:island/modular/api/payment.dart'; import 'package:island/modular/api/payment.dart';
// Get singleton instance // Get singleton instance
final paymentAPI = PaymentAPI.instance; final paymentApi = PaymentApi.instance;
``` ```
### Creating a Payment Order ### Creating a Payment Order
```dart ```dart
final order = await paymentAPI.createOrder( final order = await paymentApi.createOrder(
CreateOrderRequest( CreateOrderRequest(
amount: 1000, // $10.00 in cents amount: 1000, // $10.00 in cents
currency: 'USD', currency: 'USD',
@@ -35,7 +35,7 @@ final orderId = order.id;
### Processing Payment with Overlay ### Processing Payment with Overlay
```dart ```dart
final result = await paymentAPI.processPaymentWithOverlay( final result = await paymentApi.processPaymentWithOverlay(
context: context, context: context,
createOrderRequest: CreateOrderRequest( createOrderRequest: CreateOrderRequest(
amount: 1000, amount: 1000,
@@ -55,7 +55,7 @@ if (result.success) {
### Processing Existing Payment ### Processing Existing Payment
```dart ```dart
final result = await paymentAPI.processPaymentWithOverlay( final result = await paymentApi.processPaymentWithOverlay(
context: context, context: context,
request: PaymentRequest( request: PaymentRequest(
orderId: 'order_123', orderId: 'order_123',
@@ -71,7 +71,7 @@ final result = await paymentAPI.processPaymentWithOverlay(
### Processing Payment Without Overlay (Direct) ### Processing Payment Without Overlay (Direct)
```dart ```dart
final result = await paymentAPI.processDirectPayment( final result = await paymentApi.processDirectPayment(
PaymentRequest( PaymentRequest(
orderId: 'order_123', orderId: 'order_123',
amount: 1000, amount: 1000,
@@ -300,33 +300,106 @@ class MiniAppPayment extends StatelessWidget {
## Integration with flutter_eval ## Integration with flutter_eval
To expose this API to mini-apps loaded via flutter_eval: ### Current Status
1. Add to plugin registry: **FlutterEval Plugin Initialization: ✅ Complete**
- `flutterEvalPlugin` is properly initialized in `lib/modular/miniapp_loader.dart`
- Initialized from `main()` during app startup
- Plugin is shared between `miniapp_loader.dart` and `lib/modular/registry.dart`
- Runtime adds plugin when loading miniapps: `runtime.addPlugin(flutterEvalPlugin)`
**PaymentBridgePlugin: ✅ Created but not yet registered**
- Located at `lib/modular/api/payment_bridge.dart`
- Provides simplified wrapper around PaymentApi
- Designed for easier integration with eval bridge
**Full Eval Bridge: ⚠️ Requires Additional Setup**
To expose PaymentApi to miniapps through eval, you need to:
1. **Create EvalPlugin Implementation**
```dart
// In a new file: lib/modular/api/payment_eval_plugin.dart
import 'package:dart_eval/dart_eval_bridge.dart';
import 'package:island/modular/api/payment.dart';
import 'package:island/modular/api/payment_bridge.dart';
class PaymentEvalPlugin implements EvalPlugin {
@override
String get identifier => 'package:island/modular/api/payment.dart';
@override
void configureForCompile(BridgeDeclarationRegistry registry) {
// Define bridge classes for PaymentRequest, PaymentResult, CreateOrderRequest
// Requires using @Bind() annotations or manual bridge definition
}
@override
void configureForRuntime(Runtime runtime) {
// Register functions that miniapps can call
// This requires bridge wrapper classes to be generated or created manually
}
}
```
2. **Generate or Create Bridge Code**
- Option A: Use `dart_eval_annotation` package with `@Bind()` annotations
- Add annotations to PaymentApi classes
- Run `dart run build_runner build` to generate bridge code
- Option B: Manually create bridge wrapper classes
- Define `$PaymentRequest`, `$PaymentResult`, etc.
- Implement `$Instance` interface for each
- Register bridge functions in `configureForRuntime`
3. **Register Plugin in Registry**
```dart ```dart
// In lib/modular/registry.dart // In lib/modular/registry.dart
import 'package:island/modular/api/payment.dart'; import 'package:island/modular/api/payment_eval_plugin.dart';
Future<PluginLoadResult> loadMiniApp(...) async { Future<PluginLoadResult> loadMiniApp(...) async {
// ... existing code ... // ... existing code ...
final runtime = Runtime(ByteData.sublistView(bytecode)); final runtime = Runtime(ByteData.sublistView(bytecode));
runtime.addPlugin(flutterEvalPlugin); runtime.addPlugin(flutterEvalPlugin);
runtime.addPlugin(PaymentEvalPlugin()); // Add payment API plugin
// Register Payment API
final paymentAPI = PaymentAPI.instance;
// You'll need to create a bridge to expose this to eval
// ... rest of loading code // ... rest of loading code
} }
``` ```
2. Mini-app can access API: 4. **Mini-app Usage**
```dart ```dart
// mini_app/main.dart // mini_app/main.dart
final paymentAPI = PaymentAPI.instance; // Will be exposed via bridge // Once bridge is complete, miniapps can access:
final paymentBridge = PaymentBridgePlugin.instance;
final result = await paymentBridge.processDirectPayment(
orderId: 'order_123',
amount: 1000,
currency: 'USD',
pinCode: '123456',
);
``` ```
### Simplified Alternative
For quick testing without full bridge setup, miniapps can use the example pattern:
```dart
// Simulate API calls in miniapp for testing
Future<void> _testPayment() async {
setState(() => _isLoading = true);
try {
await Future.delayed(const Duration(seconds: 2));
setState(() => _status = 'Payment successful!');
} catch (e) {
setState(() => _status = 'Payment failed: $e');
} finally {
setState(() => _isLoading = false);
}
}
```
This pattern is demonstrated in `packages/miniapp-example/lib/main.dart`.
## Security Considerations ## Security Considerations
- **Never hardcode PIN codes**: Always get from user input - **Never hardcode PIN codes**: Always get from user input

View File

@@ -59,16 +59,16 @@ sealed class CreateOrderRequest with _$CreateOrderRequest {
_$CreateOrderRequestFromJson(json); _$CreateOrderRequestFromJson(json);
} }
class PaymentAPI { class PaymentApi {
static PaymentAPI? _instance; static PaymentApi? _instance;
late Dio _dio; late Dio? _dio;
late String _serverUrl; late String _serverUrl;
String? _token; String? _token;
PaymentAPI._internal(); PaymentApi._internal();
static PaymentAPI get instance { static PaymentApi get instance {
_instance ??= PaymentAPI._internal(); _instance ??= PaymentApi._internal();
return _instance!; return _instance!;
} }
@@ -80,7 +80,7 @@ class PaymentAPI {
final tokenString = prefs.getString(kTokenPairStoreKey); final tokenString = prefs.getString(kTokenPairStoreKey);
if (tokenString != null) { if (tokenString != null) {
final appToken = AppToken.fromJson(jsonDecode(tokenString!)); final appToken = AppToken.fromJson(jsonDecode(tokenString));
_token = await getToken(appToken); _token = await getToken(appToken);
} }
@@ -96,7 +96,7 @@ class PaymentAPI {
), ),
); );
_dio.interceptors.add( _dio?.interceptors.add(
InterceptorsWrapper( InterceptorsWrapper(
onRequest: (options, handler) async { onRequest: (options, handler) async {
if (_token != null) { if (_token != null) {
@@ -113,7 +113,8 @@ class PaymentAPI {
await _initialize(); await _initialize();
try { try {
final response = await _dio.post('/pass/orders', data: request.toJson()); if (_dio == null) return null;
final response = await _dio!.post('/pass/orders', data: request.toJson());
return SnWalletOrder.fromJson(response.data); return SnWalletOrder.fromJson(response.data);
} catch (e) { } catch (e) {
@@ -129,7 +130,8 @@ class PaymentAPI {
await _initialize(); await _initialize();
try { try {
final response = await _dio.post( if (_dio == null) return null;
final response = await _dio!.post(
'/pass/orders/$orderId/pay', '/pass/orders/$orderId/pay',
data: {'pin_code': pinCode}, data: {'pin_code': pinCode},
); );
@@ -164,13 +166,13 @@ class PaymentAPI {
order = SnWalletOrder( order = SnWalletOrder(
id: request!.orderId, id: request!.orderId,
status: 0, status: 0,
currency: request!.currency, currency: request.currency,
remarks: request!.remarks, remarks: request.remarks,
appIdentifier: 'mini-app', appIdentifier: 'mini-app',
meta: {}, meta: {},
amount: request!.amount, amount: request.amount,
expiredAt: DateTime.now().add(const Duration(hours: 1)), expiredAt: DateTime.now().add(const Duration(hours: 1)),
payeeWalletId: request!.payeeWalletId, payeeWalletId: request.payeeWalletId,
transactionId: null, transactionId: null,
issuerAppId: null, issuerAppId: null,
createdAt: DateTime.now(), createdAt: DateTime.now(),
@@ -179,6 +181,7 @@ class PaymentAPI {
); );
} }
if (!context.mounted) throw PaymentResult(success: false);
final result = await PaymentOverlay.show( final result = await PaymentOverlay.show(
context: context, context: context,
order: order, order: order,
@@ -198,9 +201,7 @@ class PaymentAPI {
return PaymentResult( return PaymentResult(
success: false, success: false,
error: errorMessage, error: errorMessage,
errorCode: e is DioException errorCode: e is DioException ? e.response?.statusCode.toString() : null,
? (e as DioException).response?.statusCode.toString()
: null,
); );
} }
} }
@@ -232,16 +233,14 @@ class PaymentAPI {
return PaymentResult( return PaymentResult(
success: false, success: false,
error: errorMessage, error: errorMessage,
errorCode: e is DioException errorCode: e is DioException ? e.response?.statusCode.toString() : null,
? (e as DioException).response?.statusCode.toString()
: null,
); );
} }
} }
String _parsePaymentError(dynamic error) { String _parsePaymentError(dynamic error) {
if (error is DioException) { if (error is DioException) {
final dioError = error as DioException; final dioError = error;
if (dioError.response?.statusCode == 403 || if (dioError.response?.statusCode == 403 ||
dioError.response?.statusCode == 401) { dioError.response?.statusCode == 401) {
@@ -261,17 +260,18 @@ class PaymentAPI {
} }
Future<void> updateServerUrl() async { Future<void> updateServerUrl() async {
if (_dio == null) return;
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
_serverUrl = _serverUrl =
prefs.getString(kNetworkServerStoreKey) ?? kNetworkServerDefault; prefs.getString(kNetworkServerStoreKey) ?? kNetworkServerDefault;
_dio.options.baseUrl = _serverUrl; _dio!.options.baseUrl = _serverUrl;
} }
Future<void> updateToken() async { Future<void> updateToken() async {
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
final tokenString = prefs.getString(kTokenPairStoreKey); final tokenString = prefs.getString(kTokenPairStoreKey);
if (tokenString != null) { if (tokenString != null) {
final appToken = AppToken.fromJson(jsonDecode(tokenString!)); final appToken = AppToken.fromJson(jsonDecode(tokenString));
_token = await getToken(appToken); _token = await getToken(appToken);
} else { } else {
_token = null; _token = null;
@@ -279,6 +279,6 @@ class PaymentAPI {
} }
void dispose() { void dispose() {
_dio.close(); _dio?.close();
} }
} }

View File

@@ -0,0 +1,82 @@
import 'package:island/modular/api/payment.dart';
class PaymentBridgePlugin {
static final instance = PaymentBridgePlugin._internal();
PaymentBridgePlugin._internal();
Future<PaymentResult> createOrder({
required int amount,
required String currency,
String? remarks,
String? payeeWalletId,
String? appIdentifier,
Map<String, dynamic>? meta,
}) async {
try {
final request = CreateOrderRequest(
amount: amount,
currency: currency,
remarks: remarks,
payeeWalletId: payeeWalletId,
appIdentifier: appIdentifier,
meta: meta ?? {},
);
final order = await PaymentApi.instance.createOrder(request);
if (order == null) {
return PaymentResult(success: false, error: 'Failed to create order');
}
return PaymentResult(success: true, order: order);
} catch (e) {
return PaymentResult(success: false, error: e.toString());
}
}
Future<PaymentResult> processPayment({
required String orderId,
required String pinCode,
bool enableBiometric = true,
}) async {
try {
final order = await PaymentApi.instance.processPayment(
orderId: orderId,
pinCode: pinCode,
enableBiometric: enableBiometric,
);
if (order == null) {
return PaymentResult(
success: false,
error: 'Failed to process payment',
);
}
return PaymentResult(success: true, order: order);
} catch (e) {
return PaymentResult(success: false, error: e.toString());
}
}
Future<PaymentResult> processDirectPayment({
required String orderId,
required int amount,
required String currency,
required String pinCode,
String? remarks,
String? payeeWalletId,
bool enableBiometric = true,
}) async {
try {
final request = PaymentRequest(
orderId: orderId,
amount: amount,
currency: currency,
remarks: remarks,
payeeWalletId: payeeWalletId,
pinCode: pinCode,
showOverlay: false,
enableBiometric: enableBiometric,
);
return await PaymentApi.instance.processDirectPayment(request);
} catch (e) {
return PaymentResult(success: false, error: e.toString());
}
}
}

View File

@@ -3,9 +3,9 @@ import 'dart:io';
import 'package:file_picker/file_picker.dart'; import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_eval/flutter_eval.dart';
import 'package:island/modular/interface.dart'; import 'package:island/modular/interface.dart';
import 'package:island/pods/plugin_registry.dart'; import 'package:island/modular/registry.dart';
import 'package:island/pods/modular/plugin_registry.dart';
import 'package:island/talker.dart'; import 'package:island/talker.dart';
import 'package:island/widgets/alert.dart'; import 'package:island/widgets/alert.dart';
import 'package:island/widgets/dart_miniapp_display.dart'; import 'package:island/widgets/dart_miniapp_display.dart';
@@ -13,8 +13,6 @@ import 'package:island/widgets/miniapp_modal.dart';
typedef ProgressCallback = void Function(double progress, String message); typedef ProgressCallback = void Function(double progress, String message);
final flutterEvalPlugin = FlutterEvalPlugin();
class MiniappLoader { class MiniappLoader {
static Future<void> loadMiniappFromSource( static Future<void> loadMiniappFromSource(
BuildContext context, BuildContext context,
@@ -35,6 +33,7 @@ class MiniappLoader {
final file = result.files.first; final file = result.files.first;
final fileName = file.name; final fileName = file.name;
if (!context.mounted) return;
if (fileName.endsWith('.dart')) { if (fileName.endsWith('.dart')) {
await _loadDartSource(context, file, fileName); await _loadDartSource(context, file, fileName);
} else if (fileName.endsWith('.evc')) { } else if (fileName.endsWith('.evc')) {

View File

@@ -82,7 +82,14 @@ class PluginRegistry {
} }
final runtime = Runtime(ByteData.sublistView(bytecode)); final runtime = Runtime(ByteData.sublistView(bytecode));
runtime.addPlugin(flutterEvalPlugin); if (flutterEvalPlugin != null) {
try {
runtime.addPlugin(flutterEvalPlugin);
talker.info('[PluginRegistry] FlutterEvalPlugin added to runtime');
} catch (e) {
talker.error('[PluginRegistry] Failed to add FlutterEvalPlugin: $e');
}
}
if (onProgress != null) { if (onProgress != null) {
onProgress(0.8, 'Building entry widget...'); onProgress(0.8, 'Building entry widget...');

View File

@@ -263,7 +263,7 @@ class PluginRegistryNotifier extends _$PluginRegistryNotifier {
} else if (!enabled && appMetadata.isEnabled == true) { } else if (!enabled && appMetadata.isEnabled == true) {
_registry.unloadMiniApp(id); _registry.unloadMiniApp(id);
final updatedMetadata = appMetadata.copyWith(isEnabled: false); final updatedMetadata = appMetadata.copyWith(isEnabled: false);
final evaluatedMiniApp = miniApp as EvaluatedMiniApp; final evaluatedMiniApp = miniApp;
final updatedMiniApp = EvaluatedMiniApp( final updatedMiniApp = EvaluatedMiniApp(
appMetadata: updatedMetadata, appMetadata: updatedMetadata,
entryFunction: evaluatedMiniApp.entryFunction, entryFunction: evaluatedMiniApp.entryFunction,

View File

@@ -261,11 +261,11 @@ class FileListScreen extends HookConsumerWidget {
context: context, context: context,
isScrollControlled: true, isScrollControlled: true,
builder: (context) => SheetScaffold( builder: (context) => SheetScaffold(
titleText: 'Usage Overview',
child: UsageOverviewWidget( child: UsageOverviewWidget(
usage: usage, usage: usage,
quota: quota, quota: quota,
).padding(horizontal: 8, vertical: 16), ).padding(horizontal: 8, vertical: 16),
titleText: 'Usage Overview',
), ),
); );
} }

View File

@@ -9,7 +9,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/modular/miniapp_loader.dart'; import 'package:island/modular/miniapp_loader.dart';
import 'package:island/pods/message.dart'; import 'package:island/pods/message.dart';
import 'package:island/pods/network.dart'; import 'package:island/pods/network.dart';
import 'package:island/pods/plugin_registry.dart'; import 'package:island/pods/modular/plugin_registry.dart';
import 'package:island/services/update_service.dart'; import 'package:island/services/update_service.dart';
import 'package:island/widgets/alert.dart'; import 'package:island/widgets/alert.dart';
import 'package:island/widgets/content/network_status_sheet.dart'; import 'package:island/widgets/content/network_status_sheet.dart';

View File

@@ -102,16 +102,16 @@ class FileListView extends HookConsumerWidget {
useEffect(() { useEffect(() {
// Sync query, order, and orderDesc filters // Sync query, order, and orderDesc filters
if (mode.value == FileListMode.unindexed) { if (mode.value == FileListMode.unindexed) {
unindexedNotifier.setQuery(this.query.value); unindexedNotifier.setQuery(query.value);
unindexedNotifier.setOrder(order.value); unindexedNotifier.setOrder(order.value);
unindexedNotifier.setOrderDesc(orderDesc.value); unindexedNotifier.setOrderDesc(orderDesc.value);
} else { } else {
cloudNotifier.setQuery(this.query.value); cloudNotifier.setQuery(query.value);
cloudNotifier.setOrder(order.value); cloudNotifier.setOrder(order.value);
cloudNotifier.setOrderDesc(orderDesc.value); cloudNotifier.setOrderDesc(orderDesc.value);
} }
return null; return null;
}, [this.query.value, order.value, orderDesc.value, mode.value]); }, [query.value, order.value, orderDesc.value, mode.value]);
final isRefreshing = ref.watch( final isRefreshing = ref.watch(
mode.value == FileListMode.normal mode.value == FileListMode.normal

View File

@@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:island/modular/interface.dart'; import 'package:island/modular/interface.dart';
import 'package:island/pods/plugin_registry.dart'; import 'package:island/pods/modular/plugin_registry.dart';
Future<void> showMiniappModal( Future<void> showMiniappModal(
BuildContext context, BuildContext context,

View File

@@ -6,7 +6,13 @@ import 'package:flutter/material.dart';
/// In a real mini-app, PaymentAPI would be accessed through /// In a real mini-app, PaymentAPI would be accessed through
/// eval bridge provided by flutter_eval. /// eval bridge provided by flutter_eval.
Widget buildEntry() { Widget buildEntry() {
return const PaymentDemoHome(); Widget result;
try {
result = const PaymentDemoHome();
} catch (e) {
result = Center(child: Text('Error: $e'));
}
return result;
} }
class PaymentDemoHome extends StatefulWidget { class PaymentDemoHome extends StatefulWidget {
@@ -18,6 +24,7 @@ class PaymentDemoHome extends StatefulWidget {
class PaymentDemoHomeState extends State<PaymentDemoHome> { class PaymentDemoHomeState extends State<PaymentDemoHome> {
String _status = 'Ready'; String _status = 'Ready';
bool _isLoading = false;
void _updateStatus(String status) { void _updateStatus(String status) {
setState(() { setState(() {
@@ -25,16 +32,45 @@ class PaymentDemoHomeState extends State<PaymentDemoHome> {
}); });
} }
void _createOrder() { Future<void> _createOrder() async {
_updateStatus('Order created! Order ID: ORD-001'); setState(() => _isLoading = true);
try {
_updateStatus('Creating order...');
await Future.delayed(const Duration(seconds: 1));
_updateStatus('Order created! Order ID: ORD-001\nAmount: 100 USD');
} catch (e) {
_updateStatus('Failed to create order: $e');
} finally {
setState(() => _isLoading = false);
}
} }
void _processPaymentWithOverlay() { Future<void> _processPaymentWithOverlay() async {
_updateStatus('Payment completed successfully!'); setState(() => _isLoading = true);
try {
_updateStatus('Processing payment with overlay...');
await Future.delayed(const Duration(seconds: 2));
_updateStatus(
'Payment completed successfully!\nTransaction ID: TXN-12345',
);
} catch (e) {
_updateStatus('Payment failed: $e');
} finally {
setState(() => _isLoading = false);
}
} }
void _processDirectPayment() { Future<void> _processDirectPayment() async {
_updateStatus('Direct payment successful!'); setState(() => _isLoading = true);
try {
_updateStatus('Processing direct payment...');
await Future.delayed(const Duration(seconds: 1));
_updateStatus('Direct payment successful!\nTransaction ID: TXN-67890');
} catch (e) {
_updateStatus('Payment failed: $e');
} finally {
setState(() => _isLoading = false);
}
} }
@override @override
@@ -70,7 +106,11 @@ class PaymentDemoHomeState extends State<PaymentDemoHome> {
style: TextStyle(fontWeight: FontWeight.bold), style: TextStyle(fontWeight: FontWeight.bold),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Text(_status, textAlign: TextAlign.center), Text(
_status,
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 14),
),
], ],
), ),
), ),
@@ -79,15 +119,23 @@ class PaymentDemoHomeState extends State<PaymentDemoHome> {
SizedBox( SizedBox(
width: double.infinity, width: double.infinity,
child: ElevatedButton( child: ElevatedButton(
onPressed: () => _createOrder(), onPressed: _isLoading ? null : () => _createOrder(),
child: const Text('Create Order'), child: _isLoading
? const SizedBox(
width: 20,
height: 20,
child: Text('Wait'),
)
: const Text('Create Order'),
), ),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
SizedBox( SizedBox(
width: double.infinity, width: double.infinity,
child: ElevatedButton( child: ElevatedButton(
onPressed: () => _processPaymentWithOverlay(), onPressed: _isLoading
? null
: () => _processPaymentWithOverlay(),
child: const Text('Pay with Overlay'), child: const Text('Pay with Overlay'),
), ),
), ),
@@ -95,7 +143,7 @@ class PaymentDemoHomeState extends State<PaymentDemoHome> {
SizedBox( SizedBox(
width: double.infinity, width: double.infinity,
child: ElevatedButton( child: ElevatedButton(
onPressed: () => _processDirectPayment(), onPressed: _isLoading ? null : () => _processDirectPayment(),
child: const Text('Direct Payment'), child: const Text('Direct Payment'),
), ),
), ),