From c5cb39b0b4c6eab0ded7d651f8f37b63efbfed97 Mon Sep 17 00:00:00 2001 From: Ahmed alaa Date: Thu, 24 Oct 2024 15:28:33 +0300 Subject: [PATCH 1/8] add private api --- ios/Classes/InstabugFlutterPlugin.m | 26 ++++++- ios/Classes/Modules/InstabugApi.h | 4 +- ios/Classes/Modules/InstabugApi.m | 3 +- ios/Classes/Modules/PrivateViewApi.h | 44 +++++++++++ ios/Classes/Modules/PrivateViewApi.m | 107 +++++++++++++++++++++++++++ 5 files changed, 181 insertions(+), 3 deletions(-) create mode 100644 ios/Classes/Modules/PrivateViewApi.h create mode 100644 ios/Classes/Modules/PrivateViewApi.m diff --git a/ios/Classes/InstabugFlutterPlugin.m b/ios/Classes/InstabugFlutterPlugin.m index 9b9182ae7..25fd48af0 100644 --- a/ios/Classes/InstabugFlutterPlugin.m +++ b/ios/Classes/InstabugFlutterPlugin.m @@ -9,19 +9,43 @@ #import "RepliesApi.h" #import "SessionReplayApi.h" #import "SurveysApi.h" +#import "PrivateViewApi.h" @implementation InstabugFlutterPlugin + (void)registerWithRegistrar:(NSObject *)registrar { + FlutterEngineRegistrar *engineRegistrar = (FlutterEngineRegistrar *) registrar; + InitApmApi([registrar messenger]); InitBugReportingApi([registrar messenger]); InitCrashReportingApi([registrar messenger]); InitFeatureRequestsApi([registrar messenger]); - InitInstabugApi([registrar messenger]); + PrivateViewApi* privateViewApi = InitPrivateViewApi([registrar messenger],engineRegistrar); + InitInstabugApi([registrar messenger],privateViewApi); InitInstabugLogApi([registrar messenger]); InitRepliesApi([registrar messenger]); InitSessionReplayApi([registrar messenger]); InitSurveysApi([registrar messenger]); + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(8.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + UIView *flutterView = engineRegistrar.flutterEngine.viewController.view; + if (flutterView) { + // Begin image context with the view's bounds + UIGraphicsBeginImageContextWithOptions(flutterView.bounds.size, NO, 0.0); + [flutterView drawViewHierarchyInRect:flutterView.bounds afterScreenUpdates:YES]; + + // Capture the image from the context + UIImage *screenshot = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + [privateViewApi mask:screenshot completion:^(UIImage * _Nonnull maskedImage) { + NSLog(@"1"); + NSLog(@"2"); + }]; + }else{ + NSLog(@"Error: Flutter view not found."); + } + + }); + } @end diff --git a/ios/Classes/Modules/InstabugApi.h b/ios/Classes/Modules/InstabugApi.h index 7030617c9..85fc07e0f 100644 --- a/ios/Classes/Modules/InstabugApi.h +++ b/ios/Classes/Modules/InstabugApi.h @@ -1,8 +1,10 @@ #import "InstabugPigeon.h" +#import "PrivateViewApi.h" -extern void InitInstabugApi(id messenger); +extern void InitInstabugApi(id _Nonnull messenger, PrivateViewApi * _Nonnull api); @interface InstabugApi : NSObject +@property (nonatomic, strong) PrivateViewApi* _Nonnull privateViewApi; - (UIImage *)getImageForAsset:(NSString *)assetName; - (UIFont *)getFontForAsset:(NSString *)assetName error:(FlutterError *_Nullable *_Nonnull)error; diff --git a/ios/Classes/Modules/InstabugApi.m b/ios/Classes/Modules/InstabugApi.m index 11ea09354..ec9a9c54d 100644 --- a/ios/Classes/Modules/InstabugApi.m +++ b/ios/Classes/Modules/InstabugApi.m @@ -8,8 +8,9 @@ #define UIColorFromRGB(rgbValue) [UIColor colorWithRed:((float)((rgbValue & 0xFF0000) >> 16)) / 255.0 green:((float)((rgbValue & 0xFF00) >> 8)) / 255.0 blue:((float)(rgbValue & 0xFF)) / 255.0 alpha:((float)((rgbValue & 0xFF000000) >> 24)) / 255.0]; -extern void InitInstabugApi(id messenger) { +extern void InitInstabugApi(id _Nonnull messenger, PrivateViewApi * _Nonnull privateViewApi) { InstabugApi *api = [[InstabugApi alloc] init]; + api.privateViewApi = privateViewApi; InstabugHostApiSetup(messenger, api); } diff --git a/ios/Classes/Modules/PrivateViewApi.h b/ios/Classes/Modules/PrivateViewApi.h new file mode 100644 index 000000000..4839bde1a --- /dev/null +++ b/ios/Classes/Modules/PrivateViewApi.h @@ -0,0 +1,44 @@ +#import +#import +#import + + +// FlutterEngineRegistrar implements FlutterPluginRegistrar protocol +@interface FlutterEngineRegistrar : NSObject + +@property (nonatomic, strong) FlutterEngine *flutterEngine; + +@end + + +@interface PrivateViewApi : NSObject + +@property (nonatomic, strong) InstabugPrivateViewApi *flutterApi; +@property (nonatomic, strong) FlutterEngineRegistrar *flutterEngineRegistrar; + +// Corrected initializer signature +- (instancetype)initWithFlutterApi:(InstabugPrivateViewApi *)api + registrar:(FlutterEngineRegistrar *)registrar; + +// Corrected block syntax for `mask` method +- (void)mask:(UIImage *)screenshot + completion:(void (^)(UIImage *maskedImage))completion; +- (void)handlePrivateViewsResult:(NSArray *)rectangles + error:(FlutterError *)error + screenshot:(UIImage *)screenshot + completion:(void (^)(UIImage *))completion; +- (NSArray *)convertToRectangles:(NSArray *)rectangles; + +- (UIImage *)drawMaskedImage:(UIImage *)screenshot withPrivateViews:(NSArray *)privateViews; +- (CGPoint)getFlutterViewOrigin; + +- (void)logError:(FlutterError *)error; + +@end + +// Extern function to initialize PrivateViewApi +extern PrivateViewApi* InitPrivateViewApi( + id messenger, + FlutterEngineRegistrar *flutterEngineRegistrar +); + diff --git a/ios/Classes/Modules/PrivateViewApi.m b/ios/Classes/Modules/PrivateViewApi.m new file mode 100644 index 000000000..dc93d80ec --- /dev/null +++ b/ios/Classes/Modules/PrivateViewApi.m @@ -0,0 +1,107 @@ +#import "PrivateViewApi.h" + +extern PrivateViewApi* InitPrivateViewApi( + id messenger, + FlutterEngineRegistrar *flutterEngineRegistrar +) { + InstabugPrivateViewApi *flutterApi = [[InstabugPrivateViewApi alloc] initWithBinaryMessenger:messenger]; + return [[PrivateViewApi alloc] initWithFlutterApi:flutterApi registrar:flutterEngineRegistrar]; +} + +@implementation PrivateViewApi + +// Initializer with proper memory management +- (instancetype)initWithFlutterApi:(InstabugPrivateViewApi *)api + registrar:(FlutterEngineRegistrar *)registrar { + if ((self = [super init])) { + _flutterApi = api; + _flutterEngineRegistrar = registrar; + } + return self; +} + +- (void)mask:(UIImage *)screenshot + completion:(void (^)(UIImage *))completion { + + __weak typeof(self) weakSelf = self; + + [self.flutterApi getPrivateViewsWithCompletion:^(NSArray *rectangles, FlutterError *error) { + [weakSelf handlePrivateViewsResult:rectangles + error:error + screenshot:screenshot + completion:completion]; + }]; +} + +#pragma mark - Private Methods + +// Handle the result of fetching private views +- (void)handlePrivateViewsResult:(NSArray *)rectangles + error:(FlutterError *)error + screenshot:(UIImage *)screenshot + completion:(void (^)(UIImage *))completion { + if (error) { + [self logError:error]; + completion(screenshot); + return; + } + + NSArray *privateViews = [self convertToRectangles:rectangles]; + UIImage *maskedScreenshot = [self drawMaskedImage:screenshot withPrivateViews:privateViews]; + completion(maskedScreenshot); + +} + +// Convert the raw rectangles array into CGRect values +- (NSArray *)convertToRectangles:(NSArray *)rectangles { + NSMutableArray *privateViews = [NSMutableArray arrayWithCapacity:rectangles.count / 4]; + CGPoint flutterOrigin = [self getFlutterViewOrigin]; + + for (NSUInteger i = 0; i < rectangles.count; i += 4) { + CGFloat left = rectangles[i].doubleValue; + CGFloat top = rectangles[i + 1].doubleValue; + CGFloat right = rectangles[i + 2].doubleValue; + CGFloat bottom = rectangles[i + 3].doubleValue; + + CGRect rect = CGRectMake(flutterOrigin.x + left, + flutterOrigin.y + top, + right - left + 1, + bottom - top + 1); + [privateViews addObject:[NSValue valueWithCGRect:rect]]; + } + return privateViews; +} + +// Draw the masked image by filling private views with black rectangles +- (UIImage *)drawMaskedImage:(UIImage *)screenshot withPrivateViews:(NSArray *)privateViews { + UIGraphicsBeginImageContextWithOptions(screenshot.size, NO, 0); + CGContextRef context = UIGraphicsGetCurrentContext(); + + @try { + [screenshot drawAtPoint:CGPointZero]; + CGContextSetFillColorWithColor(context, UIColor.blackColor.CGColor); + + for (NSValue *value in privateViews) { + CGContextFillRect(context, value.CGRectValue); + } + + return UIGraphicsGetImageFromCurrentImageContext(); + } @finally { + UIGraphicsEndImageContext(); + } +} + +// Retrieve the origin point of the Flutter view +- (CGPoint)getFlutterViewOrigin { + UIView *flutterView = self.flutterEngineRegistrar.flutterEngine.viewController.view; + return flutterView ? flutterView.frame.origin : CGPointZero; +} + + +// Log error details +- (void)logError:(FlutterError *)error { + NSLog(@"IBG-Flutter: Error getting private views: %@", error.message); +} + + +@end From 00f00e70b0363109f42d563f16f7b1cbd8d1d386 Mon Sep 17 00:00:00 2001 From: Ahmed alaa Date: Sun, 27 Oct 2024 15:40:42 +0300 Subject: [PATCH 2/8] feat: private views in ios --- .../ios/InstabugTests/PrivateViewApiTests.m | 202 ++++++++++++++++++ example/ios/Podfile | 2 + example/ios/Podfile.lock | 8 +- example/ios/Runner.xcodeproj/project.pbxproj | 16 +- example/pubspec.lock | 36 ++-- ios/Classes/InstabugFlutterPlugin.m | 23 +- ios/Classes/Modules/InstabugApi.m | 9 + ios/Classes/Modules/PrivateViewApi.h | 14 +- ios/Classes/Modules/PrivateViewApi.m | 6 +- .../FlutterPluginRegistrar+FlutterEngine.h | 8 + .../FlutterPluginRegistrar+FlutterEngine.m | 13 ++ ios/Classes/Util/Instabug+CP.h | 8 + 12 files changed, 288 insertions(+), 57 deletions(-) create mode 100644 example/ios/InstabugTests/PrivateViewApiTests.m create mode 100644 ios/Classes/Util/FlutterPluginRegistrar+FlutterEngine.h create mode 100644 ios/Classes/Util/FlutterPluginRegistrar+FlutterEngine.m create mode 100644 ios/Classes/Util/Instabug+CP.h diff --git a/example/ios/InstabugTests/PrivateViewApiTests.m b/example/ios/InstabugTests/PrivateViewApiTests.m new file mode 100644 index 000000000..47efc19a9 --- /dev/null +++ b/example/ios/InstabugTests/PrivateViewApiTests.m @@ -0,0 +1,202 @@ +#import +#import +#import +#import +#import "FlutterPluginRegistrar+FlutterEngine.h" + + +@interface MockFlutterPluginRegistrar : NSObject +@end + +@implementation MockFlutterPluginRegistrar + +@end + + +@interface PrivateViewApiTests : XCTestCase +@property (nonatomic, strong) PrivateViewApi *api; +@property (nonatomic, strong) id mockFlutterApi; +@property (nonatomic, strong) id mockRegistrar; +@property (nonatomic, strong) id mockFlutterViewController; +@property (nonatomic, strong) id mockEngine; + +@end + +@implementation PrivateViewApiTests + +#pragma mark - Setup / Teardown + +- (void)setUp { + [super setUp]; + + + self.mockFlutterApi = OCMClassMock([InstabugPrivateViewApi class]); + + + MockFlutterPluginRegistrar *mockRegistrar = [[MockFlutterPluginRegistrar alloc] init]; + + self.mockRegistrar = OCMPartialMock(mockRegistrar); + + self.mockEngine = OCMClassMock([FlutterEngine class]); + OCMStub([self.mockRegistrar flutterEngine]).andReturn(self.mockEngine); + + self.mockFlutterViewController = OCMClassMock([UIViewController class]); + + OCMStub([self.mockEngine viewController]).andReturn(_mockFlutterViewController); + + self.api = OCMPartialMock([[PrivateViewApi alloc] initWithFlutterApi:self.mockFlutterApi registrar: self.mockRegistrar]); +} + +- (void)tearDown { + [self.mockFlutterApi stopMocking]; + [self.mockRegistrar stopMocking]; + [self.mockFlutterViewController stopMocking]; + [self.mockEngine stopMocking]; + + self.api = nil; + + [super tearDown]; +} + +#pragma mark - Tests + +- (void)testMask_Success { + XCTestExpectation *expectation = [self expectationWithDescription:@"Mask method success"]; + + CGSize imageSize = CGSizeMake(100, 100); // 100x100 pixels + + // Step 2: Create the image using UIGraphicsImageRenderer + UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:imageSize]; + + UIImage *screenshot = [renderer imageWithActions:^(UIGraphicsImageRendererContext * _Nonnull rendererContext) { + // Draw a red rectangle as an example + [[UIColor redColor] setFill]; + CGRect rect = CGRectMake(0, 0, imageSize.width, imageSize.height); + UIRectFill(rect); + }]; + + NSArray *rectangles = @[@10, @20, @30, @40]; + UIView *mockView = [[UIView alloc] initWithFrame:CGRectMake(10, 20, 30, 40)]; + + OCMStub([self.mockFlutterApi getPrivateViewsWithCompletion:([OCMArg invokeBlockWithArgs:rectangles, [NSNull null], nil])]); + + + + OCMStub([self.mockFlutterViewController view]).andReturn(mockView); + + + [self.api mask:screenshot completion:^(UIImage *result) { + XCTAssertNotNil(result, @"Masked image should be returned."); + [expectation fulfill]; + }]; + + [self waitForExpectationsWithTimeout:1 handler:nil]; +} + +- (void)testMask_Error { + XCTestExpectation *expectation = [self expectationWithDescription:@"Mask method with error"]; + + UIImage *screenshot = [UIImage new]; + FlutterError *error = [FlutterError errorWithCode:@"ERROR" message:@"Test error" details:nil]; + + OCMStub([self.mockFlutterApi getPrivateViewsWithCompletion:([OCMArg invokeBlockWithArgs:[NSNull null], error, nil])]); + + [self.api mask:screenshot completion:^(UIImage *result) { + XCTAssertEqual(result, screenshot, @"Original screenshot should be returned on error."); + [expectation fulfill]; + }]; + + [self waitForExpectationsWithTimeout:1 handler:nil]; +} + +- (void)testGetFlutterViewOrigin_ValidView { + UIView *mockView = [[UIView alloc] initWithFrame:CGRectMake(10, 20, 100, 100)]; + + OCMStub([self.mockFlutterViewController view]).andReturn(mockView); + + CGPoint origin = [self.api getFlutterViewOrigin]; + + XCTAssertEqual(origin.x, 10); + XCTAssertEqual(origin.y, 20); +} + +- (void)testGetFlutterViewOrigin_NilView { + + OCMStub([self.mockFlutterViewController view]).andReturn(nil); +// + CGPoint origin = [self.api getFlutterViewOrigin]; + + XCTAssertEqual(origin.x, 0); + XCTAssertEqual(origin.y, 0); +} + +- (void)testDrawMaskedImage { + CGSize size = CGSizeMake(100, 100); + UIGraphicsBeginImageContext(size); + UIImage *screenshot = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + + NSArray *privateViews = @[ + [NSValue valueWithCGRect:CGRectMake(10, 10, 20, 20)], + [NSValue valueWithCGRect:CGRectMake(30, 30, 10, 10)] + ]; + + UIImage *result = [self.api drawMaskedImage:screenshot withPrivateViews:privateViews]; + + XCTAssertNotNil(result); + XCTAssertEqual(result.size.width, 100); + XCTAssertEqual(result.size.height, 100); +} + +- (void)testConvertToRectangles_ValidInput { + NSArray *rectangles = @[@10, @20, @30, @40]; + UIView *mockView = [[UIView alloc] initWithFrame:CGRectMake(5, 5, 100, 100)]; + OCMStub([self.mockFlutterViewController view]).andReturn(mockView); + + + NSArray *converted = [self.api convertToRectangles:rectangles]; + + XCTAssertEqual(converted.count, 1); + XCTAssertTrue(CGRectEqualToRect([converted[0] CGRectValue], CGRectMake(15, 25, 21, 21))); +} + +- (void)testConcurrentMaskCalls { + XCTestExpectation *expectation = [self expectationWithDescription:@"Handle concurrent calls"]; + + CGSize imageSize = CGSizeMake(100, 100); // 100x100 pixels + + // Step 2: Create the image using UIGraphicsImageRenderer + UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:imageSize]; + + UIImage *screenshot = [renderer imageWithActions:^(UIGraphicsImageRendererContext * _Nonnull rendererContext) { + // Draw a red rectangle as an example + [[UIColor redColor] setFill]; + CGRect rect = CGRectMake(0, 0, imageSize.width, imageSize.height); + UIRectFill(rect); + }]; + + NSArray *rectangles = @[@10, @20, @30, @40]; + + + OCMStub([self.mockFlutterApi getPrivateViewsWithCompletion:([OCMArg invokeBlockWithArgs:rectangles, [NSNull null], nil])]); + + + dispatch_group_t group = dispatch_group_create(); + + for (int i = 0; i < 5; i++) { + dispatch_group_enter(group); + + [self.api mask:screenshot completion:^(UIImage *result) { + XCTAssertNotNil(result, @"Each call should return a valid image."); + dispatch_group_leave(group); + }]; + } + + dispatch_group_notify(group, dispatch_get_main_queue(), ^{ + [expectation fulfill]; + }); + + [self waitForExpectationsWithTimeout:2 handler:nil]; +} + +@end diff --git a/example/ios/Podfile b/example/ios/Podfile index cdffbc5db..22e035918 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -27,6 +27,8 @@ require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelpe flutter_ios_podfile_setup target 'Runner' do + pod 'Instabug', :podspec => 'https://ios-releases.instabug.com/custom/feature-flutter-private-views-base/13.4.2/Instabug.podspec' + use_frameworks! use_modular_headers! diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index b0ee46890..6bf8ca622 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -8,26 +8,28 @@ PODS: DEPENDENCIES: - Flutter (from `Flutter`) + - Instabug (from `https://ios-releases.instabug.com/custom/feature-flutter-private-views-base/13.4.2/Instabug.podspec`) - instabug_flutter (from `.symlinks/plugins/instabug_flutter/ios`) - OCMock (= 3.6) SPEC REPOS: trunk: - - Instabug - OCMock EXTERNAL SOURCES: Flutter: :path: Flutter + Instabug: + :podspec: https://ios-releases.instabug.com/custom/feature-flutter-private-views-base/13.4.2/Instabug.podspec instabug_flutter: :path: ".symlinks/plugins/instabug_flutter/ios" SPEC CHECKSUMS: Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - Instabug: 7a71890217b97b1e32dbca96661845396b66da2f + Instabug: 7aacd5099c11ce96bc49dda40eba0963c06acccc instabug_flutter: a2df87e3d4d9e410785e0b1ffef4bc64d1f4b787 OCMock: 5ea90566be239f179ba766fd9fbae5885040b992 -PODFILE CHECKSUM: 8f7552fd115ace1988c3db54a69e4a123c448f84 +PODFILE CHECKSUM: f2e19aef9f983becf80950af8e2d9c1b8f57e7a2 COCOAPODS: 1.14.3 diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index 858ba01e5..d75211080 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -16,7 +16,8 @@ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; - 9D381ECFBB01BD0E978EBDF2 /* Pods_InstabugTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 71679BEC094CFF3474195C2E /* Pods_InstabugTests.framework */; }; + BEF638212CC82C7C004D29E9 /* PrivateViewApiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = BEF638202CC82C7C004D29E9 /* PrivateViewApiTests.m */; }; + BEF638292CCA5E2B004D29E9 /* Pods_InstabugTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 71679BEC094CFF3474195C2E /* Pods_InstabugTests.framework */; }; CC080E112937B7DB0041170A /* InstabugApiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC080E102937B7DB0041170A /* InstabugApiTests.m */; }; CC198C61293E1A21007077C8 /* SurveysApiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC198C60293E1A21007077C8 /* SurveysApiTests.m */; }; CC359DB92937720C0067A924 /* ApmApiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC359DB82937720C0067A924 /* ApmApiTests.m */; }; @@ -26,6 +27,7 @@ CC9925D7293DFB03001FD3EE /* InstabugLogApiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC9925D6293DFB03001FD3EE /* InstabugLogApiTests.m */; }; CC9925D9293DFD7F001FD3EE /* RepliesApiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC9925D8293DFD7F001FD3EE /* RepliesApiTests.m */; }; CCADBDD8293CFED300AE5EB8 /* BugReportingApiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CCADBDD7293CFED300AE5EB8 /* BugReportingApiTests.m */; }; + EDD1293B2F5742BC05EDD9F6 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BEF6382E2CCA6D7D004D29E9 /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -78,6 +80,9 @@ B03C8370EEFE061BDDDA1DA1 /* Pods-InstabugUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InstabugUITests.debug.xcconfig"; path = "Target Support Files/Pods-InstabugUITests/Pods-InstabugUITests.debug.xcconfig"; sourceTree = ""; }; BA5633844585BB93FE7BCCE7 /* Pods-InstabugTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InstabugTests.profile.xcconfig"; path = "Target Support Files/Pods-InstabugTests/Pods-InstabugTests.profile.xcconfig"; sourceTree = ""; }; BE26C80C2BD55575009FECCF /* IBGCrashReporting+CP.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "IBGCrashReporting+CP.h"; sourceTree = ""; }; + BEF638202CC82C7C004D29E9 /* PrivateViewApiTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PrivateViewApiTests.m; sourceTree = ""; }; + BEF6382C2CCA6176004D29E9 /* instabug_flutter.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = instabug_flutter.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + BEF6382E2CCA6D7D004D29E9 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; BF9025BBD0A6FD7B193E903A /* Pods-InstabugTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InstabugTests.debug.xcconfig"; path = "Target Support Files/Pods-InstabugTests/Pods-InstabugTests.debug.xcconfig"; sourceTree = ""; }; C090017925D9A030006F3DAE /* InstabugTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = InstabugTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; C090017D25D9A031006F3DAE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -102,6 +107,7 @@ buildActionMask = 2147483647; files = ( 65C88E6E8EAE049E32FF2F52 /* Pods_Runner.framework in Frameworks */, + EDD1293B2F5742BC05EDD9F6 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -109,7 +115,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 9D381ECFBB01BD0E978EBDF2 /* Pods_InstabugTests.framework in Frameworks */, + BEF638292CCA5E2B004D29E9 /* Pods_InstabugTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -135,6 +141,8 @@ 54C1C903B090526284242B67 /* Frameworks */ = { isa = PBXGroup; children = ( + BEF6382E2CCA6D7D004D29E9 /* Pods_Runner.framework */, + BEF6382C2CCA6176004D29E9 /* instabug_flutter.framework */, 853739F5879F6E4272829F47 /* Pods_Runner.framework */, 71679BEC094CFF3474195C2E /* Pods_InstabugTests.framework */, F5446C0D3B2623D9BCC7CCE3 /* Pods_InstabugUITests.framework */, @@ -194,6 +202,7 @@ C090017A25D9A031006F3DAE /* InstabugTests */ = { isa = PBXGroup; children = ( + BEF638202CC82C7C004D29E9 /* PrivateViewApiTests.m */, CC198C60293E1A21007077C8 /* SurveysApiTests.m */, CC78720A2938D1C5008CB2A5 /* Util */, CC080E102937B7DB0041170A /* InstabugApiTests.m */, @@ -457,6 +466,7 @@ CC080E112937B7DB0041170A /* InstabugApiTests.m in Sources */, CC198C61293E1A21007077C8 /* SurveysApiTests.m in Sources */, CCADBDD8293CFED300AE5EB8 /* BugReportingApiTests.m in Sources */, + BEF638212CC82C7C004D29E9 /* PrivateViewApiTests.m in Sources */, CC9925D9293DFD7F001FD3EE /* RepliesApiTests.m in Sources */, 206286ED2ABD0A1F00925509 /* SessionReplayApiTests.m in Sources */, CC9925D2293DEB0B001FD3EE /* CrashReportingApiTests.m in Sources */, @@ -802,6 +812,7 @@ "@loader_path/Frameworks", ); MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.instabug.InstabugTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "InstabugTests/InstabugTests-Bridging-Header.h"; @@ -835,6 +846,7 @@ "@loader_path/Frameworks", ); MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.instabug.InstabugTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "InstabugTests/InstabugTests-Bridging-Header.h"; diff --git a/example/pubspec.lock b/example/pubspec.lock index 36235cfb8..3ed8cf5a2 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -128,26 +128,26 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" url: "https://pub.dev" source: hosted - version: "10.0.0" + version: "10.0.5" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.5" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.1" lints: dependency: transitive description: @@ -168,18 +168,18 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.15.0" path: dependency: transitive description: @@ -192,10 +192,10 @@ packages: dependency: transitive description: name: platform - sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" + sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" url: "https://pub.dev" source: hosted - version: "3.1.4" + version: "3.1.5" process: dependency: transitive description: @@ -261,10 +261,10 @@ packages: dependency: transitive description: name: test_api - sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" url: "https://pub.dev" source: hosted - version: "0.6.1" + version: "0.7.2" typed_data: dependency: transitive description: @@ -285,10 +285,10 @@ packages: dependency: transitive description: name: vm_service - sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" url: "https://pub.dev" source: hosted - version: "13.0.0" + version: "14.2.5" webdriver: dependency: transitive description: @@ -298,5 +298,5 @@ packages: source: hosted version: "3.0.3" sdks: - dart: ">=3.2.0-0 <4.0.0" - flutter: ">=2.10.0" + dart: ">=3.3.0 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/ios/Classes/InstabugFlutterPlugin.m b/ios/Classes/InstabugFlutterPlugin.m index 25fd48af0..7a5b0b905 100644 --- a/ios/Classes/InstabugFlutterPlugin.m +++ b/ios/Classes/InstabugFlutterPlugin.m @@ -10,41 +10,22 @@ #import "SessionReplayApi.h" #import "SurveysApi.h" #import "PrivateViewApi.h" +#import "Util/FlutterPluginRegistrar+FlutterEngine.h" @implementation InstabugFlutterPlugin + (void)registerWithRegistrar:(NSObject *)registrar { - FlutterEngineRegistrar *engineRegistrar = (FlutterEngineRegistrar *) registrar; - InitApmApi([registrar messenger]); InitBugReportingApi([registrar messenger]); InitCrashReportingApi([registrar messenger]); InitFeatureRequestsApi([registrar messenger]); - PrivateViewApi* privateViewApi = InitPrivateViewApi([registrar messenger],engineRegistrar); + PrivateViewApi* privateViewApi = InitPrivateViewApi([registrar messenger],registrar); InitInstabugApi([registrar messenger],privateViewApi); InitInstabugLogApi([registrar messenger]); InitRepliesApi([registrar messenger]); InitSessionReplayApi([registrar messenger]); InitSurveysApi([registrar messenger]); - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(8.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - UIView *flutterView = engineRegistrar.flutterEngine.viewController.view; - if (flutterView) { - // Begin image context with the view's bounds - UIGraphicsBeginImageContextWithOptions(flutterView.bounds.size, NO, 0.0); - [flutterView drawViewHierarchyInRect:flutterView.bounds afterScreenUpdates:YES]; - // Capture the image from the context - UIImage *screenshot = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - [privateViewApi mask:screenshot completion:^(UIImage * _Nonnull maskedImage) { - NSLog(@"1"); - NSLog(@"2"); - }]; - }else{ - NSLog(@"Error: Flutter view not found."); - } - - }); } diff --git a/ios/Classes/Modules/InstabugApi.m b/ios/Classes/Modules/InstabugApi.m index ec9a9c54d..5bb9bcb55 100644 --- a/ios/Classes/Modules/InstabugApi.m +++ b/ios/Classes/Modules/InstabugApi.m @@ -5,6 +5,7 @@ #import "IBGNetworkLogger+CP.h" #import "InstabugApi.h" #import "ArgsRegistry.h" +#import "../Util/Instabug+CP.h" #define UIColorFromRGB(rgbValue) [UIColor colorWithRed:((float)((rgbValue & 0xFF0000) >> 16)) / 255.0 green:((float)((rgbValue & 0xFF00) >> 8)) / 255.0 blue:((float)(rgbValue & 0xFF)) / 255.0 alpha:((float)((rgbValue & 0xFF000000) >> 24)) / 255.0]; @@ -54,6 +55,14 @@ - (void)initToken:(NSString *)token invocationEvents:(NSArray *)invo [Instabug setSdkDebugLogsLevel:resolvedLogLevel]; [Instabug startWithToken:token invocationEvents:resolvedEvents]; + + [Instabug setScreenshotMaskingHandler:^(UIImage * _Nonnull screenshot, void (^ _Nonnull completion)(UIImage * _Nullable)) { + [self.privateViewApi mask:screenshot completion:^(UIImage * _Nonnull maskedImage) { + if (maskedImage != nil) { + completion(maskedImage); + } + }]; + }]; } - (void)showWithError:(FlutterError *_Nullable *_Nonnull)error { diff --git a/ios/Classes/Modules/PrivateViewApi.h b/ios/Classes/Modules/PrivateViewApi.h index 4839bde1a..cd477e7a3 100644 --- a/ios/Classes/Modules/PrivateViewApi.h +++ b/ios/Classes/Modules/PrivateViewApi.h @@ -3,22 +3,14 @@ #import -// FlutterEngineRegistrar implements FlutterPluginRegistrar protocol -@interface FlutterEngineRegistrar : NSObject - -@property (nonatomic, strong) FlutterEngine *flutterEngine; - -@end - - @interface PrivateViewApi : NSObject @property (nonatomic, strong) InstabugPrivateViewApi *flutterApi; -@property (nonatomic, strong) FlutterEngineRegistrar *flutterEngineRegistrar; +@property (nonatomic, strong) NSObject * flutterEngineRegistrar; // Corrected initializer signature - (instancetype)initWithFlutterApi:(InstabugPrivateViewApi *)api - registrar:(FlutterEngineRegistrar *)registrar; + registrar:(NSObject *)registrar; // Corrected block syntax for `mask` method - (void)mask:(UIImage *)screenshot @@ -39,6 +31,6 @@ // Extern function to initialize PrivateViewApi extern PrivateViewApi* InitPrivateViewApi( id messenger, - FlutterEngineRegistrar *flutterEngineRegistrar + NSObject *flutterEngineRegistrar ); diff --git a/ios/Classes/Modules/PrivateViewApi.m b/ios/Classes/Modules/PrivateViewApi.m index dc93d80ec..6bf85e660 100644 --- a/ios/Classes/Modules/PrivateViewApi.m +++ b/ios/Classes/Modules/PrivateViewApi.m @@ -1,8 +1,9 @@ #import "PrivateViewApi.h" +#import "../Util/FlutterPluginRegistrar+FlutterEngine.h" extern PrivateViewApi* InitPrivateViewApi( id messenger, - FlutterEngineRegistrar *flutterEngineRegistrar + NSObject *flutterEngineRegistrar ) { InstabugPrivateViewApi *flutterApi = [[InstabugPrivateViewApi alloc] initWithBinaryMessenger:messenger]; return [[PrivateViewApi alloc] initWithFlutterApi:flutterApi registrar:flutterEngineRegistrar]; @@ -12,7 +13,7 @@ @implementation PrivateViewApi // Initializer with proper memory management - (instancetype)initWithFlutterApi:(InstabugPrivateViewApi *)api - registrar:(FlutterEngineRegistrar *)registrar { + registrar:( NSObject *) registrar { if ((self = [super init])) { _flutterApi = api; _flutterEngineRegistrar = registrar; @@ -54,6 +55,7 @@ - (void)handlePrivateViewsResult:(NSArray *)rectangles // Convert the raw rectangles array into CGRect values - (NSArray *)convertToRectangles:(NSArray *)rectangles { + NSMutableArray *privateViews = [NSMutableArray arrayWithCapacity:rectangles.count / 4]; CGPoint flutterOrigin = [self getFlutterViewOrigin]; diff --git a/ios/Classes/Util/FlutterPluginRegistrar+FlutterEngine.h b/ios/Classes/Util/FlutterPluginRegistrar+FlutterEngine.h new file mode 100644 index 000000000..ae31cbcd4 --- /dev/null +++ b/ios/Classes/Util/FlutterPluginRegistrar+FlutterEngine.h @@ -0,0 +1,8 @@ +#import + +@interface NSObject (FlutterEngineAccess) + +// Method to access FlutterEngine +- (FlutterEngine *)flutterEngine; + +@end diff --git a/ios/Classes/Util/FlutterPluginRegistrar+FlutterEngine.m b/ios/Classes/Util/FlutterPluginRegistrar+FlutterEngine.m new file mode 100644 index 000000000..4e5109d3a --- /dev/null +++ b/ios/Classes/Util/FlutterPluginRegistrar+FlutterEngine.m @@ -0,0 +1,13 @@ + +#import "FlutterPluginRegistrar+FlutterEngine.h" + +@implementation NSObject (FlutterEngineAccess) + +- (FlutterEngine *)flutterEngine { + if ([self respondsToSelector:@selector(engine)]) { + return (FlutterEngine *)[self performSelector:@selector(engine)]; + } + return nil; +} + +@end diff --git a/ios/Classes/Util/Instabug+CP.h b/ios/Classes/Util/Instabug+CP.h new file mode 100644 index 000000000..79e988c29 --- /dev/null +++ b/ios/Classes/Util/Instabug+CP.h @@ -0,0 +1,8 @@ + +#import + +@interface Instabug (CP) + ++ (void)setScreenshotMaskingHandler:(nullable void (^)(UIImage *, void (^)(UIImage *)))maskingHandler; + +@end From cd57e5892b7131514214136445e04f4980847281 Mon Sep 17 00:00:00 2001 From: Ahmed alaa Date: Sun, 27 Oct 2024 15:51:37 +0300 Subject: [PATCH 3/8] feat: private views in ios --- ios/Classes/InstabugFlutterPlugin.m | 1 - 1 file changed, 1 deletion(-) diff --git a/ios/Classes/InstabugFlutterPlugin.m b/ios/Classes/InstabugFlutterPlugin.m index 7a5b0b905..b1d7bd948 100644 --- a/ios/Classes/InstabugFlutterPlugin.m +++ b/ios/Classes/InstabugFlutterPlugin.m @@ -10,7 +10,6 @@ #import "SessionReplayApi.h" #import "SurveysApi.h" #import "PrivateViewApi.h" -#import "Util/FlutterPluginRegistrar+FlutterEngine.h" @implementation InstabugFlutterPlugin From 9212afca85f72faf76a075de4a1e8ff08907d43b Mon Sep 17 00:00:00 2001 From: Ahmed alaa Date: Wed, 30 Oct 2024 01:06:47 +0300 Subject: [PATCH 4/8] fix import symbol --- ios/Classes/Modules/PrivateViewApi.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/Classes/Modules/PrivateViewApi.h b/ios/Classes/Modules/PrivateViewApi.h index cd477e7a3..827f3e8c6 100644 --- a/ios/Classes/Modules/PrivateViewApi.h +++ b/ios/Classes/Modules/PrivateViewApi.h @@ -1,5 +1,5 @@ #import -#import +#import "InstabugPrivateViewPigeon.h" #import From ce1bf993e1cd2556aa131133081ca16e11acdb0f Mon Sep 17 00:00:00 2001 From: Ahmed alaa Date: Wed, 30 Oct 2024 01:44:37 +0300 Subject: [PATCH 5/8] fix import symbol --- ios/Classes/Modules/PrivateViewApi.m | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/ios/Classes/Modules/PrivateViewApi.m b/ios/Classes/Modules/PrivateViewApi.m index 6bf85e660..10c1d7c91 100644 --- a/ios/Classes/Modules/PrivateViewApi.m +++ b/ios/Classes/Modules/PrivateViewApi.m @@ -95,8 +95,15 @@ - (UIImage *)drawMaskedImage:(UIImage *)screenshot withPrivateViews:(NSArray Date: Wed, 30 Oct 2024 13:18:09 +0300 Subject: [PATCH 6/8] fix import symbol --- example/ios/InstabugTests/PrivateViewApiTests.m | 3 +++ 1 file changed, 3 insertions(+) diff --git a/example/ios/InstabugTests/PrivateViewApiTests.m b/example/ios/InstabugTests/PrivateViewApiTests.m index 47efc19a9..f327f0f04 100644 --- a/example/ios/InstabugTests/PrivateViewApiTests.m +++ b/example/ios/InstabugTests/PrivateViewApiTests.m @@ -113,6 +113,9 @@ - (void)testGetFlutterViewOrigin_ValidView { UIView *mockView = [[UIView alloc] initWithFrame:CGRectMake(10, 20, 100, 100)]; OCMStub([self.mockFlutterViewController view]).andReturn(mockView); + + UIWindow* testWindow = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; + [testWindow addSubview:mockView]; CGPoint origin = [self.api getFlutterViewOrigin]; From 6d03c55b861c5a9b8c8a7d236a58aa5401490fa8 Mon Sep 17 00:00:00 2001 From: Ahmed alaa Date: Wed, 30 Oct 2024 13:33:32 +0300 Subject: [PATCH 7/8] fix import symbol --- example/ios/InstabugTests/PrivateViewApiTests.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/example/ios/InstabugTests/PrivateViewApiTests.m b/example/ios/InstabugTests/PrivateViewApiTests.m index f327f0f04..ee4de7a92 100644 --- a/example/ios/InstabugTests/PrivateViewApiTests.m +++ b/example/ios/InstabugTests/PrivateViewApiTests.m @@ -160,7 +160,8 @@ - (void)testConvertToRectangles_ValidInput { NSArray *converted = [self.api convertToRectangles:rectangles]; XCTAssertEqual(converted.count, 1); - XCTAssertTrue(CGRectEqualToRect([converted[0] CGRectValue], CGRectMake(15, 25, 21, 21))); + CGRect rect = [converted[0] CGRectValue]; + XCTAssertTrue(CGRectEqualToRect(rect, CGRectMake(10, 20, 21, 21))); } - (void)testConcurrentMaskCalls { From fbe330a868042993e35f721c4d78599da3173daf Mon Sep 17 00:00:00 2001 From: ahmed alaa <154802748+ahmedAlaaInstabug@users.noreply.github.com> Date: Thu, 31 Oct 2024 14:37:33 +0300 Subject: [PATCH 8/8] Update PrivateViewApi.h --- ios/Classes/Modules/PrivateViewApi.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/ios/Classes/Modules/PrivateViewApi.h b/ios/Classes/Modules/PrivateViewApi.h index 827f3e8c6..86d38c056 100644 --- a/ios/Classes/Modules/PrivateViewApi.h +++ b/ios/Classes/Modules/PrivateViewApi.h @@ -8,11 +8,9 @@ @property (nonatomic, strong) InstabugPrivateViewApi *flutterApi; @property (nonatomic, strong) NSObject * flutterEngineRegistrar; -// Corrected initializer signature - (instancetype)initWithFlutterApi:(InstabugPrivateViewApi *)api registrar:(NSObject *)registrar; -// Corrected block syntax for `mask` method - (void)mask:(UIImage *)screenshot completion:(void (^)(UIImage *maskedImage))completion; - (void)handlePrivateViewsResult:(NSArray *)rectangles