: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:hotkey_manager/hotkey_manager.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/talker.dart';
import 'package:island/firebase_options.dart';
@@ -188,7 +189,7 @@ void main() async {
if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) {
FlutterNativeSplash.remove();
talker.info("[SplashScreen] Now hiding the splash screen...");
talker.info("[SplashScreen] Now hiding splash screen...");
}
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';
// Get singleton instance
final paymentAPI = PaymentAPI.instance;
final paymentApi = PaymentApi.instance;
```
### Creating a Payment Order
```dart
final order = await paymentAPI.createOrder(
final order = await paymentApi.createOrder(
CreateOrderRequest(
amount: 1000, // $10.00 in cents
currency: 'USD',
@@ -35,7 +35,7 @@ final orderId = order.id;
### Processing Payment with Overlay
```dart
final result = await paymentAPI.processPaymentWithOverlay(
final result = await paymentApi.processPaymentWithOverlay(
context: context,
createOrderRequest: CreateOrderRequest(
amount: 1000,
@@ -55,7 +55,7 @@ if (result.success) {
### Processing Existing Payment
```dart
final result = await paymentAPI.processPaymentWithOverlay(
final result = await paymentApi.processPaymentWithOverlay(
context: context,
request: PaymentRequest(
orderId: 'order_123',
@@ -71,7 +71,7 @@ final result = await paymentAPI.processPaymentWithOverlay(
### Processing Payment Without Overlay (Direct)
```dart
final result = await paymentAPI.processDirectPayment(
final result = await paymentApi.processDirectPayment(
PaymentRequest(
orderId: 'order_123',
amount: 1000,
@@ -300,33 +300,106 @@ class MiniAppPayment extends StatelessWidget {
## 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
// 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 {
// ... existing code ...
final runtime = Runtime(ByteData.sublistView(bytecode));
runtime.addPlugin(flutterEvalPlugin);
// Register Payment API
final paymentAPI = PaymentAPI.instance;
// You'll need to create a bridge to expose this to eval
runtime.addPlugin(PaymentEvalPlugin()); // Add payment API plugin
// ... rest of loading code
}
```
2. Mini-app can access API:
4. **Mini-app Usage**
```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
- **Never hardcode PIN codes**: Always get from user input

View File

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

View File

@@ -82,7 +82,14 @@ class PluginRegistry {
}
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) {
onProgress(0.8, 'Building entry widget...');

View File

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

View File

@@ -261,11 +261,11 @@ class FileListScreen extends HookConsumerWidget {
context: context,
isScrollControlled: true,
builder: (context) => SheetScaffold(
titleText: 'Usage Overview',
child: UsageOverviewWidget(
usage: usage,
quota: quota,
).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/pods/message.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/widgets/alert.dart';
import 'package:island/widgets/content/network_status_sheet.dart';

View File

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

View File

@@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.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(
BuildContext context,

View File

@@ -6,7 +6,13 @@ import 'package:flutter/material.dart';
/// In a real mini-app, PaymentAPI would be accessed through
/// eval bridge provided by flutter_eval.
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 {
@@ -18,6 +24,7 @@ class PaymentDemoHome extends StatefulWidget {
class PaymentDemoHomeState extends State<PaymentDemoHome> {
String _status = 'Ready';
bool _isLoading = false;
void _updateStatus(String status) {
setState(() {
@@ -25,16 +32,45 @@ class PaymentDemoHomeState extends State<PaymentDemoHome> {
});
}
void _createOrder() {
_updateStatus('Order created! Order ID: ORD-001');
Future<void> _createOrder() async {
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() {
_updateStatus('Payment completed successfully!');
Future<void> _processPaymentWithOverlay() async {
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() {
_updateStatus('Direct payment successful!');
Future<void> _processDirectPayment() async {
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
@@ -70,7 +106,11 @@ class PaymentDemoHomeState extends State<PaymentDemoHome> {
style: TextStyle(fontWeight: FontWeight.bold),
),
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(
width: double.infinity,
child: ElevatedButton(
onPressed: () => _createOrder(),
child: const Text('Create Order'),
onPressed: _isLoading ? null : () => _createOrder(),
child: _isLoading
? const SizedBox(
width: 20,
height: 20,
child: Text('Wait'),
)
: const Text('Create Order'),
),
),
const SizedBox(height: 12),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () => _processPaymentWithOverlay(),
onPressed: _isLoading
? null
: () => _processPaymentWithOverlay(),
child: const Text('Pay with Overlay'),
),
),
@@ -95,7 +143,7 @@ class PaymentDemoHomeState extends State<PaymentDemoHome> {
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () => _processDirectPayment(),
onPressed: _isLoading ? null : () => _processDirectPayment(),
child: const Text('Direct Payment'),
),
),