diff --git a/android/build.gradle b/android/build.gradle index 49a009f..319b2bd 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -44,8 +44,13 @@ android { } dependencies { - compileOnly "com.apxor.androidx:apxor-android-sdk-core:3.0.4@aar" - compileOnly "com.apxor.androidx:apxor-android-sdk-qe:1.6.6@aar" - compileOnly "com.apxor.androidx:apxor-android-sdk-rtm:2.3.8@aar" - compileOnly "com.apxor.androidx:wysiwyg:1.4.9@aar" + // compileOnly "com.apxor.androidx:apxor-android-sdk-core:3.0.4@aar" + // compileOnly "com.apxor.androidx:apxor-android-sdk-qe:1.6.6@aar" + // compileOnly "com.apxor.androidx:apxor-android-sdk-rtm:2.3.8@aar" + // compileOnly "com.apxor.androidx:wysiwyg:1.4.9@aar" + implementation project(path: ':core') + implementation project(path: ':qe') + implementation project(path: ':rtm-x') + implementation project(path: ':surveys') + implementation project(path: ':wysiwyg') } diff --git a/android/src/main/java/com/apxor/flutter/ApxorEmbedView.java b/android/src/main/java/com/apxor/flutter/ApxorEmbedView.java new file mode 100644 index 0000000..5f98679 --- /dev/null +++ b/android/src/main/java/com/apxor/flutter/ApxorEmbedView.java @@ -0,0 +1,59 @@ +package com.apxor.flutter; + +import android.content.Context; +import android.graphics.Color; +import android.view.View; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import io.flutter.plugin.platform.PlatformView; +import java.util.Map; + +import com.apxor.androidsdk.core.utils.Logger; +import com.apxor.androidsdk.plugins.realtimeui.ApxorWidget; + +import io.flutter.plugin.common.BasicMessageChannel; +import io.flutter.plugin.common.JSONMessageCodec; +import io.flutter.plugin.common.BinaryMessenger; +import com.apxor.androidsdk.core.ce.ExecutionListener; + +import org.json.JSONObject; + +class ApxorEmbedView implements PlatformView { + private ApxorWidget apxorView; + private int tag = -1; + private BasicMessageChannel viewChannel; + + ApxorEmbedView(@NonNull Context context, int id, @Nullable Map creationParams, final BinaryMessenger binaryMessenger) { + try { + tag = (int) creationParams.get("id"); + viewChannel = new BasicMessageChannel<>( + binaryMessenger, + "plugins.flutter.io/apxor_view_"+this.tag, + JSONMessageCodec.INSTANCE + ); + } catch (Exception e) { + Logger.debug("Apxor","Flutter value key is not valid "+e.getMessage()); + } + apxorView = new ApxorWidget(context,tag,"flutter",new ExecutionListener() { + @Override + public void onAfterExecute(Object result, boolean hasError) { + Logger.debug("Apxor","Received dimensions from native "+result+""+hasError); + if(result != null && viewChannel != null) { + if(result instanceof JSONObject) { + viewChannel.send(result); + } + } + } + }); + } + + @Override + public View getView() { + return apxorView; + } + + @Override + public void dispose() { + viewChannel = null; + } +} \ No newline at end of file diff --git a/android/src/main/java/com/apxor/flutter/ApxorEmbedViewFactory.java b/android/src/main/java/com/apxor/flutter/ApxorEmbedViewFactory.java new file mode 100644 index 0000000..20e0298 --- /dev/null +++ b/android/src/main/java/com/apxor/flutter/ApxorEmbedViewFactory.java @@ -0,0 +1,26 @@ +package com.apxor.flutter; + +import android.content.Context; +import androidx.annotation.Nullable; +import androidx.annotation.NonNull; +import io.flutter.plugin.common.StandardMessageCodec; +import io.flutter.plugin.platform.PlatformView; +import io.flutter.plugin.platform.PlatformViewFactory; +import io.flutter.plugin.common.BinaryMessenger; +import java.util.Map; + +class ApxorEmbedViewFactory extends PlatformViewFactory { + private final BinaryMessenger binaryMessenger; + + public ApxorEmbedViewFactory(BinaryMessenger binaryMessenger) { + super(StandardMessageCodec.INSTANCE); + this.binaryMessenger = binaryMessenger; + } + + @NonNull + @Override + public PlatformView create(@NonNull Context context, int id, @Nullable Object args) { + final Map creationParams = (Map) args; + return new ApxorEmbedView(context, id, creationParams, binaryMessenger); + } +} diff --git a/android/src/main/java/com/apxor/flutter/ApxorFlutterPlugin.java b/android/src/main/java/com/apxor/flutter/ApxorFlutterPlugin.java index a47d7eb..35aa430 100644 --- a/android/src/main/java/com/apxor/flutter/ApxorFlutterPlugin.java +++ b/android/src/main/java/com/apxor/flutter/ApxorFlutterPlugin.java @@ -60,7 +60,8 @@ public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBindin "plugins.flutter.io/apxor_commands", JSONMessageCodec.INSTANCE ); - + registerEmbedView(flutterPluginBinding); + registerStoryView(flutterPluginBinding); SDKController controller = SDKController.getInstance(); controller.markAsFlutter(); controller.registerToEvent(INTERNAL_EVENTS, this); @@ -91,7 +92,7 @@ public void receiveAndRespond(JSONObject data, Receiver receiver) { eName = "d"; } else if (name.equals(QYG)) { eName = "f"; - } else if (name.equals("apx_iwv")){ + } else if (name.equals("apx_iwv")) { eName = "iwv"; } else if (name.equals("apx_avf")) { eName = "avf"; @@ -126,6 +127,13 @@ public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { SDKController.getInstance().setIsFlutter(false); } + private void registerEmbedView(@NonNull FlutterPluginBinding flutterPluginBinding) { + flutterPluginBinding + .getPlatformViewRegistry() + .registerViewFactory("com.apxor.flutter/ApxorEmbedView", new ApxorEmbedViewFactory( + flutterPluginBinding.getBinaryMessenger())); + } + @Override public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) { switch (call.method) { @@ -199,16 +207,16 @@ public void onEvent(BaseApxorEvent event) { handleBB(); break; case "d": - try{ + try { map.put("d", data.getDouble("d")); - map.put("js",data.optString("js")); + map.put("js", data.optString("js")); map.put("root_element", data.optString("root_element")); - } catch (Exception e){ - + } catch (Exception e) { + } break; case "avf": - map.put("d",data.getDouble("d")); + map.put("d", data.getDouble("d")); map.put("root_element", data.optString("root_element")); break; case "f": @@ -222,11 +230,11 @@ public void onEvent(BaseApxorEvent event) { map.put("msgDuration", data.getInt("msgDuration")); map.put("uuid", data.getString("uuid")); map.put("configName", data.getString("configName")); - map.put("js",data.getString("js")); + map.put("js", data.getString("js")); map.put("root_element", data.optString("root_element")); - } catch (Exception e){ - - } + } catch (Exception e) { + + } break; } if (map.size() > 0) { @@ -272,7 +280,7 @@ private void handleMethodCall(MethodCall call, Result result, String name) { } result.success(null); } catch (Exception e) { - result.error("Apxor", "Failed to parse attributes in log" + name +"Event. " + e.getMessage(), null); + result.error("Apxor", "Failed to parse attributes in log" + name + "Event. " + e.getMessage(), null); } } @@ -287,6 +295,7 @@ private void handleLogClientEvent(MethodCall call, Result result) { private void handleLogInternalEvent(MethodCall call, Result result) { handleMethodCall(call, result, "Internal"); } + private void handleSetUserIdentifier(MethodCall call, Result result) { String customUserId = call.argument("userId"); ApxorSDK.setUserIdentifier(customUserId); @@ -447,4 +456,11 @@ private static String h(String t) { return new String(a); } + private void registerStoryView(@NonNull FlutterPluginBinding flutterPluginBinding) { + flutterPluginBinding + .getPlatformViewRegistry() + .registerViewFactory("com.apxor.flutter/ApxorStoryView", new ApxorStoryViewFactory( + flutterPluginBinding.getBinaryMessenger())); + } + } diff --git a/android/src/main/java/com/apxor/flutter/ApxorStoryView.java b/android/src/main/java/com/apxor/flutter/ApxorStoryView.java new file mode 100644 index 0000000..ab82138 --- /dev/null +++ b/android/src/main/java/com/apxor/flutter/ApxorStoryView.java @@ -0,0 +1,59 @@ +package com.apxor.flutter; + +import android.content.Context; +import android.graphics.Color; +import android.view.View; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import io.flutter.plugin.platform.PlatformView; +import java.util.Map; + +import com.apxor.androidsdk.core.utils.Logger; +import com.apxor.androidsdk.plugins.realtimeui.stories.ApxorStoryWidget; + +import io.flutter.plugin.common.BasicMessageChannel; +import io.flutter.plugin.common.JSONMessageCodec; +import io.flutter.plugin.common.BinaryMessenger; +import com.apxor.androidsdk.core.ce.ExecutionListener; + +import org.json.JSONObject; + +class ApxorStoryView implements PlatformView { + private ApxorStoryWidget apxorView; + private String tag = ""; + private BasicMessageChannel viewChannel; + + ApxorStoryView(@NonNull Context context, int id, @Nullable Map creationParams, + final BinaryMessenger binaryMessenger) { + try { + this.tag = "apx_story_" + creationParams.get("id").toString(); + viewChannel = new BasicMessageChannel<>( + binaryMessenger, + "plugins.flutter.io/apxor_view_" + creationParams.get("id").toString(), + JSONMessageCodec.INSTANCE); + } catch (Exception e) { + Logger.debug("Apxor", "Flutter value key is not valid " + e.getMessage()); + } + apxorView = new ApxorStoryWidget(context, tag, new ExecutionListener() { + @Override + public void onAfterExecute(Object result, boolean hasError) { + Logger.debug("Apxor", "Received dimensions from native " + result + "" + hasError); + if (result != null && viewChannel != null) { + if (result instanceof JSONObject) { + viewChannel.send(result); + } + } + } + }); + } + + @Override + public View getView() { + return apxorView; + } + + @Override + public void dispose() { + viewChannel = null; + } +} \ No newline at end of file diff --git a/android/src/main/java/com/apxor/flutter/ApxorStoryViewFactory.java b/android/src/main/java/com/apxor/flutter/ApxorStoryViewFactory.java new file mode 100644 index 0000000..08738fb --- /dev/null +++ b/android/src/main/java/com/apxor/flutter/ApxorStoryViewFactory.java @@ -0,0 +1,26 @@ +package com.apxor.flutter; + +import android.content.Context; +import androidx.annotation.Nullable; +import androidx.annotation.NonNull; +import io.flutter.plugin.common.StandardMessageCodec; +import io.flutter.plugin.platform.PlatformView; +import io.flutter.plugin.platform.PlatformViewFactory; +import io.flutter.plugin.common.BinaryMessenger; +import java.util.Map; + +class ApxorStoryViewFactory extends PlatformViewFactory { + private final BinaryMessenger binaryMessenger; + + public ApxorStoryViewFactory(BinaryMessenger binaryMessenger) { + super(StandardMessageCodec.INSTANCE); + this.binaryMessenger = binaryMessenger; + } + + @NonNull + @Override + public PlatformView create(@NonNull Context context, int id, @Nullable Object args) { + final Map creationParams = (Map) args; + return new ApxorStoryView(context, id, creationParams, binaryMessenger); + } +} diff --git a/ios/Classes/APXECFactory.h b/ios/Classes/APXECFactory.h new file mode 100644 index 0000000..74d0241 --- /dev/null +++ b/ios/Classes/APXECFactory.h @@ -0,0 +1,30 @@ +// +// APXECFactory.h +// apxor_flutter +// +// Created by Dasari Kousik on 27/03/24. +// + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface APXECFactory : NSObject + +@property (nonatomic) NSObject *channelMessanger; +- (instancetype)initWithMessenger:(NSObject*)messenger; +- (FlutterBasicMessageChannel*)createMessageChannel:(NSString*)channelName; + +@end + +@interface APXEmbeddedView : NSObject +- (instancetype)initWithFrame:(CGRect)frame + viewIdentifier:(int64_t)viewId + arguments:(id _Nullable)args + binaryMessenger:(NSObject*)messenger; + +- (UIView*)view; +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Classes/APXECFactory.m b/ios/Classes/APXECFactory.m new file mode 100644 index 0000000..9293c60 --- /dev/null +++ b/ios/Classes/APXECFactory.m @@ -0,0 +1,51 @@ +// +// APXECFactory.m +// apxor_flutter +// +// Created by Dasari Kousik on 27/03/24. +// + +#import "APXECFactory.h" +#import "APXRTAPlugin/APXRTAPlugin.h" + +@implementation APXECFactory{ + NSObject* _messenger; +} + +- (nonnull instancetype)initWithMessenger:(nonnull NSObject *)messenger { + self = [self init]; + if(self){ + _messenger = messenger; + } + return self; +} + + +- (nonnull NSObject *)createWithFrame:(CGRect)frame viewIdentifier:(int64_t)viewId arguments:(id _Nullable)args { + return [[APXEmbeddedView alloc] initWithFrame:frame viewIdentifier:viewId arguments:args binaryMessenger:_messenger]; +} + +- (NSObject *)createArgsCodec{ + return FlutterStandardMessageCodec.sharedInstance; +} + +@end + +@implementation APXEmbeddedView{ + UIView *_view; +} + +- (instancetype)initWithFrame:(CGRect)frame viewIdentifier:(int64_t)viewId arguments:(id _Nullable)args + binaryMessenger:(NSObject*)messenger{ + if(self = [super init]){ + NSInteger tag = [[args objectForKey:@"id"] integerValue]; + _view = [APXRTAPlugin initEmbedCardWithId:tag]; + } + return self; +} + +- (nonnull UIView *)view { + return _view; +} + +@end diff --git a/ios/Classes/APXFlutterBidiEventBus.m b/ios/Classes/APXFlutterBidiEventBus.m index 69fdd0c..2525261 100644 --- a/ios/Classes/APXFlutterBidiEventBus.m +++ b/ios/Classes/APXFlutterBidiEventBus.m @@ -9,13 +9,16 @@ #import #import "APXFlutterBidiEventBus.h" #import "ApxorSDK/APXController.h" +#import "ApxorFlutterPlugin.h" +#import +static FlutterBasicMessageChannel *card_channel = nil; +static NSObject *messenger = nil; @implementation APXFlutterBidiEventBus { NSString *eName; NSString *eTime; Receiver eReceiver; } - - (void) sendAndGetWithData: (NSDictionary *)data receiver:(Receiver)receiver { id otherBus = [[APXController sharedController] getBidiEventsBusWithKey:@"APXOR_FLUTTER_C"]; if (nil != otherBus) { @@ -40,6 +43,10 @@ - (void)receiveAndRespondWithData:(NSMutableDictionary *)data receiver:(Receiver eName = @"avf"; } else if ([name isEqualToString:@"apx_iwv"]) { eName = @"iwv"; + } else if ([name isEqualToString:@"apx_ec"]){ + eName = @"EC"; + } else if ([name isEqualToString:@"apx_story"]) { + eName = @"story"; } if (nil != eName) { diff --git a/ios/Classes/APXStoryFactory.h b/ios/Classes/APXStoryFactory.h new file mode 100644 index 0000000..5ca61c7 --- /dev/null +++ b/ios/Classes/APXStoryFactory.h @@ -0,0 +1,29 @@ +// +// APXStoryFactory.h +// apxor_flutter +// +// Created by Dasari Kousik on 03/05/24. +// + +#import +#import + + +@interface APXStoryFactory : NSObject + +- (instancetype)initWithMessenger:(NSObject*)messenger; + +@end + +@interface APXStoryView : NSObject + +- (instancetype)initWithFrame:(CGRect)frame + viewIdentifier:(int64_t)viewId + arguments:(id _Nullable)args + binaryMessenger:(NSObject*_Nullable)messenger; + +- (UIView*_Nonnull)view; + +@end + + diff --git a/ios/Classes/APXStoryFactory.m b/ios/Classes/APXStoryFactory.m new file mode 100644 index 0000000..237cfe9 --- /dev/null +++ b/ios/Classes/APXStoryFactory.m @@ -0,0 +1,50 @@ +// +// APXStoryFactory.m +// apxor_flutter +// +// Created by Dasari Kousik on 03/05/24. +// + +#import "APXStoryFactory.h" +#import "APXRTAPlugin/APXRTAPlugin.h" + +@implementation APXStoryFactory { + NSObject* _messenger; +} + +- (instancetype)initWithMessenger:(NSObject*)messenger { + self = [super init]; + if (self) { + _messenger = messenger; + } + return self; +} + +- (nonnull NSObject *)createWithFrame:(CGRect)frame viewIdentifier:(int64_t)viewId arguments:(id _Nullable)args { + return [[APXStoryView alloc] initWithFrame:frame viewIdentifier:viewId arguments:args binaryMessenger:_messenger]; +} + +- (NSObject*)createArgsCodec { + return [FlutterStandardMessageCodec sharedInstance]; +} + +@end + +@implementation APXStoryView { + UIView *_view; +} + +- (instancetype)initWithFrame:(CGRect)frame viewIdentifier:(int64_t)viewId arguments:(id _Nullable)args binaryMessenger:(NSObject *)messenger { + self = [super init]; + if (self) { + NSInteger tag = [[args objectForKey:@"id"] integerValue]; + _view = [APXRTAPlugin initStoriesWithId:tag]; + } + return self; +} + +- (UIView *)view { + return _view; +} + +@end diff --git a/ios/Classes/ApxorFlutterPlugin.m b/ios/Classes/ApxorFlutterPlugin.m index e189319..58ce4ca 100644 --- a/ios/Classes/ApxorFlutterPlugin.m +++ b/ios/Classes/ApxorFlutterPlugin.m @@ -11,17 +11,23 @@ #import "ApxorSDK/APXController.h" #import "ApxorSDK/APXBidiDelegate.h" #import "APXFlutterBidiEventBus.h" +#import "APXECFactory.h" +#import "APXStoryFactory.h" static FlutterBasicMessageChannel *command_channel = nil; +static FlutterBasicMessageChannel *card_channel = nil; +static NSObject* registar = nil; -@implementation ApxorFlutterPlugin +@implementation ApxorFlutterPlugin { + id bus; +} - (instancetype)init { self = [super init]; if (self) { [[APXController sharedController] registerForEventWithType:APXEventTypeInternal listener:self]; [[APXController sharedController] markAsFlutter]; - id bus = [[APXFlutterBidiEventBus alloc] init]; + bus = [[APXFlutterBidiEventBus alloc] init]; [[APXController sharedController] registerForBidiEventsBus:bus WithKey:@"APXOR_FLUTTER_W"]; } return self; @@ -33,9 +39,15 @@ +(void)registerWithRegistrar:(NSObject*)registrar { FlutterMethodChannel* channel = [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/apxor_flutter" binaryMessenger:[registrar messenger]]; + APXECFactory *factory = [[APXECFactory alloc] initWithMessenger:registrar.messenger]; + [registrar registerViewFactory:factory withId:@"com.apxor.flutter/apxor_embeddedCard"]; + APXStoryFactory *storyfactory = [[APXStoryFactory alloc] initWithMessenger:registrar.messenger]; + [registrar registerViewFactory:storyfactory withId:@"com.apxor.flutter/apxor_stories"]; ApxorFlutterPlugin* instance = [[ApxorFlutterPlugin alloc] init]; [registrar addMethodCallDelegate:instance channel:channel]; + registar = registrar; + // basic message channel for communication between ApxorSDK and flutter command_channel = [FlutterBasicMessageChannel messageChannelWithName:@"plugins.flutter.io/apxor_commands" binaryMessenger:[registrar messenger] codec:[FlutterJSONMessageCodec sharedInstance]]; } @@ -92,6 +104,8 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { } } else if ([@"gfn" isEqualToString:call.method]) { result(@(0)); + } else if ([@"getDimensions" isEqualToString:call.method]){ + result(@{@"height": @200}); } else { NSArray *layout = [call.arguments valueForKey:@"r"]; NSNumber *time = [call.arguments valueForKey:@"t"]; @@ -106,7 +120,6 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { } } } - - (void)onEvent:(APXEvent *)event { NSMutableDictionary *data = [[event getAdditionalInfo] mutableCopy]; NSString *eName = event.identifier; @@ -123,6 +136,17 @@ - (void)onEvent:(APXEvent *)event { dispatch_async(dispatch_get_main_queue(), ^{ [command_channel sendMessage:eData]; }); + } else if ([eName isEqualToString:@"EC"]) { + dispatch_async(dispatch_get_main_queue(), ^{ + card_channel = [FlutterBasicMessageChannel messageChannelWithName:[NSString stringWithFormat:@"plugins.flutter.io/embeddedView%@",[data valueForKey:@"id"]] binaryMessenger:[registar messenger] + codec:[FlutterJSONMessageCodec sharedInstance]]; + [card_channel sendMessage:data]; + }); + } else if ([eName isEqualToString:@"story"]) { + dispatch_async(dispatch_get_main_queue(), ^{ + card_channel = [FlutterBasicMessageChannel messageChannelWithName:[NSString stringWithFormat:@"plugins.flutter.io/story%@",[data objectForKey:@"id"]] binaryMessenger:[registar messenger] codec:[FlutterJSONMessageCodec sharedInstance]]; + [card_channel sendMessage:data]; + }); } } diff --git a/lib/apxor_embed_widget.dart b/lib/apxor_embed_widget.dart new file mode 100644 index 0000000..23a443d --- /dev/null +++ b/lib/apxor_embed_widget.dart @@ -0,0 +1,179 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +class ApxorEmbedWidget extends StatefulWidget { + final int id; + + const ApxorEmbedWidget({Key? key, required this.id}) : super(key: key); + @override + State createState() => ApxorEmbedWidgetState(id); +} + +class ApxorEmbedWidgetState extends State + with AutomaticKeepAliveClientMixin { + static final Map _widgetDimensions = {}; + int id; + late double height; + late double width; + bool visible = true; + BasicMessageChannel? widgetChannel; + bool keepAlive = false; + + ApxorEmbedWidgetState(this.id); + @override + bool get wantKeepAlive => keepAlive; + + @override + void initState() { + super.initState(); + print("Apxor embed widget created for $id"); + + double initialHeight = + (defaultTargetPlatform == TargetPlatform.android) ? 300 : 0; + + Size storedSize = + _widgetDimensions[id] ?? Size(double.infinity, initialHeight); + height = storedSize.height; + width = storedSize.width; + + if (defaultTargetPlatform == TargetPlatform.android) { + visible = _widgetDimensions.containsKey(id); + } + + if (width > 0 && height > 0) { + keepAlive = true; + } + if (defaultTargetPlatform == TargetPlatform.android) { + widgetChannel = BasicMessageChannel( + "plugins.flutter.io/apxor_view_$id", const JSONMessageCodec()); + widgetChannel?.setMessageHandler((message) async { + try { + print("Apxor: Message received for $id $message"); + Map messageMap = message; + if (messageMap["method"] == "dim") { + int nativeid = messageMap["id"]; + if (nativeid != id) { + return; + } + int heightCalc = messageMap["height"]; + int widthCalc = messageMap["width"]; + print("Height and width is ${heightCalc} ${widthCalc}"); + updateStoredDimensions(heightCalc.toDouble(), widthCalc.toDouble()); + } + } catch (e) { + print("Apxor: Embed Widget Error $e"); + } + }); + } else if (defaultTargetPlatform == TargetPlatform.iOS) { + widgetChannel = BasicMessageChannel( + "plugins.flutter.io/embeddedView${id.toString()}", + JSONMessageCodec()); + widgetChannel?.setMessageHandler((message) async { + try { + print("Apxor: Message received for $message"); + Map messageMap = message; + if (messageMap["method"] == "dim") { + int Nativeid = messageMap["id"]; + if (id != Nativeid) { + return; + } + int heightCalc = messageMap["height"]; + int widthCalc = messageMap["width"]; + print("Apxor: Received Dimensions $id $heightCalc $widthCalc"); + updateStoredDimensions(heightCalc.toDouble(), widthCalc.toDouble()); + } + } catch (e) { + print("Apxor: Embed Widget Error $e"); + } + }); + } + } + + void updateStoredDimensions(double newHeight, double newWidth) { + double? oldHeight = _widgetDimensions[id]?.height; + double? oldWidth = _widgetDimensions[id]?.width; + if (oldHeight == newHeight && oldWidth == newWidth) { + return; + } + _widgetDimensions[id] = Size(newWidth, newHeight); + setState(() { + height = newHeight; + width = double.infinity; + if (height > 0 && width > 0) { + visible = true; + keepAlive = true; + } else { + visible = false; + keepAlive = false; + } + }); + } + + @override + Widget build(BuildContext context) { + super.build(context); + print( + "Apxor: Build called for ${id} with size: $width x $height, visible: $visible"); + const String viewType = 'com.apxor.flutter/ApxorEmbedView'; + const String iosViewType = 'com.apxor.flutter/apxor_embeddedCard'; + Map creationParams = {}; + creationParams['id'] = id; + switch (defaultTargetPlatform) { + case TargetPlatform.android: + return Stack(children: [ + Offstage( + offstage: !visible, + child: SizedBox( + width: double.infinity, + height: height, + child: AndroidView( + key: ValueKey("apx_card_$id"), + viewType: viewType, + layoutDirection: TextDirection.ltr, + creationParams: creationParams, + creationParamsCodec: const StandardMessageCodec(), + gestureRecognizers: >{ + Factory( + () => HorizontalDragGestureRecognizer(), + ), + }, + ), + ), + ), + ]); + case TargetPlatform.iOS: + return Stack(children: [ + Offstage( + offstage: !visible, + child: SizedBox( + width: width, + height: height, + child: UiKitView( + key: ValueKey("apx_card_$id"), + viewType: iosViewType, + layoutDirection: TextDirection.ltr, + creationParams: creationParams, + creationParamsCodec: const StandardMessageCodec(), + gestureRecognizers: >{ + Factory( + () => HorizontalDragGestureRecognizer(), + ), + }))) + ]); + default: + throw UnsupportedError('Unsupported platform view'); + } + } + + @override + void dispose() { + debugPrint("Apxor: Dispose called for $id"); + _widgetDimensions.clear(); + super.dispose(); + } +} diff --git a/lib/apxor_flutter.dart b/lib/apxor_flutter.dart index 4dbf079..e91e89e 100644 --- a/lib/apxor_flutter.dart +++ b/lib/apxor_flutter.dart @@ -474,6 +474,12 @@ class ApxorFlutter { .replaceFirst("'>]", "") .replaceFirst("[<", "") .replaceFirst(">]", ""); + if (key.startsWith("apx_card_")) { + n.isApxorWidget = true; + } else if (key.startsWith("apx_story_") && + defaultTargetPlatform == TargetPlatform.iOS) { + n.isApxorStoryWidget = true; + } } } } @@ -726,6 +732,8 @@ class LT { LT? closestParent; LT? parent; String extractedUsing = ""; + bool isApxorWidget = false; + bool isApxorStoryWidget = false; List c = []; @@ -752,6 +760,12 @@ class LT { r = z.isNaN || z.isInfinite ? 0 : z.toInt(); } + if (defaultTargetPlatform == TargetPlatform.iOS) { + var pos = k?.lastIndexOf('_'); + String? result = (pos != -1) ? k?.substring(pos! + 1) : k; + k = result; + } + return { k1: k != null && k!.isNotEmpty ? k : '', k2: op != null && op!.isNotEmpty ? op : '', @@ -776,7 +790,9 @@ class LT { : {}), ...((parent != null && parent!.k != null && parent!.k!.isNotEmpty) ? {"parent_id": parent!.k} - : {}) + : {}), + "is_embed_card_arena": isApxorWidget, + "is_stories_arena": isApxorStoryWidget }, 'is_in_wv': isInWv, 'wv_tag': wvTag, diff --git a/lib/apxor_story_widget.dart b/lib/apxor_story_widget.dart new file mode 100644 index 0000000..d9b6e41 --- /dev/null +++ b/lib/apxor_story_widget.dart @@ -0,0 +1,186 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +class ApxorStoryWidget extends StatefulWidget { + final int id; + + const ApxorStoryWidget({Key? key, required this.id}) : super(key: key); + + @override + State createState() => ApxorStoryWidgetState(id); +} + +class ApxorStoryWidgetState extends State + with AutomaticKeepAliveClientMixin { + BasicMessageChannel? widgetChannel; + static final Map _widgetDimensions = {}; + int id; + late double height; + late double width; + bool visible = true; + bool keepAlive = false; + + ApxorStoryWidgetState(this.id); + + @override + bool get wantKeepAlive => keepAlive; + + @override + void initState() { + super.initState(); + print("Apxor story widget created for $id"); + + double initialHeight = + (defaultTargetPlatform == TargetPlatform.android) ? 300 : 0; + + Size storedSize = + _widgetDimensions[id] ?? Size(double.infinity, initialHeight); + + height = storedSize.height; + width = storedSize.width; + + if (defaultTargetPlatform == TargetPlatform.android) { + visible = _widgetDimensions.containsKey(id); + } + + if (width > 0 && height > 0) { + keepAlive = true; + } + + if (defaultTargetPlatform == TargetPlatform.android) { + widgetChannel = BasicMessageChannel( + "plugins.flutter.io/apxor_view_$id", const JSONMessageCodec()); + + widgetChannel?.setMessageHandler((message) async { + try { + print("Apxor: Message received for $id $message"); + Map messageMap = message; + if (messageMap["method"] == "dim") { + String id = messageMap["id"]; + if (id != id) { + return; + } + int heightCalc = messageMap["height"]; + int widthCalc = messageMap["width"]; + + if (id != id) { + return; + } + + print("Apxor: Received Dimensions $id $heightCalc $widthCalc"); + updateStoredDimensions(heightCalc.toDouble(), widthCalc.toDouble()); + } + } catch (e) { + print("Apxor: Story Widget Error $e"); + } + }); + } else if (defaultTargetPlatform == TargetPlatform.iOS) { + widgetChannel = BasicMessageChannel( + "plugins.flutter.io/story${id.toString()}", JSONMessageCodec()); + print("Basic Message Channel is $widgetChannel"); + widgetChannel?.setMessageHandler((message) async { + try { + print("Apxor: Message received for $message"); + Map messageMap = message; + if (messageMap["id"] != id.toString()) { + return; + } + int heightCalc = messageMap["height"]; + int widthCalc = messageMap["width"]; + print("Apxor: Received Dimensions $id $heightCalc $widthCalc"); + updateStoredDimensions(heightCalc.toDouble(), widthCalc.toDouble()); + } catch (e) { + print("Apxor: Embed Widget Error $e"); + } + }); + } + } + + void updateStoredDimensions(double newHeight, double newWidth) { + double? oldHeight = _widgetDimensions[id]?.height; + double? oldWidth = _widgetDimensions[id]?.width; + if (oldHeight == newHeight && oldWidth == newWidth) { + return; + } + + _widgetDimensions[id] = Size(newWidth, newHeight); + + setState(() { + height = newHeight; + width = double.infinity; + if (height > 0 && width > 0) { + visible = true; + keepAlive = true; + } else { + visible = false; + keepAlive = false; + } + }); + } + + @override + Widget build(BuildContext context) { + super.build(context); + print( + "Apxor: Build called for ${id} with size: $width x $height, visible: $visible"); + const String viewType = 'com.apxor.flutter/ApxorStoryView'; + Map creationParams = {}; + creationParams['id'] = id; + const String iosViewType = 'com.apxor.flutter/apxor_stories'; + + switch (defaultTargetPlatform) { + case TargetPlatform.android: + return Offstage( + offstage: !visible, + child: SizedBox( + width: double.infinity, + height: height, + child: AndroidView( + key: ValueKey("apx_story_$id"), + viewType: viewType, + layoutDirection: TextDirection.ltr, + creationParams: creationParams, + creationParamsCodec: const StandardMessageCodec(), + gestureRecognizers: >{ + Factory( + () => HorizontalDragGestureRecognizer(), + ), + }, + ), + ), + ); + case TargetPlatform.iOS: + return Offstage( + offstage: !visible, + child: SizedBox( + width: width, + height: height, + child: UiKitView( + key: ValueKey("apx_story_$id"), + viewType: iosViewType, + layoutDirection: TextDirection.ltr, + creationParams: creationParams, + creationParamsCodec: const StandardMessageCodec(), + gestureRecognizers: >{ + Factory( + () => HorizontalDragGestureRecognizer(), + ), + }), + ), + ); + default: + throw UnsupportedError('Unsupported platform view'); + } + } + + @override + void dispose() { + debugPrint("Apxor: Dispose called for $id"); + if (defaultTargetPlatform == TargetPlatform.android) { + _widgetDimensions.clear(); + } + super.dispose(); + } +}