diff --git a/android/src/main/java/com/finogeeks/mop/platformview/MopWidgetPlatformView.java b/android/src/main/java/com/finogeeks/mop/platformview/MopWidgetPlatformView.java index 2ed0029..5c13fcd 100644 --- a/android/src/main/java/com/finogeeks/mop/platformview/MopWidgetPlatformView.java +++ b/android/src/main/java/com/finogeeks/mop/platformview/MopWidgetPlatformView.java @@ -6,12 +6,12 @@ import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; -import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.FragmentActivity; +import java.util.HashMap; import java.util.Map; import io.flutter.plugin.platform.PlatformView; @@ -20,6 +20,7 @@ import com.finogeeks.lib.applet.sdk.api.request.IFinAppletRequest; import com.finogeeks.lib.applet.sdk.component.ComponentCallback; import com.finogeeks.lib.applet.sdk.component.ComponentHolder; +import com.finogeeks.mop.service.MopPluginService; /** * A simple unified PlatformView which can branch by external viewType. @@ -32,9 +33,6 @@ public class MopWidgetPlatformView implements PlatformView { private final Activity activity; private final int viewId; - private final String externalViewType; - @Nullable - private final Map creationParams; private static final String TAG = "MOP-PlatformView"; private ComponentHolder holder; @@ -46,8 +44,6 @@ public MopWidgetPlatformView(@NonNull Context context, @Nullable Activity activity) { this.viewId = viewId; - this.externalViewType = externalViewType; - this.creationParams = creationParams; this.activity = activity; this.container = new FrameLayout(context); if ( creationParams != null ) { @@ -55,21 +51,29 @@ public MopWidgetPlatformView(@NonNull Context context, if (creationParams.get("appId") != null) { String appId = (String) creationParams.get("appId"); boolean enableMultiComponent = creationParams.get("enableMultiComponent") != null; - buildContent(context, appId,enableMultiComponent); + boolean forceUpdate = creationParams.get("forceUpdate") != null; + buildContent(context, appId,enableMultiComponent, forceUpdate); } else if (creationParams.get("qrCode") != null) { String qrCode = (String) creationParams.get("qrCode"); + boolean forceUpdate = creationParams.get("forceUpdate") != null; boolean enableMultiComponent = creationParams.get("enableMultiComponent") != null; - buildContentWithQR(context, qrCode, enableMultiComponent); + buildContentWithQR(context, qrCode, enableMultiComponent, forceUpdate); } else { - TextView textView = new TextView(context); - textView.setText("需要传入小组件的 appId 或者小组件的二维码"); - container.addView( - textView, - new ViewGroup.LayoutParams( - FrameLayout.LayoutParams.WRAP_CONTENT, - FrameLayout.LayoutParams.WRAP_CONTENT) - ); + try { + Map eventData = new HashMap<>(); + eventData.put("viewId", viewId); + eventData.put("errorMessage", "MopWidgetPlatformView: no appId or qrCode found"); + eventData.put("eventType", "onLoadError"); + + MopPluginService.getInstance().getMopEventStream().send( + "platform_view_events", + "onLoadError", + eventData + ); + } catch (Exception e) { + Log.e(TAG, "Failed to send onError event", e); + } Log.d(TAG, "MopWidgetPlatformView: no appId or qrCode found"); } @@ -77,11 +81,12 @@ public MopWidgetPlatformView(@NonNull Context context, } } - private void buildContent(Context context, String appId, boolean enableMultiComponent) { - // TODO: Replace with real native view creation based on externalViewType + private void buildContent(Context context, String appId, boolean enableMultiComponent, boolean forceUpdate) { - IFinAppletRequest request = IFinAppletRequest.Companion.fromAppId(appId); + IFinAppletRequest request = IFinAppletRequest.Companion.fromAppId(appId) + .setForceUpdate(forceUpdate); request.setEnableMultiComponent(enableMultiComponent); + FinAppClient.INSTANCE.getAppletApiManager().startComponent((FragmentActivity) activity, request, new ComponentCallback() { @Override public void onSuccess(@Nullable String s) { @@ -89,16 +94,24 @@ public void onSuccess(@Nullable String s) { } @Override - public void onError(int i, @Nullable String s) { - Log.d(TAG, "onError: code=$code, error=$error"); - TextView textView = new TextView(context); - textView.setText("出错啦:" + s); - container.addView( - textView, - new ViewGroup.LayoutParams( - FrameLayout.LayoutParams.WRAP_CONTENT, - FrameLayout.LayoutParams.WRAP_CONTENT) - ); + public void onError(int code, @Nullable String error) { + Log.d(TAG, "onError: " + error); + + // 发送加载失败事件 + try { + Map eventData = new HashMap<>(); + eventData.put("viewId", viewId); + eventData.put("errorMessage", "onError: " + error); + eventData.put("eventType", "onLoadError"); + + MopPluginService.getInstance().getMopEventStream().send( + "platform_view_events", + "onLoadError", + eventData + ); + } catch (Exception e) { + Log.e(TAG, "Failed to send onLoadError event", e); + } } @Override @@ -117,24 +130,56 @@ public void onComponentCreated(@NonNull ComponentHolder componentHolder) { } @Override - public void onContentSizeChanged(int i, int i1) { + public void onContentSizeChanged(int width, int height) { Log.d(TAG, "onContentSizeChanged:"); + // 发送内容尺寸变化事件 + try { + Map eventData = new HashMap<>(); + eventData.put("viewId", viewId); + eventData.put("width", width); + eventData.put("height", height); + eventData.put("eventType", "contentSizeChanged"); + + MopPluginService.getInstance().getMopEventStream().send( + "platform_view_events", + "contentSizeChanged", + eventData + ); + } catch (Exception e) { + Log.e(TAG, "Failed to send contentSizeChanged event", e); + } } @Override public void onContentLoaded() { Log.d(TAG, "onLoad:"); + // 发送内容加载完成事件 + try { + Map eventData = new HashMap<>(); + eventData.put("viewId", viewId); + eventData.put("message", "widget loaded successfully"); + eventData.put("eventType", "onContentLoaded"); + + MopPluginService.getInstance().getMopEventStream().send( + "platform_view_events", + "onContentLoaded", + eventData + ); + } catch (Exception e) { + Log.e(TAG, "Failed to send onContentLoaded event", e); + } } }); } - private void buildContentWithQR( Context context, String qrCode, boolean enableMultiComponent) { - // TODO: Replace with real native view creation based on externalViewType + private void buildContentWithQR( Context context, String qrCode, boolean enableMultiComponent, boolean forceUpdate) { - IFinAppletRequest request = IFinAppletRequest.Companion.fromQrCode(qrCode); + IFinAppletRequest request = IFinAppletRequest.Companion.fromQrCode(qrCode) + .setForceUpdate(forceUpdate); request.setEnableMultiComponent(enableMultiComponent); + FinAppClient.INSTANCE.getAppletApiManager().startComponent((FragmentActivity) activity, request, new ComponentCallback() { @Override public void onSuccess(@Nullable String s) { @@ -142,16 +187,23 @@ public void onSuccess(@Nullable String s) { } @Override - public void onError(int i, @Nullable String s) { - Log.d(TAG, "onError: code=$code, error=$error"); - TextView textView = new TextView(context); - textView.setText("出错啦:" + s); - container.addView( - textView, - new ViewGroup.LayoutParams( - FrameLayout.LayoutParams.WRAP_CONTENT, - FrameLayout.LayoutParams.WRAP_CONTENT) - ); + public void onError(int code, @Nullable String error) { + Log.d(TAG, "onError: " + error); + // 发送加载失败事件 + try { + Map eventData = new HashMap<>(); + eventData.put("viewId", viewId); + eventData.put("errorMessage", "onError: " + error); + eventData.put("eventType", "onError"); + + MopPluginService.getInstance().getMopEventStream().send( + "platform_view_events", + "onLoadError", + eventData + ); + } catch (Exception e) { + Log.e(TAG, "Failed to send onLoadError event", e); + } } @Override @@ -170,13 +222,44 @@ public void onComponentCreated(@NonNull ComponentHolder componentHolder) { } @Override - public void onContentSizeChanged(int i, int i1) { + public void onContentSizeChanged(int width, int height) { Log.d(TAG, "onContentSizeChanged:"); + try { + Map eventData = new HashMap<>(); + eventData.put("viewId", viewId); + eventData.put("width", width); + eventData.put("height", height); + eventData.put("eventType", "contentSizeChanged"); + + MopPluginService.getInstance().getMopEventStream().send( + "platform_view_events", + "contentSizeChanged", + eventData + ); + } catch (Exception e) { + Log.e(TAG, "Failed to send contentSizeChanged event", e); + } } @Override public void onContentLoaded() { Log.d(TAG, "onLoad:"); + + // 发送内容加载完成事件 + try { + Map eventData = new HashMap<>(); + eventData.put("viewId", viewId); + eventData.put("message", "widget loaded successfully"); + eventData.put("eventType", "onContentLoaded"); + + MopPluginService.getInstance().getMopEventStream().send( + "platform_view_events", + "onContentLoaded", + eventData + ); + } catch (Exception e) { + Log.e(TAG, "Failed to send onContentLoaded event", e); + } } }); diff --git a/ios/Classes/MopWidgetPlatformView.m b/ios/Classes/MopWidgetPlatformView.m index 60996fc..800566c 100644 --- a/ios/Classes/MopWidgetPlatformView.m +++ b/ios/Classes/MopWidgetPlatformView.m @@ -1,12 +1,14 @@ #import "MopWidgetPlatformView.h" #import +#import "MopPlugin.h" -@interface MopWidgetPlatformView () { +@interface MopWidgetPlatformView () { UIView *_view; NSString *_externalViewType; NSDictionary *_params; NSObject* _messenger; + int64_t _viewId; } @end @@ -19,8 +21,9 @@ - (instancetype)initWithFrame:(CGRect)frame self = [super init]; if (self) { _messenger = messenger; + _viewId = viewId; _view = [[UIView alloc] initWithFrame:frame]; - + if ([args isKindOfClass:[NSDictionary class]]) { NSDictionary *dict = (NSDictionary *)args; id vt = dict[@"viewType"]; if ([vt isKindOfClass:[NSString class]]) { _externalViewType = vt; } @@ -29,14 +32,22 @@ - (instancetype)initWithFrame:(CGRect)frame if (p[@"appId"]) { NSString *apiServer = p[@"apiServer"] != NULL ? p[@"apiServer"] : @""; - - [self openWidget:p[@"appId"] apiServer:apiServer]; + [self openWidget:p[@"appId"] apiServer:apiServer enableMultiWidget:p[@"enableMultiWidget"] != NULL forceUpdate:p[@"forceUpdate"] != NULL]; } else if (p[@"qrCode"]) { - [self openWidgetWithQR:p[@"qrCode"]]; + [self openWidgetWithQR:p[@"qrCode"] forceUpdate: p[@"forceUpdate"] != NULL]; } else { + NSLog(@"fincli 打开小组件报错:%@", error); + MopEventStream *eventStream = [[MopPlugin instance] mopEventStreamHandler]; + NSDictionary *eventData = @{ + @"viewId": @(self->_viewId), + @"eventType": @"onLoadError", + @"errorMessage": @"MopWidgetPlatformView: no appId or qrCode found" + }; + + [eventStream send:@"platform_view_events" event:@"onLoadError" body:eventData]; NSLog(@"你需要传入小组件的 appId 或着小组件的二维码"); } } @@ -49,36 +60,81 @@ - (UIView *)view { return _view; } -- (void)openWidget:(NSString *)appId apiServer:(NSString *)apiserver { +- (void)openWidget:(NSString *)appId apiServer:(NSString *)apiserver enableMultiWidget:(Boolean)enableMultiWidget forceUpdate:(Boolean)forceUpdate{ FATWidgetRequest *widgetRequest = [[FATWidgetRequest alloc]init]; widgetRequest.widgetId = appId; widgetRequest.widgetServer = apiserver; + widgetRequest.enableMultiWidget = enableMultiWidget; + widgetRequest.forceUpdate = forceUpdate; [[FATClient sharedClient].widgetManager createWidget:widgetRequest parentViewController:[[UIApplication sharedApplication] fat_topViewController] completion:^(FATWidgetView * _Nullable widgetView, FATError * _Nonnull error) { NSLog(@"fincli 打开小组件"); if (!error) { if (widgetView) { + widgetView.delegate = self; [self showWidgetView:widgetView]; + + MopEventStream *eventStream = [[MopPlugin instance] mopEventStreamHandler]; + NSDictionary *eventData = @{ + @"viewId": @(self->_viewId), + @"message": @"widget loaded successfully", + @"eventType": @"onContentLoaded" + }; + [eventStream send:@"platform_view_events" event:@"onContentLoaded" body:eventData]; + } }else { NSLog(@"fincli 打开小组件报错:%@", error); + MopEventStream *eventStream = [[MopPlugin instance] mopEventStreamHandler]; + NSDictionary *eventData = @{ + @"viewId": @(self->_viewId), + @"eventType": @"onLoadError", + @"error": @"widget failed to load", + @"errorCode": @(error.code), + @"errorMessage": error.localizedDescription ?: @"Unknown error" + }; + + [eventStream send:@"platform_view_events" event:@"onLoadError" body:eventData]; } }]; } -- (void)openWidgetWithQR:(NSString *)qrCode { +- (void)openWidgetWithQR:(NSString *)qrCode forceUpdate:(Boolean)forceUpdate{ FATWidgetQRCodeRequest *qrCodeRequest = [[FATWidgetQRCodeRequest alloc]init]; qrCodeRequest.qrCode = qrCode; - + qrCodeRequest.forceUpdate = forceUpdate; [[FATClient sharedClient].widgetManager createWidgetWithQRCode:qrCodeRequest parentViewController:[[UIApplication sharedApplication] fat_topViewController] completion:^(FATWidgetView * _Nullable widgetView, FATError *error) { if (!error) { if (widgetView) { + widgetView.delegate = self; [self showWidgetView:widgetView]; + + MopEventStream *eventStream = [[MopPlugin instance] mopEventStreamHandler]; + NSDictionary *eventData = @{ + @"viewId": @(self->_viewId), + @"message": @"widget loaded successfully", + @"eventType": @"onContentLoaded" + }; + + [eventStream send:@"platform_view_events" event:@"onContentLoaded" body:eventData]; + + } } else { NSLog(@"fincli 打开小组件报错:%@", error); + + MopEventStream *eventStream = [[MopPlugin instance] mopEventStreamHandler]; + NSDictionary *eventData = @{ + @"viewId": @(self->_viewId), + @"eventType": @"onLoadError", + @"error": @"widget failed to load", + @"errorCode": @(error.code), + @"errorMessage": error.localizedDescription ?: @"Unknown error" + }; + + [eventStream send:@"platform_view_events" event:@"onLoadError" body:eventData]; } }]; } @@ -101,4 +157,17 @@ - (void)showWidgetView:(FATWidgetView *)widgetView { } +- (void)onWidgetView:(nonnull FATWidgetView *)widgetView contentSizeUpdate:(CGSize)size { + // 通过代理发送事件到Flutter + MopEventStream *eventStream = [[MopPlugin instance] mopEventStreamHandler]; + NSDictionary *eventData = @{ + @"viewId": @(_viewId), + @"width": @(size.width), + @"height": @(size.height), + @"eventType": @"contentSizeChanged" + }; + + [eventStream send:@"platform_view_events" event:@"contentSizeChanged" body:eventData]; +} + @end diff --git a/lib/api.dart b/lib/api.dart index 4d0c8db..2e88da0 100644 --- a/lib/api.dart +++ b/lib/api.dart @@ -52,3 +52,14 @@ abstract class AppletHandler { ///getMobileNumber Future getMobileNumber(Function(dynamic params) param0); } + +abstract class WidgetHandler { + /// Widget内容尺寸变化回调 + void onContentSizeChanged(int viewId, double width, double height); + + /// 当小组件加载完成时调用 + void onContentLoaded(int viewId); + + /// 当小组件发生错误时调用 + void onError(int viewId, String error); +} diff --git a/lib/mop.dart b/lib/mop.dart index 3ff13f6..b83bf58 100644 --- a/lib/mop.dart +++ b/lib/mop.dart @@ -5,7 +5,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:mop/api.dart'; export 'package:mop/api.dart' show FinFilePathType; -export 'platform_view.dart' show MopPlatformView; +export 'platform_view.dart' show MopPlatformView, PlatformViewEventDelegate; typedef MopEventCallback = void Function(dynamic event); typedef MopEventErrorCallback = void Function(dynamic event); diff --git a/lib/platform_view.dart b/lib/platform_view.dart index da5281a..f6af0cc 100644 --- a/lib/platform_view.dart +++ b/lib/platform_view.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:io'; import 'package:flutter/foundation.dart'; @@ -6,38 +7,114 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter/rendering.dart'; +/// 平台视图事件代理接口 +abstract class PlatformViewEventDelegate { + /// 当PlatformView的内容尺寸发生变化时调用 + void onContentSizeChanged(int viewId, double width, double height); + + /// 当PlatformView加载完成时调用 + void onContentLoaded(int viewId); + + /// 当PlatformView发生错误时调用 + void onError(int viewId, String error); +} + /// 统一平台视图入口 /// /// 使用方式: /// MopPlatformView( /// viewType: 'native-view-android', /// creationParams: {'foo': 'bar'}, +/// eventDelegate: MyPlatformViewDelegate(), /// ) -class MopPlatformView extends StatelessWidget { +class MopPlatformView extends StatefulWidget { final String viewType; final Map? creationParams; final MessageCodec? creationParamsCodec; + final PlatformViewEventDelegate? eventDelegate; const MopPlatformView({ Key? key, required this.viewType, this.creationParams, this.creationParamsCodec, + this.eventDelegate, }) : super(key: key); static const String _unifiedRegistryViewType = 'com.finogeeks.mop/platform_view'; + @override + State createState() => _MopPlatformViewState(); +} + +class _MopPlatformViewState extends State { + static const String _unifiedRegistryViewType = 'com.finogeeks.mop/platform_view'; + static const EventChannel _platformViewEventChannel = + EventChannel('plugins.mop.finogeeks.com/mop_event'); + + StreamSubscription? _eventSubscription; + + @override + void initState() { + super.initState(); + _listenToPlatformViewEvents(); + } + + @override + void dispose() { + _eventSubscription?.cancel(); + super.dispose(); + } + + void _listenToPlatformViewEvents() { + _eventSubscription = _platformViewEventChannel.receiveBroadcastStream().listen( + (event) { + if (event is Map && event['channel'] == 'platform_view_events') { + final eventData = event['body']; + final eventType = event['event']; + + switch (eventType) { + case 'contentSizeChanged': + widget.eventDelegate?.onContentSizeChanged( + eventData['viewId'] as int, + (eventData['width'] as num).toDouble(), + (eventData['height'] as num).toDouble(), + ); + break; + case 'onContentLoaded': + widget.eventDelegate?.onContentLoaded(eventData['viewId'] as int); + break; + case 'onLoadError': + widget.eventDelegate?.onError( + eventData['viewId'] as int, + eventData['errorMessage'] as String, + ); + break; + } + } + }, + onError: (error) { + debugPrint('PlatformView event error: $error'); + }, + ); + } + @override Widget build(BuildContext context) { - final codec = creationParamsCodec ?? const StandardMessageCodec(); + final codec = widget.creationParamsCodec ?? const StandardMessageCodec(); final params = { - 'viewType': viewType, - 'params': creationParams, + 'viewType': widget.viewType, + 'params': widget.creationParams, }; - if (defaultTargetPlatform == TargetPlatform.android) { - // 使用统一注册的 viewType,实际的原生视图类型通过 creationParams 传递 - // 为了更好的兼容性与性能,这里采用 Hybrid Composition:PlatformViewLink + AndroidViewSurface + if (defaultTargetPlatform == TargetPlatform.iOS) { + return UiKitView( + viewType: _unifiedRegistryViewType, + creationParams: params, + creationParamsCodec: codec, + ); + } else { + // Android端暂时保持现有实现 return PlatformViewLink( viewType: _unifiedRegistryViewType, surfaceFactory: (context, controller) { @@ -52,7 +129,6 @@ class MopPlatformView extends StatelessWidget { id: creationParams0.id, viewType: _unifiedRegistryViewType, layoutDirection: TextDirection.ltr, - // 注意:这里的 creationParams 需与 surfaceFactory 同步 creationParams: params, creationParamsCodec: codec, ); @@ -61,13 +137,6 @@ class MopPlatformView extends StatelessWidget { return controller; }, ); - } else if (defaultTargetPlatform == TargetPlatform.iOS) { - return UiKitView( - viewType: _unifiedRegistryViewType, - creationParams: params, - creationParamsCodec: codec, - ); } - return const Center(child: Text('当前平台不支持原生视图')); } }