diff --git a/.circleci/config.yml b/.circleci/config.yml index 325e0a6fb..2cc6ada92 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -334,6 +334,15 @@ jobs: - run: chmod +x packages/instabug_flutter_modular/release.sh - run: ./packages/instabug_flutter_modular/release.sh + release_instabug_private_views_plugin: + executor: flutter-executor + steps: + - advanced-checkout/shallow-checkout + - setup_flutter + - run: chmod +x packages/instabug_private_views/release.sh + - run: ./packages/instabug_private_views/release.sh + + release_instabug_flutter: macos: xcode: 15.2.0 @@ -454,6 +463,20 @@ workflows: filters: branches: only: master + - hold_release_instabug_private_views_plugin: + type: approval + requires: + - test_flutter-stable + filters: + branches: + only: master + - release_instabug_private_views_plugin: + requires: + - hold_release_instabug_private_views_plugin + - verify_pub + filters: + branches: + only: master - release_instabug_flutter: requires: - hold_release_instabug_flutter diff --git a/.gitignore b/.gitignore index 15f18ed8b..dfa529b28 100644 --- a/.gitignore +++ b/.gitignore @@ -63,6 +63,8 @@ android/gradlew.bat **/ios/**/DerivedData/ **/ios/**/Icon? **/ios/**/Pods/ +**/ios/**/Pods/ + **/ios/**/.symlinks/ **/ios/**/profile **/ios/**/xcuserdata diff --git a/analysis_options.yaml b/analysis_options.yaml index 4f1f095e4..75921910a 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -4,6 +4,7 @@ analyzer: exclude: - "packages/**/*.g.dart" - "packages/**/example/**" + - "packages/instabug_private_views/example-hybrid-ios-app/**/**" linter: diff --git a/packages/instabug_dio_interceptor/lib/instabug_dio_interceptor.dart b/packages/instabug_dio_interceptor/lib/instabug_dio_interceptor.dart index da386be84..8de42f9cf 100644 --- a/packages/instabug_dio_interceptor/lib/instabug_dio_interceptor.dart +++ b/packages/instabug_dio_interceptor/lib/instabug_dio_interceptor.dart @@ -76,7 +76,8 @@ class InstabugDioInterceptor extends Interceptor { var responseBodySize = 0; if (responseHeaders.containsKey('content-length')) { - responseBodySize = int.parse(responseHeaders['content-length'][0] ?? '0'); + // ignore: avoid_dynamic_calls + responseBodySize = int.parse((responseHeaders['content-length'][0]) ?? '0'); } else if (response.data != null) { responseBodySize = response.data.toString().length; } diff --git a/packages/instabug_flutter/android/build.gradle b/packages/instabug_flutter/android/build.gradle index ebaccfa51..52b303694 100644 --- a/packages/instabug_flutter/android/build.gradle +++ b/packages/instabug_flutter/android/build.gradle @@ -44,7 +44,8 @@ android { } dependencies { - api 'com.instabug.library:instabug:14.0.0' + api 'com.instabug.library:instabug:13.4.1.6295791-SNAPSHOT' + testImplementation 'junit:junit:4.13.2' testImplementation "org.mockito:mockito-inline:3.12.1" testImplementation "io.mockk:mockk:1.13.13" diff --git a/packages/instabug_flutter/android/src/main/java/com/instabug/flutter/InstabugFlutterPlugin.java b/packages/instabug_flutter/android/src/main/java/com/instabug/flutter/InstabugFlutterPlugin.java index cd1e018cb..b79d82dbb 100644 --- a/packages/instabug_flutter/android/src/main/java/com/instabug/flutter/InstabugFlutterPlugin.java +++ b/packages/instabug_flutter/android/src/main/java/com/instabug/flutter/InstabugFlutterPlugin.java @@ -19,6 +19,7 @@ import com.instabug.flutter.modules.RepliesApi; import com.instabug.flutter.modules.SessionReplayApi; import com.instabug.flutter.modules.SurveysApi; +import com.instabug.library.internal.crossplatform.InternalCore; import java.util.concurrent.Callable; @@ -35,6 +36,7 @@ public class InstabugFlutterPlugin implements FlutterPlugin, ActivityAware { @SuppressLint("StaticFieldLeak") private static Activity activity; + /** * Embedding v1 */ diff --git a/packages/instabug_flutter/android/src/main/java/com/instabug/flutter/modules/InstabugApi.java b/packages/instabug_flutter/android/src/main/java/com/instabug/flutter/modules/InstabugApi.java index 7a8549718..3c04ca90e 100644 --- a/packages/instabug_flutter/android/src/main/java/com/instabug/flutter/modules/InstabugApi.java +++ b/packages/instabug_flutter/android/src/main/java/com/instabug/flutter/modules/InstabugApi.java @@ -17,6 +17,7 @@ import com.instabug.library.internal.crossplatform.CoreFeaturesState; import com.instabug.library.internal.crossplatform.FeaturesStateListener; import com.instabug.library.internal.crossplatform.InternalCore; +import com.instabug.flutter.util.privateViews.ScreenshotCaptor; import com.instabug.library.Feature; import com.instabug.library.Instabug; import com.instabug.library.InstabugColorTheme; @@ -25,9 +26,11 @@ import com.instabug.library.Platform; import com.instabug.library.ReproConfigurations; import com.instabug.library.featuresflags.model.IBGFeatureFlag; +import com.instabug.library.internal.crossplatform.InternalCore; import com.instabug.library.internal.module.InstabugLocale; import com.instabug.library.invocation.InstabugInvocationEvent; import com.instabug.library.model.NetworkLog; +import com.instabug.library.screenshot.instacapture.ScreenshotRequest; import com.instabug.library.ui.onboarding.WelcomeMessage; import io.flutter.FlutterInjector; import io.flutter.embedding.engine.loader.FlutterLoader; @@ -492,4 +495,24 @@ public Map isW3CFeatureFlagsEnabled() { public void willRedirectToStore() { Instabug.willRedirectToStore(); } + + public static void setScreenshotCaptor(ScreenshotCaptor screenshotCaptor,InternalCore internalCore) { + internalCore._setScreenshotCaptor(new com.instabug.library.screenshot.ScreenshotCaptor() { + @Override + public void capture(@NonNull ScreenshotRequest screenshotRequest) { + screenshotCaptor.capture(new ScreenshotCaptor.CapturingCallback() { + @Override + public void onCapturingFailure(Throwable throwable) { + screenshotRequest.getListener().onCapturingFailure(throwable); + } + + @Override + public void onCapturingSuccess(Bitmap bitmap) { + screenshotRequest.getListener().onCapturingSuccess(bitmap); + } + }); + } + }); + } + } diff --git a/packages/instabug_flutter/android/src/main/java/com/instabug/flutter/util/privateViews/ScreenshotCaptor.java b/packages/instabug_flutter/android/src/main/java/com/instabug/flutter/util/privateViews/ScreenshotCaptor.java new file mode 100644 index 000000000..86eb40007 --- /dev/null +++ b/packages/instabug_flutter/android/src/main/java/com/instabug/flutter/util/privateViews/ScreenshotCaptor.java @@ -0,0 +1,13 @@ +package com.instabug.flutter.util.privateViews; + +import android.graphics.Bitmap; + +public interface ScreenshotCaptor { + public void capture(CapturingCallback listener); + + public interface CapturingCallback { + public void onCapturingFailure(Throwable throwable); + + public void onCapturingSuccess(Bitmap bitmap); + } +} \ No newline at end of file diff --git a/packages/instabug_flutter/android/src/test/java/com/instabug/flutter/InstabugApiTest.java b/packages/instabug_flutter/android/src/test/java/com/instabug/flutter/InstabugApiTest.java index 3d0b15ed4..afdc25d23 100644 --- a/packages/instabug_flutter/android/src/test/java/com/instabug/flutter/InstabugApiTest.java +++ b/packages/instabug_flutter/android/src/test/java/com/instabug/flutter/InstabugApiTest.java @@ -46,8 +46,10 @@ import com.instabug.library.internal.crossplatform.FeaturesStateListener; import com.instabug.library.internal.crossplatform.InternalCore; import com.instabug.library.featuresflags.model.IBGFeatureFlag; +import com.instabug.library.internal.crossplatform.InternalCore; import com.instabug.library.invocation.InstabugInvocationEvent; import com.instabug.library.model.NetworkLog; +import com.instabug.library.screenshot.ScreenshotCaptor; import com.instabug.library.ui.onboarding.WelcomeMessage; import com.instabug.survey.Surveys; import com.instabug.survey.callbacks.OnShowCallback; @@ -72,6 +74,7 @@ import java.util.concurrent.Callable; import io.flutter.plugin.common.BinaryMessenger; + import kotlin.jvm.functions.Function1; import org.mockito.Mockito; @@ -643,4 +646,12 @@ public void isW3CFeatureFlagsEnabled() { assertEquals(isW3cCaughtHeaderEnabled, flags.get("isW3cCaughtHeaderEnabled")); } + + @Test + public void testSetScreenshotCaptor() { + InternalCore internalCore = spy(InternalCore.INSTANCE); + + InstabugApi.setScreenshotCaptor(any(), internalCore); + verify(internalCore)._setScreenshotCaptor(any(ScreenshotCaptor.class)); + } } diff --git a/packages/instabug_flutter/example/android/app/build.gradle b/packages/instabug_flutter/example/android/app/build.gradle index 698a32c86..996b39242 100644 --- a/packages/instabug_flutter/example/android/app/build.gradle +++ b/packages/instabug_flutter/example/android/app/build.gradle @@ -28,6 +28,7 @@ apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { compileSdkVersion 34 ndkVersion flutter.ndkVersion + namespace = "com.instabug.flutter.example" compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 diff --git a/packages/instabug_flutter/example/android/build.gradle b/packages/instabug_flutter/example/android/build.gradle index 5d4b56b3b..e7ee2eaf8 100644 --- a/packages/instabug_flutter/example/android/build.gradle +++ b/packages/instabug_flutter/example/android/build.gradle @@ -24,6 +24,12 @@ subprojects { } subprojects { project.evaluationDependsOn(':app') + tasks.withType(Test) { + // Prevent tests in moduleA from running + if (project.name == 'video_player_android') { + exclude '**/*' + } + } } tasks.register("clean", Delete) { diff --git a/packages/instabug_flutter/example/assets/img.png b/packages/instabug_flutter/example/assets/img.png new file mode 100644 index 000000000..fff04770f Binary files /dev/null and b/packages/instabug_flutter/example/assets/img.png differ diff --git a/packages/instabug_flutter/example/ios/Podfile b/packages/instabug_flutter/example/ios/Podfile index cdffbc5db..0b5121016 100644 --- a/packages/instabug_flutter/example/ios/Podfile +++ b/packages/instabug_flutter/example/ios/Podfile @@ -29,7 +29,7 @@ flutter_ios_podfile_setup target 'Runner' do use_frameworks! use_modular_headers! - + pod 'Instabug', :podspec => 'https://ios-releases.instabug.com/custom/feature-flutter-private-views-base/14.0.0/Instabug.podspec' flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) end diff --git a/packages/instabug_flutter/example/ios/Podfile.lock b/packages/instabug_flutter/example/ios/Podfile.lock index 41499d6a0..0d7116a93 100644 --- a/packages/instabug_flutter/example/ios/Podfile.lock +++ b/packages/instabug_flutter/example/ios/Podfile.lock @@ -4,30 +4,46 @@ PODS: - instabug_flutter (14.0.0): - Flutter - Instabug (= 14.0.0) + - instabug_private_views (0.0.1): + - Flutter + - instabug_flutter - OCMock (3.6) + - video_player_avfoundation (0.0.1): + - Flutter + - FlutterMacOS DEPENDENCIES: - Flutter (from `Flutter`) + - Instabug (from `https://ios-releases.instabug.com/custom/feature-flutter-private-views-base/14.0.0/Instabug.podspec`) - instabug_flutter (from `.symlinks/plugins/instabug_flutter/ios`) + - instabug_private_views (from `.symlinks/plugins/instabug_private_views/ios`) - OCMock (= 3.6) + - video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`) SPEC REPOS: trunk: - - Instabug - OCMock EXTERNAL SOURCES: Flutter: :path: Flutter + Instabug: + :podspec: https://ios-releases.instabug.com/custom/feature-flutter-private-views-base/14.0.0/Instabug.podspec instabug_flutter: :path: ".symlinks/plugins/instabug_flutter/ios" + instabug_private_views: + :path: ".symlinks/plugins/instabug_private_views/ios" + video_player_avfoundation: + :path: ".symlinks/plugins/video_player_avfoundation/darwin" SPEC CHECKSUMS: Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - Instabug: a0beffc01658773e2fac549845782f8937707dc4 + Instabug: 9d2b06afbadfbd4630bc0116dc27d84360ed70b0 instabug_flutter: ff8ab5ff34a476b1d2d887478ec190cda962b973 + instabug_private_views: df53ff3f1cc842cb686d43e077099d3b36426a7f OCMock: 5ea90566be239f179ba766fd9fbae5885040b992 + video_player_avfoundation: 7c6c11d8470e1675df7397027218274b6d2360b3 -PODFILE CHECKSUM: 8f7552fd115ace1988c3db54a69e4a123c448f84 +PODFILE CHECKSUM: 32bd1b5b0a93d31b74cc581a86b5fa93c1cc923f COCOAPODS: 1.14.3 diff --git a/packages/instabug_flutter/example/lib/main.dart b/packages/instabug_flutter/example/lib/main.dart index cda7ff3ec..d52a685cc 100644 --- a/packages/instabug_flutter/example/lib/main.dart +++ b/packages/instabug_flutter/example/lib/main.dart @@ -5,9 +5,11 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:instabug_flutter/instabug_flutter.dart'; +import 'package:instabug_flutter_example/src/screens/private_view_page.dart'; import 'package:instabug_http_client/instabug_http_client.dart'; import 'package:instabug_flutter_example/src/app_routes.dart'; import 'package:instabug_flutter_example/src/widget/nested_view.dart'; +import 'package:instabug_private_views/instabug_private_view.dart'; import 'src/native/instabug_flutter_example_method_channel.dart'; import 'src/widget/instabug_button.dart'; @@ -56,6 +58,8 @@ void main() { Zone.current.handleUncaughtError(details.exception, details.stack!); }; + enableInstabugMaskingPrivateViews(); + runApp(const MyApp()); }, CrashReporting.reportCrash, diff --git a/packages/instabug_flutter/example/lib/src/screens/my_home_page.dart b/packages/instabug_flutter/example/lib/src/screens/my_home_page.dart index 404d79cdd..4c4b681a3 100644 --- a/packages/instabug_flutter/example/lib/src/screens/my_home_page.dart +++ b/packages/instabug_flutter/example/lib/src/screens/my_home_page.dart @@ -35,6 +35,10 @@ class _MyHomePageState extends State { BugReporting.setInvocationEvents([InvocationEvent.floatingButton]); } + void disableInstabug() { + Instabug.setEnabled(false); + } + void setOnDismissCallback() { BugReporting.setOnDismissCallback((dismissType, reportType) { showDialog( @@ -161,6 +165,16 @@ class _MyHomePageState extends State { ); } + void _navigateToPrivateViewPage() { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const PrivateViewPage(), + settings: const RouteSettings(name: ComplexPage.screenName), + ), + ); + } + @override Widget build(BuildContext context) { return Page( @@ -178,6 +192,10 @@ class _MyHomePageState extends State { onPressed: restartInstabug, text: 'Restart Instabug', ), + InstabugButton( + onPressed: disableInstabug, + text: 'Disable Instabug', + ), const SectionTitle('Primary Color'), InstabugTextField( controller: primaryColorController, @@ -306,6 +324,10 @@ class _MyHomePageState extends State { onPressed: _navigateToComplex, text: 'Complex', ), + InstabugButton( + onPressed: _navigateToPrivateViewPage, + text: 'Private views', + ), const SectionTitle('Sessions Replay'), InstabugButton( onPressed: getCurrentSessionReplaylink, diff --git a/packages/instabug_flutter/example/lib/src/screens/private_view_page.dart b/packages/instabug_flutter/example/lib/src/screens/private_view_page.dart new file mode 100644 index 000000000..2e4940399 --- /dev/null +++ b/packages/instabug_flutter/example/lib/src/screens/private_view_page.dart @@ -0,0 +1,154 @@ +import 'package:flutter/material.dart'; +import 'package:instabug_flutter/instabug_flutter.dart'; +import 'package:instabug_private_views/instabug_private_view.dart'; +import 'package:video_player/video_player.dart'; + +class PrivateViewPage extends StatefulWidget { + const PrivateViewPage({Key? key}) : super(key: key); + + @override + _PrivateViewPageState createState() => _PrivateViewPageState(); +} + +class _PrivateViewPageState extends State { + late VideoPlayerController _controller; + + @override + void initState() { + super.initState(); + _controller = VideoPlayerController.networkUrl( + Uri.parse( + 'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4'), + )..initialize().then((_) { + setState(() {}); + }); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return MaterialApp( + debugShowCheckedModeBanner: false, + home: Scaffold( + appBar: AppBar(title: const Text("Private Views page")), + body: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const SizedBox(height: 16), + InstabugPrivateView( + child: const Text( + 'Private TextView', + style: TextStyle(fontSize: 18), + textAlign: TextAlign.center, + ), + ), + const SizedBox(height: 16), + InstabugPrivateView( + child: ElevatedButton( + onPressed: () { + const snackBar = SnackBar( + content: Text('Hello, you clicked on a private button'), + ); + ScaffoldMessenger.of(context).showSnackBar(snackBar); + }, + child: const Text('I am a private button'), + ), + ), + const SizedBox(height: 16), + InstabugPrivateView( + child: Image.asset( + 'assets/img.png', + // Add this image to your assets folder + height: 100, + ), + ), + const SizedBox(height: 33), + InstabugPrivateView( + child: const TextField( + obscureText: true, + decoration: InputDecoration( + hintText: 'password', + labelText: 'Password', + border: OutlineInputBorder(), + ), + ), + ), + const SizedBox(height: 16), + const TextField( + keyboardType: TextInputType.emailAddress, + decoration: InputDecoration( + hintText: 'Email', + labelText: 'Email', + border: OutlineInputBorder(), + ), + ), + const SizedBox(height: 16), + Column( + children: [ + InstabugPrivateView( + child: const Text( + 'Private TextView in column', + style: TextStyle(fontSize: 18), + textAlign: TextAlign.center, + ), + ), + SizedBox( + height: 10, + ), + const Text( + 'TextView in column', + style: TextStyle(fontSize: 18), + textAlign: TextAlign.center, + ), + ], + ), + const SizedBox(height: 24), + InstabugPrivateView( + child: Container( + height: 300, + child: _controller.value.isInitialized + ? AspectRatio( + aspectRatio: _controller.value.aspectRatio, + child: VideoPlayer(_controller), + ) + : const Center(child: CircularProgressIndicator()), + ), + ), + const SizedBox(height: 24), + const SizedBox(height: 24), + const SizedBox(height: 24), + SizedBox( + height: 200, + child: CustomScrollView( + slivers: [ + InstabugSliverPrivateView( + sliver: SliverToBoxAdapter( + child: Container( + color: Colors.red, + child: const Text( + "Private Sliver Widget", + style: TextStyle(fontSize: 18), + textAlign: TextAlign.center, + ), + ), + ), + ) + ], + ), + ) + ], + ), + ), + ), + ), + ); + } +} diff --git a/packages/instabug_flutter/example/pubspec.lock b/packages/instabug_flutter/example/pubspec.lock index 8f96d8a1e..ffb81dbe5 100644 --- a/packages/instabug_flutter/example/pubspec.lock +++ b/packages/instabug_flutter/example/pubspec.lock @@ -41,6 +41,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.18.0" + csslib: + dependency: transitive + description: + name: csslib + sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" + url: "https://pub.dev" + source: hosted + version: "1.0.2" fake_async: dependency: transitive description: @@ -80,11 +88,24 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" fuchsia_remote_debug_protocol: dependency: transitive description: flutter source: sdk version: "0.0.0" + html: + dependency: transitive + description: + name: html + sha256: "1fc58edeaec4307368c60d59b7e15b9d658b57d7f3125098b6294153c75337ec" + url: "https://pub.dev" + source: hosted + version: "0.15.5" http: dependency: "direct main" description: @@ -115,6 +136,13 @@ packages: relative: true source: path version: "2.4.0" + instabug_private_views: + dependency: "direct main" + description: + path: "../../instabug_private_views" + relative: true + source: path + version: "1.0.1" leak_tracker: dependency: transitive description: @@ -187,6 +215,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.5" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" process: dependency: transitive description: @@ -272,6 +308,46 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + video_player: + dependency: "direct main" + description: + name: video_player + sha256: "4a8c3492d734f7c39c2588a3206707a05ee80cef52e8c7f3b2078d430c84bc17" + url: "https://pub.dev" + source: hosted + version: "2.9.2" + video_player_android: + dependency: transitive + description: + name: video_player_android + sha256: "391e092ba4abe2f93b3e625bd6b6a6ec7d7414279462c1c0ee42b5ab8d0a0898" + url: "https://pub.dev" + source: hosted + version: "2.7.16" + video_player_avfoundation: + dependency: transitive + description: + name: video_player_avfoundation + sha256: cd5ab8a8bc0eab65ab0cea40304097edc46da574c8c1ecdee96f28cd8ef3792f + url: "https://pub.dev" + source: hosted + version: "2.6.2" + video_player_platform_interface: + dependency: transitive + description: + name: video_player_platform_interface + sha256: "229d7642ccd9f3dc4aba169609dd6b5f3f443bb4cc15b82f7785fcada5af9bbb" + url: "https://pub.dev" + source: hosted + version: "6.2.3" + video_player_web: + dependency: transitive + description: + name: video_player_web + sha256: "881b375a934d8ebf868c7fb1423b2bfaa393a0a265fa3f733079a86536064a10" + url: "https://pub.dev" + source: hosted + version: "2.3.3" vm_service: dependency: transitive description: @@ -280,6 +356,14 @@ packages: url: "https://pub.dev" source: hosted version: "14.2.5" + web: + dependency: transitive + description: + name: web + sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb + url: "https://pub.dev" + source: hosted + version: "1.1.0" webdriver: dependency: transitive description: @@ -290,4 +374,4 @@ packages: version: "3.0.3" sdks: dart: ">=3.5.0 <4.0.0" - flutter: ">=3.18.0-18.0.pre.54" + flutter: ">=3.24.0" diff --git a/packages/instabug_flutter/example/pubspec.yaml b/packages/instabug_flutter/example/pubspec.yaml index 6bb0e3e71..25fa60592 100644 --- a/packages/instabug_flutter/example/pubspec.yaml +++ b/packages/instabug_flutter/example/pubspec.yaml @@ -25,9 +25,11 @@ dependencies: sdk: flutter http: ^0.13.0 instabug_flutter: - path: '../../packages/Instabug-Flutter' + path: '../../Instabug-Flutter' instabug_http_client: ^2.4.0 - + video_player: + instabug_private_views: + path: '../../instabug_private_views' dev_dependencies: flutter_driver: sdk: flutter @@ -50,9 +52,8 @@ flutter: uses-material-design: true # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg + assets: + - assets/img.png # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware. diff --git a/packages/instabug_flutter/ios/Classes/Modules/InstabugApi.h b/packages/instabug_flutter/ios/Classes/Modules/InstabugApi.h index 7030617c9..777013ee0 100644 --- a/packages/instabug_flutter/ios/Classes/Modules/InstabugApi.h +++ b/packages/instabug_flutter/ios/Classes/Modules/InstabugApi.h @@ -6,5 +6,6 @@ extern void InitInstabugApi(id messenger); - (UIImage *)getImageForAsset:(NSString *)assetName; - (UIFont *)getFontForAsset:(NSString *)assetName error:(FlutterError *_Nullable *_Nonnull)error; ++ (void)setScreenshotMaskingHandler:(nullable void (^)(UIImage *_Nonnull, void (^_Nonnull)(UIImage *_Nonnull)))maskingHandler; @end diff --git a/packages/instabug_flutter/ios/Classes/Modules/InstabugApi.m b/packages/instabug_flutter/ios/Classes/Modules/InstabugApi.m index 8cdd336d1..58f2725e1 100644 --- a/packages/instabug_flutter/ios/Classes/Modules/InstabugApi.m +++ b/packages/instabug_flutter/ios/Classes/Modules/InstabugApi.m @@ -7,6 +7,7 @@ #import "ArgsRegistry.h" #import "../Util/IBGAPM+PrivateAPIs.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]; extern void InitInstabugApi(id messenger) { @@ -393,4 +394,8 @@ - (void)registerFeatureFlagChangeListenerWithError:(FlutterError * _Nullable __a } ++ (void)setScreenshotMaskingHandler:(nullable void (^)(UIImage * _Nonnull __strong, void (^ _Nonnull __strong)(UIImage * _Nonnull __strong)))maskingHandler { + [Instabug setScreenshotMaskingHandler:maskingHandler]; +} + @end diff --git a/packages/instabug_flutter/ios/Classes/Util/ArgsRegistry.m b/packages/instabug_flutter/ios/Classes/Util/ArgsRegistry.m index b8cdaa21a..47de80d13 100644 --- a/packages/instabug_flutter/ios/Classes/Util/ArgsRegistry.m +++ b/packages/instabug_flutter/ios/Classes/Util/ArgsRegistry.m @@ -1,5 +1,4 @@ #import "ArgsRegistry.h" - @implementation ArgsRegistry + (ArgsDictionary *)sdkLogLevels { diff --git a/packages/instabug_flutter/ios/Classes/Util/Instabug+CP.h b/packages/instabug_flutter/ios/Classes/Util/Instabug+CP.h new file mode 100644 index 000000000..79e988c29 --- /dev/null +++ b/packages/instabug_flutter/ios/Classes/Util/Instabug+CP.h @@ -0,0 +1,8 @@ + +#import + +@interface Instabug (CP) + ++ (void)setScreenshotMaskingHandler:(nullable void (^)(UIImage *, void (^)(UIImage *)))maskingHandler; + +@end diff --git a/packages/instabug_flutter/ios/instabug_flutter.podspec b/packages/instabug_flutter/ios/instabug_flutter.podspec index 4d610ddbf..e48169fb9 100644 --- a/packages/instabug_flutter/ios/instabug_flutter.podspec +++ b/packages/instabug_flutter/ios/instabug_flutter.podspec @@ -18,5 +18,7 @@ Pod::Spec.new do |s| s.dependency 'Flutter' s.dependency 'Instabug', '14.0.0' + + end diff --git a/packages/instabug_flutter_modular/lib/src/instabug_modular_manager.dart b/packages/instabug_flutter_modular/lib/src/instabug_modular_manager.dart index 12fb81e21..d1f8c8ea7 100644 --- a/packages/instabug_flutter_modular/lib/src/instabug_modular_manager.dart +++ b/packages/instabug_flutter_modular/lib/src/instabug_modular_manager.dart @@ -1,9 +1,8 @@ import 'package:flutter_modular/flutter_modular.dart'; import 'package:instabug_flutter/instabug_flutter.dart'; +import 'package:instabug_flutter_modular/src/instabug_module.dart'; import 'package:meta/meta.dart'; -import './instabug_module.dart'; - class InstabugModularManager { InstabugModularManager._(); @@ -44,7 +43,7 @@ class InstabugModularManager { if (route is ModuleRoute && route.context is Module && wrapModules) { final module = InstabugModule( - route.context as Module, + route.context! as Module, path: fullPath, ); diff --git a/packages/instabug_flutter_modular/lib/src/instabug_module.dart b/packages/instabug_flutter_modular/lib/src/instabug_module.dart index fb78b6273..dc56fb171 100644 --- a/packages/instabug_flutter_modular/lib/src/instabug_module.dart +++ b/packages/instabug_flutter_modular/lib/src/instabug_module.dart @@ -1,5 +1,5 @@ import 'package:flutter_modular/flutter_modular.dart'; -import 'instabug_modular_manager.dart'; +import 'package:instabug_flutter_modular/src/instabug_modular_manager.dart'; class InstabugModule extends Module { final Module module; diff --git a/packages/instabug_flutter_modular/pubspec.yaml b/packages/instabug_flutter_modular/pubspec.yaml index 7768d5fbd..e75afbebc 100644 --- a/packages/instabug_flutter_modular/pubspec.yaml +++ b/packages/instabug_flutter_modular/pubspec.yaml @@ -10,16 +10,15 @@ environment: dependencies: flutter: sdk: flutter - meta: ^1.3.0 flutter_modular: '>=5.0.0 <6.0.0' instabug_flutter: '>=13.2.0 <14.0.0' - + meta: ^1.3.0 dev_dependencies: build_runner: ^2.0.3 + flutter_lints: ^2.0.0 flutter_test: sdk: flutter - flutter_lints: ^2.0.0 # mockito v5.2.0 is needed for running Flutter 2 tests on CI mockito: '>=5.2.0 <=5.4.4' # A specific version isn't specified as we want to use the version used in flutter_modular diff --git a/packages/instabug_flutter_modular/test/src/instabug_modular_manager_test.dart b/packages/instabug_flutter_modular/test/src/instabug_modular_manager_test.dart index 7e97e1eef..85b4aa011 100644 --- a/packages/instabug_flutter_modular/test/src/instabug_modular_manager_test.dart +++ b/packages/instabug_flutter_modular/test/src/instabug_modular_manager_test.dart @@ -1,13 +1,13 @@ +// ignore_for_file: avoid_dynamic_calls + import 'package:flutter/widgets.dart'; import 'package:flutter_modular/flutter_modular.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:instabug_flutter/instabug_flutter.dart'; import 'package:instabug_flutter_modular/instabug_flutter_modular.dart'; import 'package:instabug_flutter_modular/src/instabug_modular_manager.dart'; -import 'package:modular_core/modular_core.dart'; - -import 'package:instabug_flutter/instabug_flutter.dart'; - import 'package:mockito/annotations.dart'; +import 'package:modular_core/modular_core.dart'; import 'instabug_modular_manager_test.mocks.dart'; @@ -113,7 +113,7 @@ void main() { // Arrange final customTransition = MockCustomTransition(); const duration = Duration.zero; - final List guards = []; + final guards = []; const transition = TransitionType.downToUp; final homeRoute = ChildRoute( @@ -139,7 +139,7 @@ void main() { final wrappedRoutes = InstabugModularManager.instance.wrapRoutes(routes) as List; - for (var element in wrappedRoutes) { + for (final element in wrappedRoutes) { final widget = element.child(mockContext, mockArgs); // Assert diff --git a/packages/instabug_http_client/lib/instabug_http_client.dart b/packages/instabug_http_client/lib/instabug_http_client.dart index 25ec7a2bb..6f978f850 100644 --- a/packages/instabug_http_client/lib/instabug_http_client.dart +++ b/packages/instabug_http_client/lib/instabug_http_client.dart @@ -30,10 +30,10 @@ class InstabugHttpClient extends InstabugHttpLogger implements http.Client { @override Future delete(Uri url, - {Map? headers, Object? body, Encoding? encoding}) async { - final DateTime startTime = DateTime.now(); - final Map requestHeader = headers ?? {}; - final W3CHeader? w3cHeader = await getW3cHeader(requestHeader, startTime); + {Map? headers, Object? body, Encoding? encoding,}) async { + final startTime = DateTime.now(); + final requestHeader = headers ?? {}; + final w3cHeader = await getW3cHeader(requestHeader, startTime); return client .delete(url, body: body, headers: requestHeader, encoding: encoding) .then((http.Response response) { @@ -43,8 +43,8 @@ class InstabugHttpClient extends InstabugHttpLogger implements http.Client { } Future getW3cHeader(Map requestHeader, DateTime startTime) async { - final W3CHeader? w3cHeader = await _networklogger.getW3CHeader( - requestHeader, startTime.millisecondsSinceEpoch); + final w3cHeader = await _networklogger.getW3CHeader( + requestHeader, startTime.millisecondsSinceEpoch,); if (w3cHeader?.isW3cHeaderFound == false && w3cHeader?.w3CGeneratedHeader != null) { requestHeader['traceparent'] = w3cHeader!.w3CGeneratedHeader!; @@ -54,9 +54,9 @@ class InstabugHttpClient extends InstabugHttpLogger implements http.Client { @override Future get(Uri url, {Map? headers}) async { - final DateTime startTime = DateTime.now(); - final Map requestHeader = headers ?? {}; - final W3CHeader? w3cHeader = await getW3cHeader(requestHeader, startTime); + final startTime = DateTime.now(); + final requestHeader = headers ?? {}; + final w3cHeader = await getW3cHeader(requestHeader, startTime); return client .get(url, headers: requestHeader) .then((http.Response response) { @@ -67,9 +67,9 @@ class InstabugHttpClient extends InstabugHttpLogger implements http.Client { @override Future head(Uri url, {Map? headers}) async { - final DateTime startTime = DateTime.now(); - final Map requestHeader = headers ?? {}; - final W3CHeader? w3cHeader = await getW3cHeader(requestHeader, startTime); + final startTime = DateTime.now(); + final requestHeader = headers ?? {}; + final w3cHeader = await getW3cHeader(requestHeader, startTime); return client .head(url, headers: requestHeader) .then((http.Response response) { @@ -80,10 +80,10 @@ class InstabugHttpClient extends InstabugHttpLogger implements http.Client { @override Future patch(Uri url, - {Map? headers, Object? body, Encoding? encoding}) async { - final DateTime startTime = DateTime.now(); - final Map requestHeader = headers ?? {}; - final W3CHeader? w3cHeader = await getW3cHeader(requestHeader, startTime); + {Map? headers, Object? body, Encoding? encoding,}) async { + final startTime = DateTime.now(); + final requestHeader = headers ?? {}; + final w3cHeader = await getW3cHeader(requestHeader, startTime); return client .patch(url, headers: requestHeader, body: body, encoding: encoding) .then((http.Response response) { @@ -94,10 +94,10 @@ class InstabugHttpClient extends InstabugHttpLogger implements http.Client { @override Future post(Uri url, - {Map? headers, Object? body, Encoding? encoding}) async { - final DateTime startTime = DateTime.now(); - final Map requestHeader = headers ?? {}; - final W3CHeader? w3cHeader = await getW3cHeader(requestHeader, startTime); + {Map? headers, Object? body, Encoding? encoding,}) async { + final startTime = DateTime.now(); + final requestHeader = headers ?? {}; + final w3cHeader = await getW3cHeader(requestHeader, startTime); return client .post(url, headers: requestHeader, body: body, encoding: encoding) .then((http.Response response) { @@ -108,10 +108,10 @@ class InstabugHttpClient extends InstabugHttpLogger implements http.Client { @override Future put(Uri url, - {Map? headers, Object? body, Encoding? encoding}) async { - final DateTime startTime = DateTime.now(); - final Map requestHeader = headers ?? {}; - final W3CHeader? w3cHeader = await getW3cHeader(requestHeader, startTime); + {Map? headers, Object? body, Encoding? encoding,}) async { + final startTime = DateTime.now(); + final requestHeader = headers ?? {}; + final w3cHeader = await getW3cHeader(requestHeader, startTime); return client .put(url, headers: requestHeader, body: body, encoding: encoding) .then((http.Response response) { @@ -130,9 +130,9 @@ class InstabugHttpClient extends InstabugHttpLogger implements http.Client { @override Future send(http.BaseRequest request) async { - final DateTime startTime = DateTime.now(); - final Map requestHeader = request.headers; - final W3CHeader? w3cHeader = await getW3cHeader(requestHeader, startTime); + final startTime = DateTime.now(); + final requestHeader = request.headers; + final w3cHeader = await getW3cHeader(requestHeader, startTime); return client.send(request).then((http.StreamedResponse streamedResponse) => http.Response.fromStream(streamedResponse) .then((http.Response response) { @@ -148,6 +148,6 @@ class InstabugHttpClient extends InstabugHttpLogger implements http.Client { persistentConnection: response.persistentConnection, reasonPhrase: response.reasonPhrase, ); - })); + }),); } } diff --git a/packages/instabug_http_client/lib/instabug_http_logger.dart b/packages/instabug_http_client/lib/instabug_http_logger.dart index 13bfce62c..b2b324ed8 100644 --- a/packages/instabug_http_client/lib/instabug_http_logger.dart +++ b/packages/instabug_http_client/lib/instabug_http_logger.dart @@ -5,47 +5,47 @@ import 'package:instabug_flutter/instabug_flutter.dart'; class InstabugHttpLogger { void onLogger(http.Response response, {DateTime? startTime,W3CHeader? w3CHeader}) { - final NetworkLogger networkLogger = NetworkLogger(); + final networkLogger = NetworkLogger(); - final Map requestHeaders = {}; + final requestHeaders = {}; response.request?.headers.forEach((String header, dynamic value) { requestHeaders[header] = value; }); - final http.BaseRequest? request = response.request; + final request = response.request; if (request == null) { return; } - final String requestBody = request is http.MultipartRequest + final requestBody = request is http.MultipartRequest ? json.encode(request.fields) : request is http.Request ? request.body : ''; - final NetworkData requestData = NetworkData( + final requestData = NetworkData( startTime: startTime!, method: request.method, url: request.url.toString(), requestHeaders: requestHeaders, requestBody: requestBody, - w3cHeader: w3CHeader + w3cHeader: w3CHeader, ); - final DateTime endTime = DateTime.now(); + final endTime = DateTime.now(); - final Map responseHeaders = {}; + final responseHeaders = {}; response.headers.forEach((String header, dynamic value) { responseHeaders[header] = value; }); - int requestBodySize = 0; + var requestBodySize = 0; if (requestHeaders.containsKey('content-length')) { requestBodySize = int.parse(responseHeaders['content-length'] ?? '0'); } else { requestBodySize = requestBody.length; } - int responseBodySize = 0; + var responseBodySize = 0; if (responseHeaders.containsKey('content-length')) { responseBodySize = int.parse(responseHeaders['content-length'] ?? '0'); } else { @@ -65,6 +65,6 @@ class InstabugHttpLogger { requestContentType: request.headers.containsKey('content-type') ? request.headers['content-type'] : '', - )); + ),); } } diff --git a/packages/instabug_http_client/test/instabug_http_client_test.dart b/packages/instabug_http_client/test/instabug_http_client_test.dart index 1448982a2..ae6d74a53 100644 --- a/packages/instabug_http_client/test/instabug_http_client_test.dart +++ b/packages/instabug_http_client/test/instabug_http_client_test.dart @@ -23,7 +23,7 @@ import 'instabug_http_client_test.mocks.dart'; ]) Future main() async { TestWidgetsFlutterBinding.ensureInitialized(); - final MockInstabugHostApi mHost = MockInstabugHostApi(); + final mHost = MockInstabugHostApi(); setUpAll(() { Instabug.$setHostApi(mHost); @@ -32,15 +32,15 @@ Future main() async { 'isW3cCaughtHeaderEnabled': true, 'isW3cExternalGeneratedHeaderEnabled': false, 'isW3cExternalTraceIDEnabled': true, - })); + }),); }); - const Map fakeResponse = { + const fakeResponse = { 'id': '123', 'activationCode': '111111', }; late Uri url; - final http.Response mockedResponse = + final mockedResponse = http.Response(json.encode(fakeResponse), 200); late InstabugHttpClient instabugHttpClient; @@ -55,106 +55,106 @@ Future main() async { test('expect instabug http client GET to return response', () async { when(instabugHttpClient.client.get(url,headers: anyNamed('headers'))) .thenAnswer((_) async => mockedResponse); - final http.Response result = await instabugHttpClient.get(url); + final result = await instabugHttpClient.get(url); expect(result, isInstanceOf()); expect(result, mockedResponse); verify(instabugHttpClient.logger - .onLogger(mockedResponse, startTime: anyNamed('startTime'))) + .onLogger(mockedResponse, startTime: anyNamed('startTime')),) .called(1); }); test('expect instabug http client HEAD to return response', () async { when(instabugHttpClient.client.head(url,headers: anyNamed('headers'))) .thenAnswer((_) async => mockedResponse); - final http.Response result = await instabugHttpClient.head(url); + final result = await instabugHttpClient.head(url); expect(result, isInstanceOf()); expect(result, mockedResponse); verify(instabugHttpClient.logger - .onLogger(mockedResponse, startTime: anyNamed('startTime'))) + .onLogger(mockedResponse, startTime: anyNamed('startTime')),) .called(1); }); test('expect instabug http client DELETE to return response', () async { when(instabugHttpClient.client.delete(url,headers: anyNamed('headers'))) .thenAnswer((_) async => mockedResponse); - final http.Response result = await instabugHttpClient.delete(url); + final result = await instabugHttpClient.delete(url); expect(result, isInstanceOf()); expect(result, mockedResponse); verify(instabugHttpClient.logger - .onLogger(mockedResponse, startTime: anyNamed('startTime'))) + .onLogger(mockedResponse, startTime: anyNamed('startTime')),) .called(1); }); test('expect instabug http client PATCH to return response', () async { when(instabugHttpClient.client.patch(url,headers: anyNamed('headers'))) .thenAnswer((_) async => mockedResponse); - final http.Response result = await instabugHttpClient.patch(url); + final result = await instabugHttpClient.patch(url); expect(result, isInstanceOf()); expect(result, mockedResponse); verify(instabugHttpClient.logger - .onLogger(mockedResponse, startTime: anyNamed('startTime'))) + .onLogger(mockedResponse, startTime: anyNamed('startTime')),) .called(1); }); test('expect instabug http client POST to return response', () async { when(instabugHttpClient.client.post(url,headers: anyNamed('headers'))) .thenAnswer((_) async => mockedResponse); - final http.Response result = await instabugHttpClient.post(url); + final result = await instabugHttpClient.post(url); expect(result, isInstanceOf()); expect(result, mockedResponse); verify(instabugHttpClient.logger - .onLogger(mockedResponse, startTime: anyNamed('startTime'))) + .onLogger(mockedResponse, startTime: anyNamed('startTime')),) .called(1); }); test('expect instabug http client PUT to return response', () async { when(instabugHttpClient.client.put(url,headers: anyNamed('headers'))) .thenAnswer((_) async => mockedResponse); - final http.Response result = await instabugHttpClient.put(url); + final result = await instabugHttpClient.put(url); expect(result, isInstanceOf()); expect(result.body, mockedResponse.body); verify(instabugHttpClient.logger - .onLogger(mockedResponse, startTime: anyNamed('startTime'))) + .onLogger(mockedResponse, startTime: anyNamed('startTime')),) .called(1); }); test('expect instabug http client READ to return response', () async { - const String response = 'Some response string'; + const response = 'Some response string'; when(instabugHttpClient.client.read(url,headers: anyNamed('headers'))) .thenAnswer((_) async => response); - final String result = await instabugHttpClient.read(url); + final result = await instabugHttpClient.read(url); expect(result, isInstanceOf()); expect(result, response); }); test('expect instabug http client READBYTES to return response', () async { - final Uint8List response = Uint8List(3); + final response = Uint8List(3); instabugHttpClient.client = MockClient((_) async => http.Response.bytes(response, 200)); - final Uint8List result = await instabugHttpClient.readBytes(url); + final result = await instabugHttpClient.readBytes(url); expect(result, isInstanceOf()); expect(result, response); }); test('expect instabug http client SEND to return response', () async { - final http.StreamedResponse response = http.StreamedResponse( + final response = http.StreamedResponse( const Stream>.empty(), 200, - contentLength: 0); - final http.StreamedRequest request = http.StreamedRequest('POST', url) + contentLength: 0,); + final request = http.StreamedRequest('POST', url) ..headers[HttpHeaders.contentTypeHeader] = 'application/json; charset=utf-8' ..headers[HttpHeaders.userAgentHeader] = 'Dart'; when(instabugHttpClient.client.send(request)) .thenAnswer((_) async => response); - final Future responseFuture = + final responseFuture = instabugHttpClient.send(request); request ..sink.add('{"hello": "world"}'.codeUnits) ..sink.close(); - final http.StreamedResponse result = await responseFuture; + final result = await responseFuture; expect(result, isInstanceOf()); expect(result.headers, response.headers); expect(result.statusCode, response.statusCode); @@ -164,8 +164,8 @@ Future main() async { expect(result.reasonPhrase, response.reasonPhrase); expect(result.request, response.request); expect(await result.stream.bytesToString(), - await response.stream.bytesToString()); - final MockInstabugHttpLogger logger = + await response.stream.bytesToString(),); + final logger = instabugHttpClient.logger as MockInstabugHttpLogger; verify(logger.onLogger(any, startTime: anyNamed('startTime'))).called(1); }); @@ -179,11 +179,11 @@ Future main() async { test('stress test for GET method', () async { when(instabugHttpClient.client.get(url,headers: anyNamed('headers'))) .thenAnswer((_) async => mockedResponse); - for (int i = 0; i < 10000; i++) { + for (var i = 0; i < 10000; i++) { await instabugHttpClient.get(url); } verify(instabugHttpClient.logger - .onLogger(mockedResponse, startTime: anyNamed('startTime'))) + .onLogger(mockedResponse, startTime: anyNamed('startTime')),) .called(10000); }); } diff --git a/packages/instabug_private_views/.metadata b/packages/instabug_private_views/.metadata new file mode 100644 index 000000000..97e8389f1 --- /dev/null +++ b/packages/instabug_private_views/.metadata @@ -0,0 +1,33 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "603104015dd692ea3403755b55d07813d5cf8965" + channel: "stable" + +project_type: plugin + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 603104015dd692ea3403755b55d07813d5cf8965 + base_revision: 603104015dd692ea3403755b55d07813d5cf8965 + - platform: android + create_revision: 603104015dd692ea3403755b55d07813d5cf8965 + base_revision: 603104015dd692ea3403755b55d07813d5cf8965 + - platform: ios + create_revision: 603104015dd692ea3403755b55d07813d5cf8965 + base_revision: 603104015dd692ea3403755b55d07813d5cf8965 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/packages/instabug_private_views/CHANGELOG.md b/packages/instabug_private_views/CHANGELOG.md new file mode 100644 index 000000000..26deb9dda --- /dev/null +++ b/packages/instabug_private_views/CHANGELOG.md @@ -0,0 +1,7 @@ +# Changelog + +## [UnReleased](https://github.com/Instabug/) + +### Added + +- Add support for masking private views during screen capturing ([#527](https://github.com/Instabug/Instabug-Flutter/pull/527)). diff --git a/packages/instabug_private_views/LICENSE b/packages/instabug_private_views/LICENSE new file mode 100644 index 000000000..80d729766 --- /dev/null +++ b/packages/instabug_private_views/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) Instabug + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/packages/instabug_private_views/README.md b/packages/instabug_private_views/README.md new file mode 100644 index 000000000..0c7f936a8 --- /dev/null +++ b/packages/instabug_private_views/README.md @@ -0,0 +1,63 @@ +# instabug_private_views + +An add-on for the Instabug Flutter SDK that provides private views support in screen. + +[![Twitter](https://img.shields.io/badge/twitter-@Instabug-blue.svg)](https://twitter.com/Instabug) + +An add-on for the [Instabug Flutter SDK](https://github.com/Instabug/Instabug-Flutter) hat provides private views support in screenshot capturing [Flutter Private views](https://pub.dev/packages/). + +## Installation + +1. Add `instabug_private_views` to your `pubspec.yaml` file. + +```yaml +dependencies: + instabug_private_views: +``` + +2. Install the package by running the following command. + +```sh +flutter pub get +``` + +## Usage + +1. enable `PrivateViews` after `init` the SDK: + + +```dart + +void main() { + + Instabug.init( + token: 'App token', + invocationEvents: [InvocationEvent.floatingButton], + ); + + ReproSteps.enableMaskingPrivateViews(); + + runApp(MyApp()); + +} +``` + +2. Wrap the view you want to mask with `InstabugPrivateView`: + +```dart + InstabugPrivateView( +child: const Text( +'Private TextView', +style: TextStyle(fontSize: 18), +textAlign: TextAlign.center, +), +), +``` + +you can use `InstabugSliverPrivateView` if you want to wrap Sliver widget +```dart + InstabugSliverPrivateView( +sliver: SliverToBoxAdapter( +child: /// child +)), +``` \ No newline at end of file diff --git a/packages/instabug_private_views/android/build.gradle b/packages/instabug_private_views/android/build.gradle new file mode 100644 index 000000000..50025559f --- /dev/null +++ b/packages/instabug_private_views/android/build.gradle @@ -0,0 +1,56 @@ +group = "com.instabug.instabug_private_views" +version = "1.0" + +buildscript { + repositories { + google() + mavenCentral() + } + + dependencies { + classpath("com.android.tools.build:gradle:8.1.0") + } +} + +rootProject.allprojects { + repositories { + google() + mavenCentral() + } +} + +apply plugin: "com.android.library" + +android { + if (project.android.hasProperty("namespace")) { + namespace = "com.instabug.instabug_private_views" + } + + compileSdk = 34 + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + defaultConfig { + minSdk = 21 + } + + dependencies { + testImplementation("junit:junit:4.13.2") + testImplementation("org.mockito:mockito-core:5.0.0") + testImplementation ('org.robolectric:robolectric:4.12.2') + + } + + testOptions { + unitTests.all { + testLogging { + events "passed", "skipped", "failed", "standardOut", "standardError" + outputs.upToDateWhen {false} + showStandardStreams = true + } + } + } +} diff --git a/packages/instabug_private_views/android/settings.gradle b/packages/instabug_private_views/android/settings.gradle new file mode 100644 index 000000000..64f5453f5 --- /dev/null +++ b/packages/instabug_private_views/android/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'instabug_private_views' diff --git a/packages/instabug_private_views/android/src/main/AndroidManifest.xml b/packages/instabug_private_views/android/src/main/AndroidManifest.xml new file mode 100644 index 000000000..23fe3b997 --- /dev/null +++ b/packages/instabug_private_views/android/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + diff --git a/packages/instabug_private_views/android/src/main/java/com/instabug/instabug_private_views/InstabugPrivateViewsPlugin.java b/packages/instabug_private_views/android/src/main/java/com/instabug/instabug_private_views/InstabugPrivateViewsPlugin.java new file mode 100644 index 000000000..2065b60c1 --- /dev/null +++ b/packages/instabug_private_views/android/src/main/java/com/instabug/instabug_private_views/InstabugPrivateViewsPlugin.java @@ -0,0 +1,88 @@ +package com.instabug.instabug_private_views; + +import android.annotation.SuppressLint; +import android.app.Activity; + +import androidx.annotation.NonNull; + +import com.instabug.instabug_private_views.generated.InstabugPrivateViewPigeon; +import com.instabug.instabug_private_views.modules.capturing.BoundryCaptureManager; +import com.instabug.instabug_private_views.modules.InstabugPrivateView; +import com.instabug.instabug_private_views.modules.capturing.PixelCopyCaptureManager; +import com.instabug.instabug_private_views.modules.PrivateViewManager; +import com.instabug.library.internal.crossplatform.InternalCore; + +import io.flutter.embedding.engine.plugins.FlutterPlugin; +import io.flutter.embedding.engine.plugins.activity.ActivityAware; +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; +import io.flutter.embedding.engine.renderer.FlutterRenderer; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugin.common.PluginRegistry; + +/** + * InstabugPrivateViewsPlugin + */ +public class InstabugPrivateViewsPlugin implements FlutterPlugin, ActivityAware { + private static final String TAG = InstabugPrivateViewsPlugin.class.getName(); + + @SuppressLint("StaticFieldLeak") + private static Activity activity; + + PrivateViewManager privateViewManager; + + /** + * Embedding v1 + */ + @SuppressWarnings("deprecation") + public void registerWith(PluginRegistry.Registrar registrar) { + activity = registrar.activity(); + register( registrar.messenger(), (FlutterRenderer) registrar.textures()); + } + + + @Override + public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { + register(binding.getBinaryMessenger(), (FlutterRenderer) binding.getTextureRegistry()); + } + + @Override + public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { + activity = null; + } + + @Override + public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) { + activity = binding.getActivity(); + if (privateViewManager != null) { + privateViewManager.setActivity(activity); + } + + } + + @Override + public void onDetachedFromActivityForConfigChanges() { + activity = null; + privateViewManager.setActivity(null); + + } + + @Override + public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) { + activity = binding.getActivity(); + privateViewManager.setActivity(activity); + + } + + @Override + public void onDetachedFromActivity() { + activity = null; + privateViewManager.setActivity(null); + + } + + private void register(BinaryMessenger messenger, FlutterRenderer renderer) { + privateViewManager = new PrivateViewManager(new InstabugPrivateViewPigeon.InstabugPrivateViewFlutterApi(messenger), new PixelCopyCaptureManager(), new BoundryCaptureManager(renderer)); + InstabugPrivateView.init(messenger,privateViewManager); + + } +} diff --git a/packages/instabug_private_views/android/src/main/java/com/instabug/instabug_private_views/model/ScreenshotResult.java b/packages/instabug_private_views/android/src/main/java/com/instabug/instabug_private_views/model/ScreenshotResult.java new file mode 100644 index 000000000..107d431d2 --- /dev/null +++ b/packages/instabug_private_views/android/src/main/java/com/instabug/instabug_private_views/model/ScreenshotResult.java @@ -0,0 +1,21 @@ +package com.instabug.instabug_private_views.model; + +import android.graphics.Bitmap; + +public class ScreenshotResult { + private final float pixelRatio; + private final Bitmap screenshot; + + public ScreenshotResult(float pixelRatio, Bitmap screenshot) { + this.pixelRatio = pixelRatio; + this.screenshot = screenshot; + } + + public Bitmap getScreenshot() { + return screenshot; + } + + public float getPixelRatio() { + return pixelRatio; + } +} diff --git a/packages/instabug_private_views/android/src/main/java/com/instabug/instabug_private_views/modules/InstabugPrivateView.java b/packages/instabug_private_views/android/src/main/java/com/instabug/instabug_private_views/modules/InstabugPrivateView.java new file mode 100644 index 000000000..9213eadf3 --- /dev/null +++ b/packages/instabug_private_views/android/src/main/java/com/instabug/instabug_private_views/modules/InstabugPrivateView.java @@ -0,0 +1,39 @@ +package com.instabug.instabug_private_views.modules; + +import androidx.annotation.NonNull; + +import com.instabug.flutter.generated.InstabugLogPigeon; +import com.instabug.flutter.modules.InstabugApi; +import com.instabug.flutter.modules.InstabugLogApi; +import com.instabug.flutter.util.privateViews.ScreenshotCaptor; +import com.instabug.instabug_private_views.generated.InstabugPrivateViewPigeon; +import com.instabug.library.internal.crossplatform.InternalCore; +import com.instabug.library.screenshot.instacapture.ScreenshotRequest; + +import io.flutter.plugin.common.BinaryMessenger; + +public class InstabugPrivateView implements InstabugPrivateViewPigeon.InstabugPrivateViewHostApi { + PrivateViewManager privateViewManager; + + public static void init(BinaryMessenger messenger, PrivateViewManager privateViewManager) { + final InstabugPrivateView api = new InstabugPrivateView(messenger,privateViewManager); + InstabugPrivateViewPigeon.InstabugPrivateViewHostApi.setup(messenger, api); + } + + public InstabugPrivateView(BinaryMessenger messenger, PrivateViewManager privateViewManager) { + this.privateViewManager = privateViewManager; + InstabugPrivateViewPigeon.InstabugPrivateViewHostApi.setup(messenger, this); + + } + + @Override + public void init() { + InstabugApi.setScreenshotCaptor(new ScreenshotCaptor() { + @Override + public void capture(CapturingCallback listener) { + privateViewManager.mask(listener); + + } + },InternalCore.INSTANCE); + } +} diff --git a/packages/instabug_private_views/android/src/main/java/com/instabug/instabug_private_views/modules/PrivateViewManager.java b/packages/instabug_private_views/android/src/main/java/com/instabug/instabug_private_views/modules/PrivateViewManager.java new file mode 100644 index 000000000..6b8abb152 --- /dev/null +++ b/packages/instabug_private_views/android/src/main/java/com/instabug/instabug_private_views/modules/PrivateViewManager.java @@ -0,0 +1,139 @@ +package com.instabug.instabug_private_views.modules; + +import android.app.Activity; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.os.Build; + +import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; + +import com.instabug.flutter.util.ThreadManager; +import com.instabug.flutter.util.privateViews.ScreenshotCaptor; +import com.instabug.instabug_private_views.generated.InstabugPrivateViewPigeon; +import com.instabug.instabug_private_views.model.ScreenshotResult; +import com.instabug.instabug_private_views.modules.capturing.CaptureManager; +import com.instabug.instabug_private_views.modules.capturing.ScreenshotResultCallback; + +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicReference; + +public class PrivateViewManager { + private static final String THREAD_NAME = "IBG-Flutter-Screenshot"; + public static final String EXCEPTION_MESSAGE = "IBG-Flutter-Screenshot: error capturing screenshot"; + + private final ExecutorService screenshotExecutor = Executors.newSingleThreadExecutor(runnable -> { + Thread thread = new Thread(runnable); + thread.setName(THREAD_NAME); + return thread; + }); + + private final InstabugPrivateViewPigeon.InstabugPrivateViewFlutterApi instabugPrivateViewApi; + private Activity activity; + final CaptureManager pixelCopyScreenshotCaptor; + final CaptureManager boundryScreenshotCaptor; + + public PrivateViewManager(@NonNull InstabugPrivateViewPigeon.InstabugPrivateViewFlutterApi instabugPrivateViewApi, CaptureManager pixelCopyCaptureManager, CaptureManager boundryCaptureManager) { + this.instabugPrivateViewApi = instabugPrivateViewApi; + this.pixelCopyScreenshotCaptor = pixelCopyCaptureManager; + this.boundryScreenshotCaptor = boundryCaptureManager; + + + } + + public void setActivity(Activity activity) { + this.activity = activity; + } + + + public void mask(ScreenshotCaptor.CapturingCallback capturingCallback) { + if (activity != null) { + CountDownLatch latch = new CountDownLatch(1); + AtomicReference> privateViews = new AtomicReference<>(); + final ScreenshotResultCallback boundryScreenshotResult = new ScreenshotResultCallback() { + + @Override + public void onScreenshotResult(ScreenshotResult screenshotResult) { + processScreenshot(screenshotResult, privateViews, latch, capturingCallback); + + } + + @Override + public void onError() { + capturingCallback.onCapturingFailure(new Exception(EXCEPTION_MESSAGE)); + } + }; + + try { + ThreadManager.runOnMainThread(new Runnable() { + @Override + public void run() { + instabugPrivateViewApi.getPrivateViews(result -> { + privateViews.set(result); + latch.countDown(); + }); + } + }); + + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + pixelCopyScreenshotCaptor.capture(activity, new ScreenshotResultCallback() { + @Override + public void onScreenshotResult(ScreenshotResult result) { + processScreenshot(result, privateViews, latch, capturingCallback); + } + + @Override + public void onError() { + boundryScreenshotCaptor.capture(activity, boundryScreenshotResult); + + } + }); + } else { + boundryScreenshotCaptor.capture(activity, boundryScreenshotResult); + } + + } catch (Exception e) { + capturingCallback.onCapturingFailure(e); + } + } else { + capturingCallback.onCapturingFailure(new Exception(EXCEPTION_MESSAGE)); + } + } + + + private void processScreenshot(ScreenshotResult result, AtomicReference> privateViews, CountDownLatch latch, ScreenshotCaptor.CapturingCallback capturingCallback) { + screenshotExecutor.execute(() -> { + try { + latch.await(); // Wait + Bitmap bitmap = result.getScreenshot(); + maskPrivateViews(result, privateViews.get()); + capturingCallback.onCapturingSuccess(bitmap); + } catch (InterruptedException e) { + capturingCallback.onCapturingFailure(e); + } + }); + } + + @VisibleForTesting + public void maskPrivateViews(ScreenshotResult result, List privateViews) { + if (privateViews == null || privateViews.isEmpty()) return; + + Bitmap bitmap = result.getScreenshot(); + float pixelRatio = result.getPixelRatio(); + Canvas canvas = new Canvas(bitmap); + Paint paint = new Paint(); // Default color is black + + for (int i = 0; i < privateViews.size(); i += 4) { + float left = privateViews.get(i).floatValue() * pixelRatio; + float top = privateViews.get(i + 1).floatValue() * pixelRatio; + float right = privateViews.get(i + 2).floatValue() * pixelRatio; + float bottom = privateViews.get(i + 3).floatValue() * pixelRatio; + canvas.drawRect(left, top, right, bottom, paint); // Mask private view + } + } +} \ No newline at end of file diff --git a/packages/instabug_private_views/android/src/main/java/com/instabug/instabug_private_views/modules/capturing/BoundryCaptureManager.java b/packages/instabug_private_views/android/src/main/java/com/instabug/instabug_private_views/modules/capturing/BoundryCaptureManager.java new file mode 100644 index 000000000..4c9f3d047 --- /dev/null +++ b/packages/instabug_private_views/android/src/main/java/com/instabug/instabug_private_views/modules/capturing/BoundryCaptureManager.java @@ -0,0 +1,42 @@ +package com.instabug.instabug_private_views.modules.capturing; + +import android.app.Activity; +import android.graphics.Bitmap; +import android.util.DisplayMetrics; +import android.view.View; + +import com.instabug.flutter.util.ThreadManager; +import com.instabug.instabug_private_views.model.ScreenshotResult; +import io.flutter.embedding.engine.renderer.FlutterRenderer; + +public class BoundryCaptureManager implements CaptureManager { + FlutterRenderer renderer; + + public BoundryCaptureManager(FlutterRenderer renderer) { + this.renderer = renderer; + } + + @Override + public void capture(Activity activity, ScreenshotResultCallback screenshotResultCallback) { + ThreadManager.runOnMainThread(new Runnable() { + @Override + public void run() { + try { + if (activity == null) { + screenshotResultCallback.onError(); + return; + } + View rootView = activity.getWindow().getDecorView().getRootView(); + rootView.setDrawingCacheEnabled(true); + Bitmap bitmap = renderer.getBitmap(); + rootView.setDrawingCacheEnabled(false); + DisplayMetrics displayMetrics = activity.getResources().getDisplayMetrics(); + screenshotResultCallback.onScreenshotResult(new ScreenshotResult(displayMetrics.density, bitmap)); + + } catch (Exception e) { + screenshotResultCallback.onError(); + } + } + }); + } +} diff --git a/packages/instabug_private_views/android/src/main/java/com/instabug/instabug_private_views/modules/capturing/CaptureManager.java b/packages/instabug_private_views/android/src/main/java/com/instabug/instabug_private_views/modules/capturing/CaptureManager.java new file mode 100644 index 000000000..4b6ccff84 --- /dev/null +++ b/packages/instabug_private_views/android/src/main/java/com/instabug/instabug_private_views/modules/capturing/CaptureManager.java @@ -0,0 +1,7 @@ +package com.instabug.instabug_private_views.modules.capturing; + +import android.app.Activity; + +public interface CaptureManager { + void capture(Activity activity, ScreenshotResultCallback screenshotResultCallback); +} diff --git a/packages/instabug_private_views/android/src/main/java/com/instabug/instabug_private_views/modules/capturing/PixelCopyCaptureManager.java b/packages/instabug_private_views/android/src/main/java/com/instabug/instabug_private_views/modules/capturing/PixelCopyCaptureManager.java new file mode 100644 index 000000000..f8f3d2cca --- /dev/null +++ b/packages/instabug_private_views/android/src/main/java/com/instabug/instabug_private_views/modules/capturing/PixelCopyCaptureManager.java @@ -0,0 +1,86 @@ +package com.instabug.instabug_private_views.modules.capturing; + +import android.app.Activity; +import android.graphics.Bitmap; +import android.os.Build; +import android.os.Handler; +import android.os.Looper; +import android.util.DisplayMetrics; +import android.view.PixelCopy; +import android.view.SurfaceView; + +import androidx.annotation.RequiresApi; + +import com.instabug.instabug_private_views.model.ScreenshotResult; +import com.instabug.library.util.memory.MemoryUtils; + +import io.flutter.embedding.android.FlutterActivity; +import io.flutter.embedding.android.FlutterFragment; +import io.flutter.embedding.android.FlutterView; + +public class PixelCopyCaptureManager implements CaptureManager { + + @RequiresApi(api = Build.VERSION_CODES.N) + @Override + public void capture(Activity activity, ScreenshotResultCallback screenshotResultCallback) { + FlutterView flutterView = getFlutterView(activity); + if (flutterView == null || !isValidFlutterView(flutterView)) { + screenshotResultCallback.onError(); + return; + } + + SurfaceView surfaceView = (SurfaceView) flutterView.getChildAt(0); + Bitmap bitmap = createBitmapFromSurface(surfaceView); + + if (bitmap == null) { + screenshotResultCallback.onError(); + return; + } + + PixelCopy.request(surfaceView, bitmap, copyResult -> { + if (copyResult == PixelCopy.SUCCESS) { + DisplayMetrics displayMetrics = activity.getResources().getDisplayMetrics(); + screenshotResultCallback.onScreenshotResult(new ScreenshotResult(displayMetrics.density, bitmap)); + } else { + screenshotResultCallback.onError(); + } + }, new Handler(Looper.getMainLooper())); + } + + private FlutterView getFlutterView(Activity activity) { + FlutterView flutterViewInActivity = activity.findViewById(FlutterActivity.FLUTTER_VIEW_ID); + FlutterView flutterViewInFragment = activity.findViewById(FlutterFragment.FLUTTER_VIEW_ID); + return flutterViewInActivity != null ? flutterViewInActivity : flutterViewInFragment; + } + + private boolean isValidFlutterView(FlutterView flutterView) { + boolean hasChildren = flutterView.getChildCount() > 0; + boolean isSurfaceView = flutterView.getChildAt(0) instanceof SurfaceView; + return hasChildren && isSurfaceView; + } + + private Bitmap createBitmapFromSurface(SurfaceView surfaceView) { + int width = surfaceView.getWidth(); + int height = surfaceView.getHeight(); + + if (width <= 0 || height <= 0) { + return null; + } + Bitmap bitmap; + try { + if (((long) width * height * 4) < MemoryUtils.getFreeMemory(surfaceView.getContext())) { + // ARGB_8888 store each pixel in 4 bytes + bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + } else { + // RGB_565 store each pixel in 2 bytes + bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565); + } + + } catch (IllegalArgumentException | OutOfMemoryError e) { + bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565); + } + + + return bitmap; + } +} diff --git a/packages/instabug_private_views/android/src/main/java/com/instabug/instabug_private_views/modules/capturing/ScreenshotResultCallback.java b/packages/instabug_private_views/android/src/main/java/com/instabug/instabug_private_views/modules/capturing/ScreenshotResultCallback.java new file mode 100644 index 000000000..fd6c0f23f --- /dev/null +++ b/packages/instabug_private_views/android/src/main/java/com/instabug/instabug_private_views/modules/capturing/ScreenshotResultCallback.java @@ -0,0 +1,8 @@ +package com.instabug.instabug_private_views.modules.capturing; + +import com.instabug.instabug_private_views.model.ScreenshotResult; + +public interface ScreenshotResultCallback { + void onScreenshotResult(ScreenshotResult screenshotResult); + void onError(); +} diff --git a/packages/instabug_private_views/android/src/test/java/com/instabug/instabug_private_views/BoundryScreenshotCaptorTest.java b/packages/instabug_private_views/android/src/test/java/com/instabug/instabug_private_views/BoundryScreenshotCaptorTest.java new file mode 100644 index 000000000..979f6c6c5 --- /dev/null +++ b/packages/instabug_private_views/android/src/test/java/com/instabug/instabug_private_views/BoundryScreenshotCaptorTest.java @@ -0,0 +1,80 @@ +package com.instabug.instabug_private_views; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.robolectric.Shadows.shadowOf; + +import android.app.Activity; +import android.graphics.Bitmap; +import android.os.Looper; + +import com.instabug.instabug_private_views.modules.capturing.BoundryCaptureManager; +import com.instabug.instabug_private_views.modules.capturing.CaptureManager; +import com.instabug.instabug_private_views.model.ScreenshotResult; +import com.instabug.instabug_private_views.modules.capturing.ScreenshotResultCallback; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentMatcher; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import io.flutter.embedding.engine.renderer.FlutterRenderer; + +@RunWith(RobolectricTestRunner.class) +@Config(sdk = {28}, manifest = Config.NONE) +public class BoundryScreenshotCaptorTest { + private Activity activityMock; + private Bitmap bitmap; + private CaptureManager captureManager; + + @Before + public void setUp() { + FlutterRenderer rendererMock = mock(FlutterRenderer.class); + activityMock = spy(Robolectric.buildActivity(Activity.class).setup().create().start().resume().get()); + bitmap = Bitmap.createBitmap(200, 200, Bitmap.Config.ARGB_8888); + when(rendererMock.getBitmap()).thenReturn(bitmap); + captureManager = new BoundryCaptureManager(rendererMock); + } + + @Test + public void testCaptureGivenEmptyActivity() { + ScreenshotResultCallback mockCallback = mock(ScreenshotResultCallback.class); + + captureManager.capture(null, mockCallback); + shadowOf(Looper.getMainLooper()).idle(); + + verify(mockCallback).onError(); + verify(mockCallback, never()).onScreenshotResult(any(ScreenshotResult.class)); + + } + + @Test + public void testCapture() { + ScreenshotResultCallback mockCallback = mock(ScreenshotResultCallback.class); + captureManager.capture(activityMock, mockCallback); + shadowOf(Looper.getMainLooper()).idle(); + + verify(mockCallback, never()).onError(); + verify(mockCallback).onScreenshotResult(argThat(new ArgumentMatcher() { + @Override + public boolean matches(ScreenshotResult argument) { + return (Math.abs(activityMock.getResources().getDisplayMetrics().density - argument.getPixelRatio()) < 0.01)&& + bitmap == argument.getScreenshot(); + + } + })); + } + + +} diff --git a/packages/instabug_private_views/android/src/test/java/com/instabug/instabug_private_views/PixelCopyScreenshotCaptorTest.java b/packages/instabug_private_views/android/src/test/java/com/instabug/instabug_private_views/PixelCopyScreenshotCaptorTest.java new file mode 100644 index 000000000..4841e8506 --- /dev/null +++ b/packages/instabug_private_views/android/src/test/java/com/instabug/instabug_private_views/PixelCopyScreenshotCaptorTest.java @@ -0,0 +1,94 @@ +package com.instabug.instabug_private_views; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.robolectric.Shadows.shadowOf; + +import android.app.Activity; +import android.graphics.Bitmap; +import android.os.Looper; +import android.view.SurfaceView; + +import com.instabug.instabug_private_views.modules.capturing.CaptureManager; +import com.instabug.instabug_private_views.modules.capturing.PixelCopyCaptureManager; +import com.instabug.instabug_private_views.model.ScreenshotResult; +import com.instabug.instabug_private_views.modules.capturing.ScreenshotResultCallback; +import com.instabug.library.util.memory.MemoryUtils; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockedStatic; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import io.flutter.embedding.android.FlutterActivity; +import io.flutter.embedding.android.FlutterView; +import io.flutter.embedding.engine.renderer.FlutterRenderer; + +@RunWith(RobolectricTestRunner.class) +@Config(sdk = {28}, manifest = Config.NONE) +public class PixelCopyScreenshotCaptorTest { + private Activity activityMock; + private Bitmap bitmap; + private CaptureManager captureManager; + + @Before + public void setUp() { + FlutterRenderer rendererMock = mock(FlutterRenderer.class); + activityMock = spy(Robolectric.buildActivity(Activity.class).setup().create().start().resume().get()); + bitmap = Bitmap.createBitmap(200, 200, Bitmap.Config.ARGB_8888); + when(rendererMock.getBitmap()).thenReturn(bitmap); + captureManager = new PixelCopyCaptureManager(); + } + + @Test + public void testCaptureWithPixelCopyGivenEmptyView() { + + ScreenshotResultCallback mockScreenshotResultCallback = mock(ScreenshotResultCallback.class); + when(activityMock.findViewById(FlutterActivity.FLUTTER_VIEW_ID)).thenReturn(null); + captureManager.capture(activityMock,mockScreenshotResultCallback); + + verify(mockScreenshotResultCallback).onError(); + } + + @Test + public void testCaptureWithPixelCopy() { + try (MockedStatic mockedStatic = mockStatic(MemoryUtils.class)) { + mockedStatic.when(() -> MemoryUtils.getFreeMemory(any())).thenReturn(Long.MAX_VALUE); + + mockFlutterViewInPixelCopy(); + + ScreenshotResultCallback mockScreenshotResultCallback = mock(ScreenshotResultCallback.class); + + + captureManager.capture(activityMock, mockScreenshotResultCallback); + shadowOf(Looper.getMainLooper()).idle(); + + verify(mockScreenshotResultCallback, timeout(1000)).onScreenshotResult(any(ScreenshotResult.class)); // PixelCopy success + + } + } + + + private void mockFlutterViewInPixelCopy() { + + SurfaceView mockSurfaceView = mock(SurfaceView.class); + FlutterView flutterView = mock(FlutterView.class); + when(flutterView.getChildAt(0)).thenReturn(mockSurfaceView); + when(flutterView.getChildCount()).thenReturn(1); + + when(activityMock.findViewById(FlutterActivity.FLUTTER_VIEW_ID)).thenReturn(flutterView); + when(mockSurfaceView.getWidth()).thenReturn(100); + when(mockSurfaceView.getHeight()).thenReturn(100); + } +} diff --git a/packages/instabug_private_views/android/src/test/java/com/instabug/instabug_private_views/PrivateViewManagerTest.java b/packages/instabug_private_views/android/src/test/java/com/instabug/instabug_private_views/PrivateViewManagerTest.java new file mode 100644 index 000000000..e08b3c50d --- /dev/null +++ b/packages/instabug_private_views/android/src/test/java/com/instabug/instabug_private_views/PrivateViewManagerTest.java @@ -0,0 +1,146 @@ +package com.instabug.instabug_private_views; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.robolectric.Shadows.shadowOf; + +import android.app.Activity; +import android.graphics.Bitmap; +import android.os.Build; +import android.os.Looper; +import android.view.SurfaceView; +import com.instabug.instabug_private_views.generated.InstabugPrivateViewPigeon; +import com.instabug.instabug_private_views.modules.capturing.BoundryCaptureManager; +import com.instabug.instabug_private_views.modules.capturing.CaptureManager; +import com.instabug.instabug_private_views.modules.capturing.PixelCopyCaptureManager; +import com.instabug.instabug_private_views.modules.PrivateViewManager; +import com.instabug.instabug_private_views.model.ScreenshotResult; +import com.instabug.library.screenshot.ScreenshotCaptor; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import java.util.Arrays; +import java.util.List; + +import io.flutter.embedding.android.FlutterActivity; +import io.flutter.embedding.android.FlutterView; +import io.flutter.embedding.engine.renderer.FlutterRenderer; + +@RunWith(RobolectricTestRunner.class) +@Config(sdk = {28}, manifest = Config.NONE) +public class PrivateViewManagerTest { + + private PrivateViewManager privateViewManager; + private InstabugPrivateViewPigeon.InstabugPrivateViewFlutterApi InstabugPrivateViewFlutterApiMock; + private Activity activityMock; + private Bitmap bitmap; + private CaptureManager pixelCopyScreenCaptor, boundryScreenCaptor; + + @Before + public void setUp() { + InstabugPrivateViewFlutterApiMock = mock(InstabugPrivateViewPigeon.InstabugPrivateViewFlutterApi.class); + FlutterRenderer rendererMock = mock(FlutterRenderer.class); + activityMock = spy(Robolectric.buildActivity(Activity.class).setup().create().start().resume().get()); + bitmap = Bitmap.createBitmap(200, 200, Bitmap.Config.ARGB_8888); + when(rendererMock.getBitmap()).thenReturn(bitmap); + pixelCopyScreenCaptor = spy(new PixelCopyCaptureManager()); + boundryScreenCaptor = spy(new BoundryCaptureManager(rendererMock)); + privateViewManager = spy(new PrivateViewManager(InstabugPrivateViewFlutterApiMock, pixelCopyScreenCaptor, boundryScreenCaptor)); + privateViewManager.setActivity(activityMock); + + } + + + @Test + public void testMaskGivenEmptyActivity() { + ScreenshotCaptor.CapturingCallback capturingCallbackMock = mock(ScreenshotCaptor.CapturingCallback.class); + privateViewManager.setActivity(null); + privateViewManager.mask(capturingCallbackMock); + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Throwable.class); + verify(capturingCallbackMock).onCapturingFailure(argumentCaptor.capture()); + assertEquals( PrivateViewManager.EXCEPTION_MESSAGE, argumentCaptor.getValue().getMessage()); + } + + @Test + public void testMask() throws InterruptedException { + ScreenshotCaptor.CapturingCallback capturingCallbackMock = mock(ScreenshotCaptor.CapturingCallback.class); + doAnswer(invocation -> { + InstabugPrivateViewPigeon.InstabugPrivateViewFlutterApi.Reply> callback = invocation.getArgument(0); // Get the callback + callback.reply(Arrays.asList(10.0, 20.0, 100.0, 200.0)); // Trigger the success callback + return null; + }).when(InstabugPrivateViewFlutterApiMock).getPrivateViews(any(InstabugPrivateViewPigeon.InstabugPrivateViewFlutterApi.Reply.class)); // Mock the method call + + + // Trigger the mask operation + privateViewManager.mask(capturingCallbackMock); + // Mock that latch.await() has been called + shadowOf(Looper.getMainLooper()).idle(); + + // Simulate a successful bitmap capture + verify(capturingCallbackMock, timeout(1000)).onCapturingSuccess(bitmap); + } + + + private void mockFlutterViewInPixelCopy() { + SurfaceView mockSurfaceView = mock(SurfaceView.class); + FlutterView flutterView = mock(FlutterView.class); + when(flutterView.getChildAt(0)).thenReturn(mockSurfaceView); + when(flutterView.getChildCount()).thenReturn(1); + + when(activityMock.findViewById(FlutterActivity.FLUTTER_VIEW_ID)).thenReturn(flutterView); + when(mockSurfaceView.getWidth()).thenReturn(100); + when(mockSurfaceView.getHeight()).thenReturn(100); + } + + + @Test + public void testMaskPrivateViews() { + ScreenshotResult mockResult = mock(ScreenshotResult.class); + when(mockResult.getScreenshot()).thenReturn(Bitmap.createBitmap(200, 200, Bitmap.Config.ARGB_8888)); + when(mockResult.getPixelRatio()).thenReturn(2.0f); + + List privateViews = Arrays.asList(10.0, 20.0, 100.0, 200.0); + + privateViewManager.maskPrivateViews(mockResult, privateViews); + + assertNotNull(mockResult.getScreenshot()); + } + + @Test + @Config(sdk = {Build.VERSION_CODES.M}) + public void testMaskShouldGetScreenshotWhenAPIVersionLessThan28() { + ScreenshotCaptor.CapturingCallback capturingCallbackMock = mock(ScreenshotCaptor.CapturingCallback.class); + privateViewManager.mask(capturingCallbackMock); + shadowOf(Looper.getMainLooper()).idle(); + + verify(boundryScreenCaptor).capture(any(), any()); + + } + + @Test + public void testMaskShouldCallPixelCopyWhenAPIVersionMoreThan28() { + ScreenshotCaptor.CapturingCallback capturingCallbackMock = mock(ScreenshotCaptor.CapturingCallback.class); + mockFlutterViewInPixelCopy(); + privateViewManager.mask(capturingCallbackMock); + shadowOf(Looper.getMainLooper()).idle(); + verify(boundryScreenCaptor, never()).capture(any(), any()); + verify(pixelCopyScreenCaptor).capture(any(), any()); + + + } +} \ No newline at end of file diff --git a/packages/instabug_private_views/example-hybrid-ios-app/.gitignore b/packages/instabug_private_views/example-hybrid-ios-app/.gitignore new file mode 100644 index 000000000..2624e2177 --- /dev/null +++ b/packages/instabug_private_views/example-hybrid-ios-app/.gitignore @@ -0,0 +1,45 @@ +# Xcode +DerivedData/ +build/ +*.pbxuser +*.mode1v3 +*.mode2v3 +*.perspectivev3 +*.xcworkspace +!default.xcworkspace +xcuserdata/ +*.moved-aside +*.xcuserstate +*.xcscmblueprint + +# iOS Specific +*.hmap +*.ipa +*.dSYM.zip +*.dSYM + +# Swift Package Manager +.build/ +.swiftpm/xcode/package.xcworkspace/ +.swiftpm/ + +# CocoaPods (if used) +Pods/ +Podfile.lock + +# Carthage (if used) +Carthage/Build/ + +# Fastlane (if used) +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots +fastlane/test_output + +# Bundle artifact +*.xcarchive + +# Temporary files +*.tmp +*.swp +*.swm diff --git a/packages/instabug_private_views/example-hybrid-ios-app/Podfile b/packages/instabug_private_views/example-hybrid-ios-app/Podfile new file mode 100644 index 000000000..775126029 --- /dev/null +++ b/packages/instabug_private_views/example-hybrid-ios-app/Podfile @@ -0,0 +1,21 @@ +# Uncomment the next line to define a global platform for your project +# platform :ios, '9.0' + +flutter_application_path = 'my_flutter' +load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb') + +target 'hybrid-flutter-app' do + # Comment the next line if you don't want to use dynamic frameworks + pod 'Instabug', :podspec => 'https://ios-releases.instabug.com/custom/feature-flutter-private-views-base/13.4.2/Instabug.podspec' + + use_frameworks! + + install_all_flutter_pods(flutter_application_path) + + # Pods for hybrid-flutter-app + +end + +post_install do |installer| + flutter_post_install(installer) if defined?(flutter_post_install) +end diff --git a/packages/instabug_private_views/example-hybrid-ios-app/hybrid-flutter-app.xcodeproj/project.pbxproj b/packages/instabug_private_views/example-hybrid-ios-app/hybrid-flutter-app.xcodeproj/project.pbxproj new file mode 100644 index 000000000..5e6fb0c4d --- /dev/null +++ b/packages/instabug_private_views/example-hybrid-ios-app/hybrid-flutter-app.xcodeproj/project.pbxproj @@ -0,0 +1,435 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 77; + objects = { + +/* Begin PBXBuildFile section */ + CC1152E765F8580640185243 /* Pods_hybrid_flutter_app.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 74A87659FEAAB6DF26380477 /* Pods_hybrid_flutter_app.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 6A35F6E0DFACBBAB47D661EA /* Pods-hybrid-flutter-app.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-hybrid-flutter-app.debug.xcconfig"; path = "Target Support Files/Pods-hybrid-flutter-app/Pods-hybrid-flutter-app.debug.xcconfig"; sourceTree = ""; }; + 74A87659FEAAB6DF26380477 /* Pods_hybrid_flutter_app.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_hybrid_flutter_app.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 9F7459A16324C6347AA6DAFB /* Pods-hybrid-flutter-app.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-hybrid-flutter-app.release.xcconfig"; path = "Target Support Files/Pods-hybrid-flutter-app/Pods-hybrid-flutter-app.release.xcconfig"; sourceTree = ""; }; + BE9EC8252CD1812F0026F537 /* hybrid-flutter-app.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "hybrid-flutter-app.app"; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ + BE9EC8372CD181310026F537 /* Exceptions for "hybrid-flutter-app" folder in "hybrid-flutter-app" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + Info.plist, + ); + target = BE9EC8242CD1812F0026F537 /* hybrid-flutter-app */; + }; +/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + BE9EC8272CD1812F0026F537 /* hybrid-flutter-app */ = { + isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + BE9EC8372CD181310026F537 /* Exceptions for "hybrid-flutter-app" folder in "hybrid-flutter-app" target */, + ); + path = "hybrid-flutter-app"; + sourceTree = ""; + }; +/* End PBXFileSystemSynchronizedRootGroup section */ + +/* Begin PBXFrameworksBuildPhase section */ + BE9EC8222CD1812F0026F537 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + CC1152E765F8580640185243 /* Pods_hybrid_flutter_app.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 324CF1E79ACC6661E48AC83B /* Pods */ = { + isa = PBXGroup; + children = ( + 6A35F6E0DFACBBAB47D661EA /* Pods-hybrid-flutter-app.debug.xcconfig */, + 9F7459A16324C6347AA6DAFB /* Pods-hybrid-flutter-app.release.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + 60FD6C74BF2A20B8B71EECA2 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 74A87659FEAAB6DF26380477 /* Pods_hybrid_flutter_app.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + BE9EC81C2CD1812F0026F537 = { + isa = PBXGroup; + children = ( + BE9EC8272CD1812F0026F537 /* hybrid-flutter-app */, + BE9EC8262CD1812F0026F537 /* Products */, + 324CF1E79ACC6661E48AC83B /* Pods */, + 60FD6C74BF2A20B8B71EECA2 /* Frameworks */, + ); + sourceTree = ""; + }; + BE9EC8262CD1812F0026F537 /* Products */ = { + isa = PBXGroup; + children = ( + BE9EC8252CD1812F0026F537 /* hybrid-flutter-app.app */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + BE9EC8242CD1812F0026F537 /* hybrid-flutter-app */ = { + isa = PBXNativeTarget; + buildConfigurationList = BE9EC8382CD181310026F537 /* Build configuration list for PBXNativeTarget "hybrid-flutter-app" */; + buildPhases = ( + 12C1309DE0C239420C1F847F /* [CP] Check Pods Manifest.lock */, + 742AC800B4072D761DDE7C14 /* [CP-User] Run Flutter Build my_flutter Script */, + BE9EC8212CD1812F0026F537 /* Sources */, + BE9EC8222CD1812F0026F537 /* Frameworks */, + BE9EC8232CD1812F0026F537 /* Resources */, + 56BEAC412A6C93A663ED05C8 /* [CP-User] Embed Flutter Build my_flutter Script */, + A3E7F43B3A5E735EC880C3EF /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + BE9EC8272CD1812F0026F537 /* hybrid-flutter-app */, + ); + name = "hybrid-flutter-app"; + productName = "hybrid-flutter-app"; + productReference = BE9EC8252CD1812F0026F537 /* hybrid-flutter-app.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + BE9EC81D2CD1812F0026F537 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1600; + LastUpgradeCheck = 1600; + TargetAttributes = { + BE9EC8242CD1812F0026F537 = { + CreatedOnToolsVersion = 16.0; + }; + }; + }; + buildConfigurationList = BE9EC8202CD1812F0026F537 /* Build configuration list for PBXProject "hybrid-flutter-app" */; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = BE9EC81C2CD1812F0026F537; + minimizedProjectReferenceProxies = 1; + preferredProjectObjectVersion = 77; + productRefGroup = BE9EC8262CD1812F0026F537 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + BE9EC8242CD1812F0026F537 /* hybrid-flutter-app */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + BE9EC8232CD1812F0026F537 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 12C1309DE0C239420C1F847F /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-hybrid-flutter-app-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 56BEAC412A6C93A663ED05C8 /* [CP-User] Embed Flutter Build my_flutter Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + name = "[CP-User] Embed Flutter Build my_flutter Script"; + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "set -e\nset -u\nsource \"${SRCROOT}/my_flutter/.ios/Flutter/flutter_export_environment.sh\"\nexport VERBOSE_SCRIPT_LOGGING=1 && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/xcode_backend.sh embed_and_thin"; + }; + 742AC800B4072D761DDE7C14 /* [CP-User] Run Flutter Build my_flutter Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + name = "[CP-User] Run Flutter Build my_flutter Script"; + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "set -e\nset -u\nsource \"${SRCROOT}/my_flutter/.ios/Flutter/flutter_export_environment.sh\"\nexport VERBOSE_SCRIPT_LOGGING=1 && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/xcode_backend.sh build"; + }; + A3E7F43B3A5E735EC880C3EF /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-hybrid-flutter-app/Pods-hybrid-flutter-app-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-hybrid-flutter-app/Pods-hybrid-flutter-app-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-hybrid-flutter-app/Pods-hybrid-flutter-app-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + BE9EC8212CD1812F0026F537 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + BE9EC8392CD181310026F537 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 6A35F6E0DFACBBAB47D661EA /* Pods-hybrid-flutter-app.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = VF78H8LBT4; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "hybrid-flutter-app/Info.plist"; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UIMainStoryboardFile = Main; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.ahmedalaa.hybrid-flutter-app"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + BE9EC83A2CD181310026F537 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9F7459A16324C6347AA6DAFB /* Pods-hybrid-flutter-app.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = VF78H8LBT4; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "hybrid-flutter-app/Info.plist"; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UIMainStoryboardFile = Main; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.ahmedalaa.hybrid-flutter-app"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + BE9EC83B2CD181310026F537 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + BE9EC83C2CD181310026F537 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + BE9EC8202CD1812F0026F537 /* Build configuration list for PBXProject "hybrid-flutter-app" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + BE9EC83B2CD181310026F537 /* Debug */, + BE9EC83C2CD181310026F537 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + BE9EC8382CD181310026F537 /* Build configuration list for PBXNativeTarget "hybrid-flutter-app" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + BE9EC8392CD181310026F537 /* Debug */, + BE9EC83A2CD181310026F537 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = BE9EC81D2CD1812F0026F537 /* Project object */; +} diff --git a/packages/instabug_private_views/example-hybrid-ios-app/hybrid-flutter-app/AppDelegate.swift b/packages/instabug_private_views/example-hybrid-ios-app/hybrid-flutter-app/AppDelegate.swift new file mode 100644 index 000000000..9157e26ae --- /dev/null +++ b/packages/instabug_private_views/example-hybrid-ios-app/hybrid-flutter-app/AppDelegate.swift @@ -0,0 +1,41 @@ +// +// AppDelegate.swift +// hybrid-flutter-app +// +// Created by Ahmed alaa on 29/10/2024. +// + +import UIKit +import Flutter +import FlutterPluginRegistrant +@main +class AppDelegate: UIResponder, UIApplicationDelegate { + + var flutterEngine: FlutterEngine! + + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + flutterEngine = FlutterEngine(name: "my flutter engine") + flutterEngine.run() + GeneratedPluginRegistrant.register(with: flutterEngine) + + return true + } + + // MARK: UISceneSession Lifecycle + + func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { + // Called when a new scene session is being created. + // Use this method to select a configuration to create the new scene with. + return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) + } + + func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { + // Called when the user discards a scene session. + // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. + // Use this method to release any resources that were specific to the discarded scenes, as they will not return. + } + + +} + diff --git a/packages/instabug_private_views/example-hybrid-ios-app/hybrid-flutter-app/Assets.xcassets/AccentColor.colorset/Contents.json b/packages/instabug_private_views/example-hybrid-ios-app/hybrid-flutter-app/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 000000000..eb8789700 --- /dev/null +++ b/packages/instabug_private_views/example-hybrid-ios-app/hybrid-flutter-app/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/packages/instabug_private_views/example-hybrid-ios-app/hybrid-flutter-app/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/instabug_private_views/example-hybrid-ios-app/hybrid-flutter-app/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..230588010 --- /dev/null +++ b/packages/instabug_private_views/example-hybrid-ios-app/hybrid-flutter-app/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,35 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/packages/instabug_private_views/example-hybrid-ios-app/hybrid-flutter-app/Assets.xcassets/Contents.json b/packages/instabug_private_views/example-hybrid-ios-app/hybrid-flutter-app/Assets.xcassets/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/packages/instabug_private_views/example-hybrid-ios-app/hybrid-flutter-app/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/packages/instabug_private_views/example-hybrid-ios-app/hybrid-flutter-app/Base.lproj/LaunchScreen.storyboard b/packages/instabug_private_views/example-hybrid-ios-app/hybrid-flutter-app/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 000000000..865e9329f --- /dev/null +++ b/packages/instabug_private_views/example-hybrid-ios-app/hybrid-flutter-app/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/instabug_private_views/example-hybrid-ios-app/hybrid-flutter-app/Base.lproj/Main.storyboard b/packages/instabug_private_views/example-hybrid-ios-app/hybrid-flutter-app/Base.lproj/Main.storyboard new file mode 100644 index 000000000..ddecb9df6 --- /dev/null +++ b/packages/instabug_private_views/example-hybrid-ios-app/hybrid-flutter-app/Base.lproj/Main.storyboard @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/instabug_private_views/example-hybrid-ios-app/hybrid-flutter-app/Info.plist b/packages/instabug_private_views/example-hybrid-ios-app/hybrid-flutter-app/Info.plist new file mode 100644 index 000000000..dd3c9afda --- /dev/null +++ b/packages/instabug_private_views/example-hybrid-ios-app/hybrid-flutter-app/Info.plist @@ -0,0 +1,25 @@ + + + + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + + + + diff --git a/packages/instabug_private_views/example-hybrid-ios-app/hybrid-flutter-app/SceneDelegate.swift b/packages/instabug_private_views/example-hybrid-ios-app/hybrid-flutter-app/SceneDelegate.swift new file mode 100644 index 000000000..d41584377 --- /dev/null +++ b/packages/instabug_private_views/example-hybrid-ios-app/hybrid-flutter-app/SceneDelegate.swift @@ -0,0 +1,52 @@ +// +// SceneDelegate.swift +// hybrid-flutter-app +// +// Created by Ahmed alaa on 29/10/2024. +// + +import UIKit + +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + + var window: UIWindow? + + + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. + // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. + // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). + guard let _ = (scene as? UIWindowScene) else { return } + } + + func sceneDidDisconnect(_ scene: UIScene) { + // Called as the scene is being released by the system. + // This occurs shortly after the scene enters the background, or when its session is discarded. + // Release any resources associated with this scene that can be re-created the next time the scene connects. + // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). + } + + func sceneDidBecomeActive(_ scene: UIScene) { + // Called when the scene has moved from an inactive state to an active state. + // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. + } + + func sceneWillResignActive(_ scene: UIScene) { + // Called when the scene will move from an active state to an inactive state. + // This may occur due to temporary interruptions (ex. an incoming phone call). + } + + func sceneWillEnterForeground(_ scene: UIScene) { + // Called as the scene transitions from the background to the foreground. + // Use this method to undo the changes made on entering the background. + } + + func sceneDidEnterBackground(_ scene: UIScene) { + // Called as the scene transitions from the foreground to the background. + // Use this method to save data, release shared resources, and store enough scene-specific state information + // to restore the scene back to its current state. + } + + +} + diff --git a/packages/instabug_private_views/example-hybrid-ios-app/hybrid-flutter-app/ViewController.swift b/packages/instabug_private_views/example-hybrid-ios-app/hybrid-flutter-app/ViewController.swift new file mode 100644 index 000000000..49fc98fd1 --- /dev/null +++ b/packages/instabug_private_views/example-hybrid-ios-app/hybrid-flutter-app/ViewController.swift @@ -0,0 +1,29 @@ +// +// ViewController.swift +// hybrid-flutter-app +// +// Created by Ahmed alaa on 29/10/2024. +// + +import UIKit +import Flutter +class ViewController: UIViewController { + + @IBOutlet weak var flutterView: UIView! + override func viewDidLoad() { + super.viewDidLoad() + let appDelegate = UIApplication.shared.delegate as! AppDelegate + + + let flutterViewController = FlutterViewController(engine: appDelegate.flutterEngine, nibName: nil, bundle: nil) + + addChild(flutterViewController) + flutterViewController.view.frame = flutterView.bounds + flutterView.addSubview(flutterViewController.view) + flutterViewController.didMove(toParent: self) + // Do any additional setup after loading the view. + } + + +} + diff --git a/packages/instabug_private_views/example-hybrid-ios-app/init.sh b/packages/instabug_private_views/example-hybrid-ios-app/init.sh new file mode 100644 index 000000000..bbdbd30db --- /dev/null +++ b/packages/instabug_private_views/example-hybrid-ios-app/init.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +cd my_flutter +flutter pub get +cd ../ +pod install --repo-update \ No newline at end of file diff --git a/packages/instabug_private_views/example-hybrid-ios-app/my_flutter/.gitignore b/packages/instabug_private_views/example-hybrid-ios-app/my_flutter/.gitignore new file mode 100644 index 000000000..525e05cfb --- /dev/null +++ b/packages/instabug_private_views/example-hybrid-ios-app/my_flutter/.gitignore @@ -0,0 +1,49 @@ +.DS_Store +.dart_tool/ + +.pub/ + +.idea/ +.vagrant/ +.sconsign.dblite +.svn/ + +migrate_working_dir/ + +*.swp +profile + +DerivedData/ + +.generated/ + +*.pbxuser +*.mode1v3 +*.mode2v3 +*.perspectivev3 + +!default.pbxuser +!default.mode1v3 +!default.mode2v3 +!default.perspectivev3 + +xcuserdata + +*.moved-aside + +*.pyc +*sync/ +Icon? +.tags* + +build/ +.android/ +.ios/ +.flutter-plugins +.flutter-plugins-dependencies + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json diff --git a/packages/instabug_private_views/example-hybrid-ios-app/my_flutter/.metadata b/packages/instabug_private_views/example-hybrid-ios-app/my_flutter/.metadata new file mode 100644 index 000000000..1ff39d995 --- /dev/null +++ b/packages/instabug_private_views/example-hybrid-ios-app/my_flutter/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "603104015dd692ea3403755b55d07813d5cf8965" + channel: "stable" + +project_type: module diff --git a/packages/instabug_private_views/example-hybrid-ios-app/my_flutter/README.md b/packages/instabug_private_views/example-hybrid-ios-app/my_flutter/README.md new file mode 100644 index 000000000..12d679054 --- /dev/null +++ b/packages/instabug_private_views/example-hybrid-ios-app/my_flutter/README.md @@ -0,0 +1,11 @@ +# my_flutter + +A new Flutter module project. + +## Getting Started + +For help getting started with Flutter development, view the online +[documentation](https://flutter.dev/). + +For instructions integrating Flutter modules to your existing applications, +see the [add-to-app documentation](https://flutter.dev/to/add-to-app). diff --git a/packages/instabug_private_views/example-hybrid-ios-app/my_flutter/lib/main.dart b/packages/instabug_private_views/example-hybrid-ios-app/my_flutter/lib/main.dart new file mode 100644 index 000000000..7bb901132 --- /dev/null +++ b/packages/instabug_private_views/example-hybrid-ios-app/my_flutter/lib/main.dart @@ -0,0 +1,76 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:instabug_flutter/instabug_flutter.dart'; +import 'package:instabug_private_views/instabug_private_view.dart'; + +void main() { + runZonedGuarded( + () { + WidgetsFlutterBinding.ensureInitialized(); + + Instabug.init( + token: 'ed6f659591566da19b67857e1b9d40ab', + invocationEvents: [InvocationEvent.floatingButton], + debugLogsLevel: LogLevel.verbose, + ); + + FlutterError.onError = (FlutterErrorDetails details) { + Zone.current.handleUncaughtError(details.exception, details.stack!); + }; + + enableInstabugMaskingPrivateViews(); + runApp(const MyApp()); + }, + CrashReporting.reportCrash, + ); +} + +class MyApp extends StatelessWidget { + // This widget is the root of your application. + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Flutter Demo', + debugShowCheckedModeBanner: false, + theme: ThemeData( + primarySwatch: Colors.blue, + ), + home: const MyHomePage(title: 'Flutter View'), + ); + } + + const MyApp({super.key}); +} + +class MyHomePage extends StatefulWidget { + const MyHomePage({super.key, required this.title}); + + final String title; + + @override + State createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(widget.title), + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + InstabugPrivateView( + child: const Text( + 'Private text', + ), + ), + ], + ), + ), // This trailing comma makes auto-formatting nicer for build methods. + ); + } +} diff --git a/packages/instabug_private_views/example-hybrid-ios-app/my_flutter/pubspec.lock b/packages/instabug_private_views/example-hybrid-ios-app/my_flutter/pubspec.lock new file mode 100644 index 000000000..1e5288b3d --- /dev/null +++ b/packages/instabug_private_views/example-hybrid-ios-app/my_flutter/pubspec.lock @@ -0,0 +1,227 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + characters: + dependency: transitive + description: + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + clock: + dependency: transitive + description: + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" + source: hosted + version: "1.1.1" + collection: + dependency: transitive + description: + name: collection + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + url: "https://pub.dev" + source: hosted + version: "1.18.0" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 + url: "https://pub.dev" + source: hosted + version: "1.0.8" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + instabug_flutter: + dependency: "direct main" + description: + path: "../../../instabug_flutter" + relative: true + source: path + version: "14.0.0" + instabug_private_views: + dependency: "direct main" + description: + path: "../.." + relative: true + source: path + version: "1.0.1" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" + url: "https://pub.dev" + source: hosted + version: "10.0.5" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" + url: "https://pub.dev" + source: hosted + version: "3.0.5" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + lints: + dependency: transitive + description: + name: lints + sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + url: "https://pub.dev" + source: hosted + version: "0.12.16+1" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" + source: hosted + version: "0.11.1" + meta: + dependency: transitive + description: + name: meta + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + url: "https://pub.dev" + source: hosted + version: "1.15.0" + path: + dependency: transitive + description: + name: path + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + url: "https://pub.dev" + source: hosted + version: "1.9.0" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + url: "https://pub.dev" + source: hosted + version: "1.11.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + url: "https://pub.dev" + source: hosted + version: "2.1.2" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" + url: "https://pub.dev" + source: hosted + version: "0.7.2" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" + url: "https://pub.dev" + source: hosted + version: "14.2.5" +sdks: + dart: ">=3.3.0 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/packages/instabug_private_views/example-hybrid-ios-app/my_flutter/pubspec.yaml b/packages/instabug_private_views/example-hybrid-ios-app/my_flutter/pubspec.yaml new file mode 100644 index 000000000..5fc02c8cf --- /dev/null +++ b/packages/instabug_private_views/example-hybrid-ios-app/my_flutter/pubspec.yaml @@ -0,0 +1,80 @@ +name: my_flutter +description: "A new Flutter module project." + +version: 1.0.0+1 + +environment: + sdk: ">=2.12.0 <4.0.0" + +dependency_overrides: + instabug_flutter: + path: '../../../instabug_flutter' + +dependencies: + flutter: + sdk: flutter + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.8 + instabug_flutter: + path: '../../../instabug_flutter' + instabug_private_views: + path: '../../../instabug_private_views' +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^4.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +flutter: + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add Flutter specific assets to your application, add an assets section, + # like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/to/resolution-aware-images + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/to/asset-from-package + + # To add Flutter specific custom fonts to your application, add a fonts + # section here, in this "flutter" section. Each entry in this list should + # have a "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/to/font-from-package + + + # This section identifies your Flutter project as a module meant for + # embedding in a native host app. These identifiers should _not_ ordinarily + # be changed after generation - they are used to ensure that the tooling can + # maintain consistency when adding or modifying assets and plugins. + # They also do not have any bearing on your native host application's + # identifiers, which may be completely independent or the same as these. + module: + androidX: true + androidPackage: com.example.my_flutter + iosBundleIdentifier: com.example.myFlutter diff --git a/packages/instabug_private_views/example/.metadata b/packages/instabug_private_views/example/.metadata new file mode 100644 index 000000000..c2aa44bdb --- /dev/null +++ b/packages/instabug_private_views/example/.metadata @@ -0,0 +1,45 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "603104015dd692ea3403755b55d07813d5cf8965" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 603104015dd692ea3403755b55d07813d5cf8965 + base_revision: 603104015dd692ea3403755b55d07813d5cf8965 + - platform: android + create_revision: 603104015dd692ea3403755b55d07813d5cf8965 + base_revision: 603104015dd692ea3403755b55d07813d5cf8965 + - platform: ios + create_revision: 603104015dd692ea3403755b55d07813d5cf8965 + base_revision: 603104015dd692ea3403755b55d07813d5cf8965 + - platform: linux + create_revision: 603104015dd692ea3403755b55d07813d5cf8965 + base_revision: 603104015dd692ea3403755b55d07813d5cf8965 + - platform: macos + create_revision: 603104015dd692ea3403755b55d07813d5cf8965 + base_revision: 603104015dd692ea3403755b55d07813d5cf8965 + - platform: web + create_revision: 603104015dd692ea3403755b55d07813d5cf8965 + base_revision: 603104015dd692ea3403755b55d07813d5cf8965 + - platform: windows + create_revision: 603104015dd692ea3403755b55d07813d5cf8965 + base_revision: 603104015dd692ea3403755b55d07813d5cf8965 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/packages/instabug_private_views/example/README.md b/packages/instabug_private_views/example/README.md new file mode 100644 index 000000000..81240d7ca --- /dev/null +++ b/packages/instabug_private_views/example/README.md @@ -0,0 +1,16 @@ +# instabug_private_views_example + +Demonstrates how to use the instabug_private_views plugin. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/packages/instabug_private_views/example/android/app/build.gradle b/packages/instabug_private_views/example/android/app/build.gradle new file mode 100644 index 000000000..a3103874a --- /dev/null +++ b/packages/instabug_private_views/example/android/app/build.gradle @@ -0,0 +1,44 @@ +plugins { + id "com.android.application" + id "kotlin-android" + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id "dev.flutter.flutter-gradle-plugin" +} + +android { + namespace = "com.instabug.instabug_private_views_example" + compileSdk = 34 + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8 + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.instabug.instabug_private_views_example" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.debug + } + } +} + +flutter { + source = "../.." +} diff --git a/packages/instabug_private_views/example/android/app/src/debug/AndroidManifest.xml b/packages/instabug_private_views/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 000000000..399f6981d --- /dev/null +++ b/packages/instabug_private_views/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/packages/instabug_private_views/example/android/app/src/main/AndroidManifest.xml b/packages/instabug_private_views/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000..1a4340728 --- /dev/null +++ b/packages/instabug_private_views/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/instabug_private_views/example/android/app/src/main/java/com/instabug/instabug_private_views_example/MainActivity.java b/packages/instabug_private_views/example/android/app/src/main/java/com/instabug/instabug_private_views_example/MainActivity.java new file mode 100644 index 000000000..bd7b0511b --- /dev/null +++ b/packages/instabug_private_views/example/android/app/src/main/java/com/instabug/instabug_private_views_example/MainActivity.java @@ -0,0 +1,6 @@ +package com.instabug.instabug_private_views_example; + +import io.flutter.embedding.android.FlutterActivity; + +public class MainActivity extends FlutterActivity { +} diff --git a/packages/instabug_private_views/example/android/app/src/main/kotlin/com/instabug/example/MainActivity.kt b/packages/instabug_private_views/example/android/app/src/main/kotlin/com/instabug/example/MainActivity.kt new file mode 100644 index 000000000..3759d1c71 --- /dev/null +++ b/packages/instabug_private_views/example/android/app/src/main/kotlin/com/instabug/example/MainActivity.kt @@ -0,0 +1,5 @@ +package com.instabug.example + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() diff --git a/packages/instabug_private_views/example/android/app/src/main/res/drawable-v21/launch_background.xml b/packages/instabug_private_views/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 000000000..f74085f3f --- /dev/null +++ b/packages/instabug_private_views/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/instabug_private_views/example/android/app/src/main/res/drawable/launch_background.xml b/packages/instabug_private_views/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 000000000..304732f88 --- /dev/null +++ b/packages/instabug_private_views/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/instabug_private_views/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/instabug_private_views/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..db77bb4b7 Binary files /dev/null and b/packages/instabug_private_views/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/packages/instabug_private_views/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/instabug_private_views/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..17987b79b Binary files /dev/null and b/packages/instabug_private_views/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/packages/instabug_private_views/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/instabug_private_views/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..09d439148 Binary files /dev/null and b/packages/instabug_private_views/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/packages/instabug_private_views/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/instabug_private_views/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..d5f1c8d34 Binary files /dev/null and b/packages/instabug_private_views/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/packages/instabug_private_views/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/instabug_private_views/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..4d6372eeb Binary files /dev/null and b/packages/instabug_private_views/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/packages/instabug_private_views/example/android/app/src/main/res/values-night/styles.xml b/packages/instabug_private_views/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 000000000..06952be74 --- /dev/null +++ b/packages/instabug_private_views/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/instabug_private_views/example/android/app/src/main/res/values/styles.xml b/packages/instabug_private_views/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 000000000..cb1ef8805 --- /dev/null +++ b/packages/instabug_private_views/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/instabug_private_views/example/android/app/src/profile/AndroidManifest.xml b/packages/instabug_private_views/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 000000000..399f6981d --- /dev/null +++ b/packages/instabug_private_views/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/packages/instabug_private_views/example/android/build.gradle b/packages/instabug_private_views/example/android/build.gradle new file mode 100644 index 000000000..3b7cbec9f --- /dev/null +++ b/packages/instabug_private_views/example/android/build.gradle @@ -0,0 +1,24 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = "../build" +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(":app") + tasks.withType(Test) { + // Prevent tests in moduleA from running + if (project.name == 'video_player_android') { + exclude '**/*' + } + } +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} \ No newline at end of file diff --git a/packages/instabug_private_views/example/android/gradle.properties b/packages/instabug_private_views/example/android/gradle.properties new file mode 100644 index 000000000..259717082 --- /dev/null +++ b/packages/instabug_private_views/example/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true +android.enableJetifier=true diff --git a/packages/instabug_private_views/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/instabug_private_views/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..7bb2df6ba --- /dev/null +++ b/packages/instabug_private_views/example/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip diff --git a/packages/instabug_private_views/example/android/gradlew b/packages/instabug_private_views/example/android/gradlew new file mode 100755 index 000000000..9d82f7891 --- /dev/null +++ b/packages/instabug_private_views/example/android/gradlew @@ -0,0 +1,160 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/packages/instabug_private_views/example/android/gradlew.bat b/packages/instabug_private_views/example/android/gradlew.bat new file mode 100644 index 000000000..8a0b282aa --- /dev/null +++ b/packages/instabug_private_views/example/android/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/packages/instabug_private_views/example/android/settings.gradle b/packages/instabug_private_views/example/android/settings.gradle new file mode 100644 index 000000000..b9e43bd37 --- /dev/null +++ b/packages/instabug_private_views/example/android/settings.gradle @@ -0,0 +1,25 @@ +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + }() + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "8.1.0" apply false + id "org.jetbrains.kotlin.android" version "1.8.22" apply false +} + +include ":app" diff --git a/packages/instabug_private_views/example/assets/img.png b/packages/instabug_private_views/example/assets/img.png new file mode 100644 index 000000000..fff04770f Binary files /dev/null and b/packages/instabug_private_views/example/assets/img.png differ diff --git a/packages/instabug_private_views/example/ios/Flutter/AppFrameworkInfo.plist b/packages/instabug_private_views/example/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 000000000..7c5696400 --- /dev/null +++ b/packages/instabug_private_views/example/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 12.0 + + diff --git a/packages/instabug_private_views/example/ios/Flutter/Debug.xcconfig b/packages/instabug_private_views/example/ios/Flutter/Debug.xcconfig new file mode 100644 index 000000000..ec97fc6f3 --- /dev/null +++ b/packages/instabug_private_views/example/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/packages/instabug_private_views/example/ios/Flutter/Release.xcconfig b/packages/instabug_private_views/example/ios/Flutter/Release.xcconfig new file mode 100644 index 000000000..c4855bfe2 --- /dev/null +++ b/packages/instabug_private_views/example/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/packages/instabug_private_views/example/ios/Podfile b/packages/instabug_private_views/example/ios/Podfile new file mode 100644 index 000000000..8b43e9c1b --- /dev/null +++ b/packages/instabug_private_views/example/ios/Podfile @@ -0,0 +1,46 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '12.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! +pod 'Instabug', :podspec => 'https://ios-releases.instabug.com/custom/feature-flutter-private-views-base/14.0.0/Instabug.podspec' + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + pod 'OCMock', '3.6' + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/packages/instabug_private_views/example/ios/Podfile.lock b/packages/instabug_private_views/example/ios/Podfile.lock new file mode 100644 index 000000000..4ec18dae9 --- /dev/null +++ b/packages/instabug_private_views/example/ios/Podfile.lock @@ -0,0 +1,49 @@ +PODS: + - Flutter (1.0.0) + - Instabug (14.0.0) + - instabug_flutter (14.0.0): + - Flutter + - Instabug (= 14.0.0) + - instabug_private_views (0.0.1): + - Flutter + - instabug_flutter + - OCMock (3.6) + - video_player_avfoundation (0.0.1): + - Flutter + - FlutterMacOS + +DEPENDENCIES: + - Flutter (from `Flutter`) + - Instabug (from `https://ios-releases.instabug.com/custom/feature-flutter-private-views-base/14.0.0/Instabug.podspec`) + - instabug_flutter (from `.symlinks/plugins/instabug_flutter/ios`) + - instabug_private_views (from `.symlinks/plugins/instabug_private_views/ios`) + - OCMock (= 3.6) + - video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`) + +SPEC REPOS: + trunk: + - OCMock + +EXTERNAL SOURCES: + Flutter: + :path: Flutter + Instabug: + :podspec: https://ios-releases.instabug.com/custom/feature-flutter-private-views-base/14.0.0/Instabug.podspec + instabug_flutter: + :path: ".symlinks/plugins/instabug_flutter/ios" + instabug_private_views: + :path: ".symlinks/plugins/instabug_private_views/ios" + video_player_avfoundation: + :path: ".symlinks/plugins/video_player_avfoundation/darwin" + +SPEC CHECKSUMS: + Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 + Instabug: 9d2b06afbadfbd4630bc0116dc27d84360ed70b0 + instabug_flutter: ff8ab5ff34a476b1d2d887478ec190cda962b973 + instabug_private_views: df53ff3f1cc842cb686d43e077099d3b36426a7f + OCMock: 5ea90566be239f179ba766fd9fbae5885040b992 + video_player_avfoundation: 7c6c11d8470e1675df7397027218274b6d2360b3 + +PODFILE CHECKSUM: 38cf660255aba2d321a6f139341d5a867e19b769 + +COCOAPODS: 1.14.3 diff --git a/packages/instabug_private_views/example/ios/Runner.xcodeproj/project.pbxproj b/packages/instabug_private_views/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 000000000..ef3f7beed --- /dev/null +++ b/packages/instabug_private_views/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,761 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 4EA625EC1B1293615B30A623 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92C8C175664DB55D5FE2AE9D /* Pods_RunnerTests.framework */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 8115939F17BE56A387B25531 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 15AA449B734113B9F4617C0E /* Pods_Runner.framework */; }; + 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 */; }; + BE133A632CD831A500FEADB5 /* PrivateViewHostApiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = BE133A622CD831A500FEADB5 /* PrivateViewHostApiTests.m */; }; + BE9F8D392CD5C068003ADA97 /* PrivateViewApiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = BE9F8D382CD5C068003ADA97 /* PrivateViewApiTests.m */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 0303582970644CA58D276892 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 15AA449B734113B9F4617C0E /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 3F54E317D7EFCD6FA9EBC039 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 820D7973B5497175631FF34A /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 8801EEBF9F9D8EE99D4ED5A2 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + 92C8C175664DB55D5FE2AE9D /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 99B4BB79D25D93036AAF6480 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + A1871ACDFF6D3EA7210660EF /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + BE133A622CD831A500FEADB5 /* PrivateViewHostApiTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PrivateViewHostApiTests.m; sourceTree = ""; }; + BE9F8D382CD5C068003ADA97 /* PrivateViewApiTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PrivateViewApiTests.m; sourceTree = ""; }; + BE9F8D3A2CD5C06A003ADA97 /* RunnerTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RunnerTests-Bridging-Header.h"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 8115939F17BE56A387B25531 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C7F1472ADA1CCAD68225BF65 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 4EA625EC1B1293615B30A623 /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + BE9F8D382CD5C068003ADA97 /* PrivateViewApiTests.m */, + BE9F8D3A2CD5C06A003ADA97 /* RunnerTests-Bridging-Header.h */, + BE133A622CD831A500FEADB5 /* PrivateViewHostApiTests.m */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 51A32382AC0867EEF3CF4501 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 15AA449B734113B9F4617C0E /* Pods_Runner.framework */, + 92C8C175664DB55D5FE2AE9D /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 940EE8988085F83C8461DB17 /* Pods */ = { + isa = PBXGroup; + children = ( + 820D7973B5497175631FF34A /* Pods-Runner.debug.xcconfig */, + 99B4BB79D25D93036AAF6480 /* Pods-Runner.release.xcconfig */, + 0303582970644CA58D276892 /* Pods-Runner.profile.xcconfig */, + 8801EEBF9F9D8EE99D4ED5A2 /* Pods-RunnerTests.debug.xcconfig */, + A1871ACDFF6D3EA7210660EF /* Pods-RunnerTests.release.xcconfig */, + 3F54E317D7EFCD6FA9EBC039 /* Pods-RunnerTests.profile.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + 940EE8988085F83C8461DB17 /* Pods */, + 51A32382AC0867EEF3CF4501 /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 5D0C51123159955CBB75DD3C /* [CP] Check Pods Manifest.lock */, + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + C7F1472ADA1CCAD68225BF65 /* Frameworks */, + 40EB49289482643018B2EF40 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + FA0379807AF94B93BE4FA182 /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 589206114E198C940A065FDE /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + LastSwiftMigration = 1600; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 40EB49289482643018B2EF40 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-RunnerTests/Pods-RunnerTests-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-RunnerTests/Pods-RunnerTests-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-RunnerTests/Pods-RunnerTests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 589206114E198C940A065FDE /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 5D0C51123159955CBB75DD3C /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; + FA0379807AF94B93BE4FA182 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + BE9F8D392CD5C068003ADA97 /* PrivateViewApiTests.m in Sources */, + BE133A632CD831A500FEADB5 /* PrivateViewHostApiTests.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 4574PQBJA9; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.instabug.example; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 8801EEBF9F9D8EE99D4ED5A2 /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.instabug.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OBJC_BRIDGING_HEADER = "RunnerTests/RunnerTests-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A1871ACDFF6D3EA7210660EF /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.instabug.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "RunnerTests/RunnerTests-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 3F54E317D7EFCD6FA9EBC039 /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.instabug.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "RunnerTests/RunnerTests-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 4574PQBJA9; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.instabug.example; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 4574PQBJA9; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.instabug.example; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/packages/instabug_private_views/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/instabug_private_views/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/packages/instabug_private_views/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/instabug_private_views/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/instabug_private_views/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/packages/instabug_private_views/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/instabug_private_views/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/instabug_private_views/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..f9b0d7c5e --- /dev/null +++ b/packages/instabug_private_views/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/instabug_private_views/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/instabug_private_views/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 000000000..8e3ca5dfe --- /dev/null +++ b/packages/instabug_private_views/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/instabug_private_views/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/instabug_private_views/example/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..21a3cc14c --- /dev/null +++ b/packages/instabug_private_views/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/packages/instabug_private_views/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/instabug_private_views/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/packages/instabug_private_views/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/instabug_private_views/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/instabug_private_views/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..f9b0d7c5e --- /dev/null +++ b/packages/instabug_private_views/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/instabug_private_views/example/ios/Runner/AppDelegate.swift b/packages/instabug_private_views/example/ios/Runner/AppDelegate.swift new file mode 100644 index 000000000..626664468 --- /dev/null +++ b/packages/instabug_private_views/example/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import Flutter +import UIKit + +@main +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..d36b1fab2 --- /dev/null +++ b/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 000000000..dc9ada472 Binary files /dev/null and b/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 000000000..7353c41ec Binary files /dev/null and b/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 000000000..797d452e4 Binary files /dev/null and b/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 000000000..6ed2d933e Binary files /dev/null and b/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 000000000..4cd7b0099 Binary files /dev/null and b/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 000000000..fe730945a Binary files /dev/null and b/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 000000000..321773cd8 Binary files /dev/null and b/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 000000000..797d452e4 Binary files /dev/null and b/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 000000000..502f463a9 Binary files /dev/null and b/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 000000000..0ec303439 Binary files /dev/null and b/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 000000000..0ec303439 Binary files /dev/null and b/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 000000000..e9f5fea27 Binary files /dev/null and b/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 000000000..84ac32ae7 Binary files /dev/null and b/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 000000000..8953cba09 Binary files /dev/null and b/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 000000000..0467bf12a Binary files /dev/null and b/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 000000000..0bedcf2fd --- /dev/null +++ b/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 000000000..9da19eaca Binary files /dev/null and b/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 000000000..9da19eaca Binary files /dev/null and b/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 000000000..9da19eaca Binary files /dev/null and b/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 000000000..89c2725b7 --- /dev/null +++ b/packages/instabug_private_views/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/packages/instabug_private_views/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/instabug_private_views/example/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 000000000..f2e259c7c --- /dev/null +++ b/packages/instabug_private_views/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/instabug_private_views/example/ios/Runner/Base.lproj/Main.storyboard b/packages/instabug_private_views/example/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 000000000..f3c28516f --- /dev/null +++ b/packages/instabug_private_views/example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/instabug_private_views/example/ios/Runner/Info.plist b/packages/instabug_private_views/example/ios/Runner/Info.plist new file mode 100644 index 000000000..5458fc418 --- /dev/null +++ b/packages/instabug_private_views/example/ios/Runner/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Example + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + example + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + + diff --git a/packages/instabug_private_views/example/ios/Runner/Runner-Bridging-Header.h b/packages/instabug_private_views/example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 000000000..308a2a560 --- /dev/null +++ b/packages/instabug_private_views/example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/packages/instabug_private_views/example/ios/RunnerTests/PrivateViewApiTests.m b/packages/instabug_private_views/example/ios/RunnerTests/PrivateViewApiTests.m new file mode 100644 index 000000000..4d7aa8a65 --- /dev/null +++ b/packages/instabug_private_views/example/ios/RunnerTests/PrivateViewApiTests.m @@ -0,0 +1,206 @@ +#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([InstabugPrivateViewFlutterApi 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); + + UIWindow* testWindow = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; + [testWindow addSubview: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); + CGRect rect = [converted[0] CGRectValue]; + XCTAssertTrue(CGRectEqualToRect(rect, CGRectMake(10, 20, 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/packages/instabug_private_views/example/ios/RunnerTests/PrivateViewHostApiTests.m b/packages/instabug_private_views/example/ios/RunnerTests/PrivateViewHostApiTests.m new file mode 100644 index 000000000..8cbe328ea --- /dev/null +++ b/packages/instabug_private_views/example/ios/RunnerTests/PrivateViewHostApiTests.m @@ -0,0 +1,57 @@ +#import +#import +#import +#import "PrivateViewApi.h" +#import "instabug_flutter/InstabugApi.h" +#import "PrivateViewHostApi.h" + +@interface PrivateViewHostApiTests : XCTestCase + +@property (nonatomic, strong) PrivateViewHostApi *api; +@property (nonatomic, strong) id privateViewApiMock; + +@end + +@implementation PrivateViewHostApiTests + +- (void)setUp { + [super setUp]; + + // Set up a mock for PrivateViewApi + self.privateViewApiMock = OCMClassMock([PrivateViewApi class]); + + // Initialize the PrivateViewHostApi instance + self.api = [[PrivateViewHostApi alloc] init]; + self.api.privateViewApi = self.privateViewApiMock; +} + +- (void)tearDown { + self.api = nil; + self.privateViewApiMock = nil; + [super tearDown]; +} + +- (void)testInitWithError_setsScreenshotMaskingHandler { + // Define an expectation for the screenshot masking handler + UIImage *mockScreenshot = [[UIImage alloc] init]; + UIImage *mockMaskedImage = [[UIImage alloc] init]; + FlutterError *error = nil; + + + + OCMStub([self.privateViewApiMock mask:mockScreenshot completion:([OCMArg invokeBlockWithArgs:mockMaskedImage, nil])]); + + + // Call initWithError and set up the screenshot masking handler + [self.api initWithError:&error]; + + // Invoke the screenshot masking handler + void (^completionHandler)(UIImage * _Nullable) = ^(UIImage * _Nullable maskedImage) { + XCTAssertEqual(maskedImage, mockMaskedImage, @"The masked image should be returned by the completion handler."); + }; + [InstabugApi setScreenshotMaskingHandler:^(UIImage * _Nonnull screenshot, void (^ _Nonnull completion)(UIImage * _Nullable)) { + completionHandler(screenshot); + }]; +} + +@end diff --git a/packages/instabug_private_views/example/ios/RunnerTests/RunnerTests-Bridging-Header.h b/packages/instabug_private_views/example/ios/RunnerTests/RunnerTests-Bridging-Header.h new file mode 100644 index 000000000..1b2cb5d6d --- /dev/null +++ b/packages/instabug_private_views/example/ios/RunnerTests/RunnerTests-Bridging-Header.h @@ -0,0 +1,4 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + diff --git a/packages/instabug_private_views/example/lib/main.dart b/packages/instabug_private_views/example/lib/main.dart new file mode 100644 index 000000000..9ae8442dd --- /dev/null +++ b/packages/instabug_private_views/example/lib/main.dart @@ -0,0 +1,29 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:instabug_flutter/instabug_flutter.dart'; +import 'package:instabug_private_views/instabug_private_view.dart'; + +import 'package:instabug_private_views_example/private_view_page.dart'; + +void main() { + runZonedGuarded( + () { + WidgetsFlutterBinding.ensureInitialized(); + + Instabug.init( + token: 'ed6f659591566da19b67857e1b9d40ab', + invocationEvents: [InvocationEvent.floatingButton], + debugLogsLevel: LogLevel.verbose, + ); + + FlutterError.onError = (FlutterErrorDetails details) { + Zone.current.handleUncaughtError(details.exception, details.stack!); + }; + + enableInstabugMaskingPrivateViews(); + runApp(const PrivateViewPage()); + }, + CrashReporting.reportCrash, + ); +} diff --git a/packages/instabug_private_views/example/lib/private_view_page.dart b/packages/instabug_private_views/example/lib/private_view_page.dart new file mode 100644 index 000000000..6178f1b24 --- /dev/null +++ b/packages/instabug_private_views/example/lib/private_view_page.dart @@ -0,0 +1,133 @@ +import 'package:flutter/material.dart'; +import 'package:instabug_private_views/instabug_private_view.dart'; +import 'package:video_player/video_player.dart'; + +class PrivateViewPage extends StatefulWidget { + const PrivateViewPage({Key? key}) : super(key: key); + + @override + _PrivateViewPageState createState() => _PrivateViewPageState(); +} + +class _PrivateViewPageState extends State { + late VideoPlayerController _controller; + + @override + void initState() { + super.initState(); + _controller = VideoPlayerController.networkUrl( + Uri.parse( + 'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4'), + )..initialize().then((_) { + setState(() {}); + }); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return MaterialApp( + debugShowCheckedModeBanner: false, + home: Scaffold( + appBar: AppBar(title: const Text("Private Views page")), + body: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const SizedBox(height: 16), + InstabugPrivateView( + child: const Text( + 'Private TextView', + style: TextStyle(fontSize: 18), + textAlign: TextAlign.center, + ), + ), + const SizedBox(height: 16), + InstabugPrivateView( + child: ElevatedButton( + onPressed: () { + const snackBar = SnackBar( + content: Text('Hello, you clicked on a private button'), + ); + ScaffoldMessenger.of(context).showSnackBar(snackBar); + }, + child: const Text('I am a private button'), + ), + ), + const SizedBox(height: 16), + InstabugPrivateView( + child: Image.asset( + 'assets/img.png', + // Add this image to your assets folder + height: 100, + ), + ), + const SizedBox(height: 33), + InstabugPrivateView( + child: const TextField( + obscureText: true, + decoration: InputDecoration( + hintText: 'password', + labelText: 'Password', + border: OutlineInputBorder(), + ), + ), + ), + const SizedBox(height: 16), + const TextField( + keyboardType: TextInputType.emailAddress, + decoration: InputDecoration( + hintText: 'Email', + labelText: 'Email', + border: OutlineInputBorder(), + ), + ), + const SizedBox(height: 24), + InstabugPrivateView( + child: Container( + height: 300, + child: _controller.value.isInitialized + ? AspectRatio( + aspectRatio: _controller.value.aspectRatio, + child: VideoPlayer(_controller), + ) + : const Center(child: CircularProgressIndicator()), + ), + ), + const SizedBox(height: 24), + const SizedBox(height: 24), + const SizedBox(height: 24), + SizedBox( + height: 200, + child: CustomScrollView( + slivers: [ + InstabugSliverPrivateView( + sliver: SliverToBoxAdapter( + child: Container( + color: Colors.red, + child: const Text( + "Private Sliver Widget", + style: TextStyle(fontSize: 18), + textAlign: TextAlign.center, + ), + ), + ), + ) + ], + ), + ) + ], + ), + ), + ), + ), + ); + } +} diff --git a/packages/instabug_private_views/example/lib/widget/instabug_button.dart b/packages/instabug_private_views/example/lib/widget/instabug_button.dart new file mode 100644 index 000000000..97e434061 --- /dev/null +++ b/packages/instabug_private_views/example/lib/widget/instabug_button.dart @@ -0,0 +1,48 @@ +import 'package:flutter/material.dart'; + +class InstabugButton extends StatelessWidget { + const InstabugButton({ + Key? key, + required this.text, + this.onPressed, + this.fontSize, + this.margin, + }) : super(key: key); + + const InstabugButton.smallFontSize({ + Key? key, + required this.text, + this.onPressed, + this.fontSize = 10.0, + this.margin, + }) : super(key: key); + + final String text; + final Function()? onPressed; + final double? fontSize; + + final EdgeInsetsGeometry? margin; + + @override + Widget build(BuildContext context) { + return Container( + width: double.infinity, + margin: margin ?? + const EdgeInsets.symmetric( + horizontal: 20.0, + ), + child: ElevatedButton( + onPressed: onPressed, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.lightBlue, + foregroundColor: Colors.white, + textStyle: Theme.of(context) + .textTheme + .labelLarge + ?.copyWith(fontSize: fontSize), + ), + child: Text(text), + ), + ); + } +} diff --git a/packages/instabug_private_views/example/lib/widget/instabug_text_field.dart b/packages/instabug_private_views/example/lib/widget/instabug_text_field.dart new file mode 100644 index 000000000..3d01cc623 --- /dev/null +++ b/packages/instabug_private_views/example/lib/widget/instabug_text_field.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; + +class InstabugTextField extends StatelessWidget { + const InstabugTextField({ + Key? key, + required this.label, + required this.controller, + this.labelStyle, + this.margin, + this.keyboardType, + this.validator, + }) : super(key: key); + + final String label; + final TextEditingController controller; + final EdgeInsetsGeometry? margin; + final TextStyle? labelStyle; + final TextInputType? keyboardType; + final FormFieldValidator? validator; + + @override + Widget build(BuildContext context) { + return Container( + margin: margin ?? + const EdgeInsets.symmetric( + horizontal: 20.0, + ), + child: TextFormField( + controller: controller, + keyboardType: keyboardType, + validator: validator, + decoration: InputDecoration( + labelText: label, + labelStyle: labelStyle ?? Theme.of(context).textTheme.labelLarge, + suffixIcon: IconButton( + onPressed: controller.clear, + iconSize: 12.0, + icon: const Icon( + Icons.clear, + ), + ), + ), + ), + ); + } +} diff --git a/packages/instabug_private_views/example/lib/widget/section_title.dart b/packages/instabug_private_views/example/lib/widget/section_title.dart new file mode 100644 index 000000000..2c0509fa8 --- /dev/null +++ b/packages/instabug_private_views/example/lib/widget/section_title.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; + +class SectionTitle extends StatelessWidget { + final String text; + + const SectionTitle(this.text, {Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + alignment: Alignment.centerLeft, + margin: const EdgeInsets.only(top: 20.0, left: 20.0), + child: Text( + text, + textAlign: TextAlign.left, + style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + ), + ); + } +} diff --git a/packages/instabug_private_views/example/pubspec.lock b/packages/instabug_private_views/example/pubspec.lock new file mode 100644 index 000000000..5e8a12aa4 --- /dev/null +++ b/packages/instabug_private_views/example/pubspec.lock @@ -0,0 +1,304 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + characters: + dependency: transitive + description: + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + clock: + dependency: transitive + description: + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" + source: hosted + version: "1.1.1" + collection: + dependency: transitive + description: + name: collection + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + url: "https://pub.dev" + source: hosted + version: "1.18.0" + csslib: + dependency: transitive + description: + name: csslib + sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" + url: "https://pub.dev" + source: hosted + version: "1.0.2" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 + url: "https://pub.dev" + source: hosted + version: "1.0.8" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + html: + dependency: transitive + description: + name: html + sha256: "1fc58edeaec4307368c60d59b7e15b9d658b57d7f3125098b6294153c75337ec" + url: "https://pub.dev" + source: hosted + version: "0.15.5" + instabug_flutter: + dependency: "direct overridden" + description: + path: "../../instabug_flutter" + relative: true + source: path + version: "14.0.0" + instabug_private_views: + dependency: "direct main" + description: + path: ".." + relative: true + source: path + version: "1.0.1" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" + url: "https://pub.dev" + source: hosted + version: "10.0.5" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" + url: "https://pub.dev" + source: hosted + version: "3.0.5" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + lints: + dependency: transitive + description: + name: lints + sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + url: "https://pub.dev" + source: hosted + version: "0.12.16+1" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" + source: hosted + version: "0.11.1" + meta: + dependency: transitive + description: + name: meta + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + url: "https://pub.dev" + source: hosted + version: "1.15.0" + path: + dependency: transitive + description: + name: path + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + url: "https://pub.dev" + source: hosted + version: "1.9.0" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + url: "https://pub.dev" + source: hosted + version: "1.11.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + url: "https://pub.dev" + source: hosted + version: "2.1.2" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" + url: "https://pub.dev" + source: hosted + version: "0.7.2" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + video_player: + dependency: "direct main" + description: + name: video_player + sha256: "4a8c3492d734f7c39c2588a3206707a05ee80cef52e8c7f3b2078d430c84bc17" + url: "https://pub.dev" + source: hosted + version: "2.9.2" + video_player_android: + dependency: transitive + description: + name: video_player_android + sha256: "391e092ba4abe2f93b3e625bd6b6a6ec7d7414279462c1c0ee42b5ab8d0a0898" + url: "https://pub.dev" + source: hosted + version: "2.7.16" + video_player_avfoundation: + dependency: transitive + description: + name: video_player_avfoundation + sha256: "0b146e5d82e886ff43e5a46c6bcbe390761b802864a6e2503eb612d69a405dfa" + url: "https://pub.dev" + source: hosted + version: "2.6.3" + video_player_platform_interface: + dependency: transitive + description: + name: video_player_platform_interface + sha256: "229d7642ccd9f3dc4aba169609dd6b5f3f443bb4cc15b82f7785fcada5af9bbb" + url: "https://pub.dev" + source: hosted + version: "6.2.3" + video_player_web: + dependency: transitive + description: + name: video_player_web + sha256: "881b375a934d8ebf868c7fb1423b2bfaa393a0a265fa3f733079a86536064a10" + url: "https://pub.dev" + source: hosted + version: "2.3.3" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" + url: "https://pub.dev" + source: hosted + version: "14.2.5" + web: + dependency: transitive + description: + name: web + sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb + url: "https://pub.dev" + source: hosted + version: "1.1.0" +sdks: + dart: ">=3.5.0 <4.0.0" + flutter: ">=3.24.0" diff --git a/packages/instabug_private_views/example/pubspec.yaml b/packages/instabug_private_views/example/pubspec.yaml new file mode 100644 index 000000000..8e9d153ee --- /dev/null +++ b/packages/instabug_private_views/example/pubspec.yaml @@ -0,0 +1,68 @@ +name: instabug_private_views_example +description: "Demonstrates how to use the instabug_private_views plugin." +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +environment: + sdk: ">=2.12.0 <4.0.0" + +dependencies: + flutter: + sdk: flutter + + instabug_private_views: + path: '../' + + cupertino_icons: ^1.0.8 + video_player: + +dev_dependencies: + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^4.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + assets: + - assets/img.png + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/to/resolution-aware-images + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/to/asset-from-package + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/to/font-from-package diff --git a/packages/instabug_private_views/ios/.gitignore b/packages/instabug_private_views/ios/.gitignore new file mode 100644 index 000000000..034771fc9 --- /dev/null +++ b/packages/instabug_private_views/ios/.gitignore @@ -0,0 +1,38 @@ +.idea/ +.vagrant/ +.sconsign.dblite +.svn/ + +.DS_Store +*.swp +profile + +DerivedData/ +build/ +GeneratedPluginRegistrant.h +GeneratedPluginRegistrant.m + +.generated/ + +*.pbxuser +*.mode1v3 +*.mode2v3 +*.perspectivev3 + +!default.pbxuser +!default.mode1v3 +!default.mode2v3 +!default.perspectivev3 + +xcuserdata + +*.moved-aside + +*.pyc +*sync/ +Icon? +.tags* + +/Flutter/Generated.xcconfig +/Flutter/ephemeral/ +/Flutter/flutter_export_environment.sh diff --git a/packages/instabug_private_views/ios/Assets/.gitkeep b/packages/instabug_private_views/ios/Assets/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/packages/instabug_private_views/ios/Classes/InstabugPrivateViewsPlugin.h b/packages/instabug_private_views/ios/Classes/InstabugPrivateViewsPlugin.h new file mode 100644 index 000000000..f1ab588ce --- /dev/null +++ b/packages/instabug_private_views/ios/Classes/InstabugPrivateViewsPlugin.h @@ -0,0 +1,4 @@ +#import + +@interface InstabugPrivateViewsPlugin : NSObject +@end diff --git a/packages/instabug_private_views/ios/Classes/InstabugPrivateViewsPlugin.m b/packages/instabug_private_views/ios/Classes/InstabugPrivateViewsPlugin.m new file mode 100644 index 000000000..833cea6de --- /dev/null +++ b/packages/instabug_private_views/ios/Classes/InstabugPrivateViewsPlugin.m @@ -0,0 +1,13 @@ +#import "InstabugPrivateViewsPlugin.h" +#import "PrivateViewApi.h" +#import "PrivateViewHostApi.h" +@implementation InstabugPrivateViewsPlugin ++ (void)registerWithRegistrar:(NSObject*)registrar { + PrivateViewApi* privateViewApi = InitPrivateViewApi([registrar messenger],registrar); + InitPrivateViewApi([registrar messenger], registrar); + InitPrivateViewHostApi([registrar messenger], privateViewApi); + +} + + +@end diff --git a/packages/instabug_private_views/ios/Classes/Modules/PrivateViewApi.h b/packages/instabug_private_views/ios/Classes/Modules/PrivateViewApi.h new file mode 100644 index 000000000..80aa0bd36 --- /dev/null +++ b/packages/instabug_private_views/ios/Classes/Modules/PrivateViewApi.h @@ -0,0 +1,34 @@ +#import +#import "InstabugPrivateViewPigeon.h" +#import + + +@interface PrivateViewApi : NSObject + +@property (nonatomic, strong) InstabugPrivateViewFlutterApi *flutterApi; +@property (nonatomic, strong) NSObject * flutterEngineRegistrar; + +- (instancetype)initWithFlutterApi:(InstabugPrivateViewFlutterApi *)api + registrar:(NSObject *)registrar; + +- (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, + NSObject *flutterEngineRegistrar +); + diff --git a/packages/instabug_private_views/ios/Classes/Modules/PrivateViewApi.m b/packages/instabug_private_views/ios/Classes/Modules/PrivateViewApi.m new file mode 100644 index 000000000..3b3a8cf7c --- /dev/null +++ b/packages/instabug_private_views/ios/Classes/Modules/PrivateViewApi.m @@ -0,0 +1,133 @@ +#import "PrivateViewApi.h" +#import "FlutterPluginRegistrar+FlutterEngine.h" + +extern PrivateViewApi* InitPrivateViewApi( + id messenger, + NSObject *flutterEngineRegistrar +) { + InstabugPrivateViewFlutterApi *flutterApi = [[InstabugPrivateViewFlutterApi alloc] initWithBinaryMessenger:messenger]; + return [[PrivateViewApi alloc] initWithFlutterApi:flutterApi registrar:flutterEngineRegistrar]; +} + +@implementation PrivateViewApi + +// Initializer with proper memory management +- (instancetype)initWithFlutterApi:(InstabugPrivateViewFlutterApi *)api + registrar:( NSObject *) registrar { + if ((self = [super init])) { + _flutterApi = api; + _flutterEngineRegistrar = registrar; + } + return self; +} + +- (void)mask:(UIImage *)screenshot + completion:(void (^)(UIImage *))completion { + + __weak typeof(self) weakSelf = self; +// Capture screenshot in parallel + + [self.flutterApi getPrivateViewsWithCompletion:^(NSArray *rectangles, FlutterError *error) { + UIImage *capturedScreenshot = [self captureScreenshot]; + [weakSelf handlePrivateViewsResult:rectangles + error:error + screenshot:capturedScreenshot + completion:completion]; + }]; +} + +#pragma mark - Private Methods + +// Method to capture a screenshot of the app's main window +- (UIImage *)captureScreenshot { + CGSize imageSize = UIScreen.mainScreen.bounds.size; + UIGraphicsBeginImageContextWithOptions(imageSize, NO, UIScreen.mainScreen.scale); + + // Iterate over all windows, including the keyboard window + for (UIWindow *window in UIApplication.sharedApplication.windows) { + [window drawViewHierarchyInRect:window.bounds afterScreenUpdates:YES]; + } + + UIImage *screenshot = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + return screenshot; +} + +// 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 { + FlutterViewController *flutterVC = (FlutterViewController *)self.flutterEngineRegistrar.flutterEngine.viewController; + + UIView *flutterView = flutterVC.view; + if(!flutterView) + return CGPointZero; + UIWindow *window = flutterView.window; + CGRect globalFrame = [flutterView convertRect:flutterView.bounds toView:window]; + + return globalFrame.origin ; +} + + +// Log error details +- (void)logError:(FlutterError *)error { + NSLog(@"IBG-Flutter: Error getting private views: %@", error.message); +} + + +@end diff --git a/packages/instabug_private_views/ios/Classes/Modules/PrivateViewHostApi.h b/packages/instabug_private_views/ios/Classes/Modules/PrivateViewHostApi.h new file mode 100644 index 000000000..54ced156c --- /dev/null +++ b/packages/instabug_private_views/ios/Classes/Modules/PrivateViewHostApi.h @@ -0,0 +1,7 @@ +#import "PrivateViewApi.h" +#import "InstabugPrivateViewPigeon.h" +extern void InitPrivateViewHostApi(id _Nonnull messenger, PrivateViewApi * _Nonnull api); + +@interface PrivateViewHostApi : NSObject +@property (nonatomic, strong) PrivateViewApi* _Nonnull privateViewApi; +@end diff --git a/packages/instabug_private_views/ios/Classes/Modules/PrivateViewHostApi.m b/packages/instabug_private_views/ios/Classes/Modules/PrivateViewHostApi.m new file mode 100644 index 000000000..6b1c26c4d --- /dev/null +++ b/packages/instabug_private_views/ios/Classes/Modules/PrivateViewHostApi.m @@ -0,0 +1,29 @@ +// +// PrivateViewHostApi.m +// instabug_flutter +// +// Created by Ahmed alaa on 02/11/2024. +// + +#import "PrivateViewHostApi.h" +#import "instabug_flutter/InstabugApi.h" + +extern void InitPrivateViewHostApi(id _Nonnull messenger, PrivateViewApi * _Nonnull privateViewApi) { + PrivateViewHostApi *api = [[PrivateViewHostApi alloc] init]; + api.privateViewApi = privateViewApi; + InstabugPrivateViewHostApiSetup(messenger, api); +} + +@implementation PrivateViewHostApi + +- (void)initWithError:(FlutterError * _Nullable __autoreleasing * _Nonnull)error { + [InstabugApi setScreenshotMaskingHandler:^(UIImage * _Nonnull screenshot, void (^ _Nonnull completion)(UIImage * _Nullable)) { + [self.privateViewApi mask:screenshot completion:^(UIImage * _Nonnull maskedImage) { + if (maskedImage != nil) { + completion(maskedImage); + } + }]; + }]; +} + +@end diff --git a/packages/instabug_private_views/ios/Classes/Util/FlutterPluginRegistrar+FlutterEngine.h b/packages/instabug_private_views/ios/Classes/Util/FlutterPluginRegistrar+FlutterEngine.h new file mode 100644 index 000000000..ae31cbcd4 --- /dev/null +++ b/packages/instabug_private_views/ios/Classes/Util/FlutterPluginRegistrar+FlutterEngine.h @@ -0,0 +1,8 @@ +#import + +@interface NSObject (FlutterEngineAccess) + +// Method to access FlutterEngine +- (FlutterEngine *)flutterEngine; + +@end diff --git a/packages/instabug_private_views/ios/Classes/Util/FlutterPluginRegistrar+FlutterEngine.m b/packages/instabug_private_views/ios/Classes/Util/FlutterPluginRegistrar+FlutterEngine.m new file mode 100644 index 000000000..4e5109d3a --- /dev/null +++ b/packages/instabug_private_views/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/packages/instabug_private_views/ios/Classes/instabug_private_views-Bridging-Header.h b/packages/instabug_private_views/ios/Classes/instabug_private_views-Bridging-Header.h new file mode 100644 index 000000000..1b2cb5d6d --- /dev/null +++ b/packages/instabug_private_views/ios/Classes/instabug_private_views-Bridging-Header.h @@ -0,0 +1,4 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + diff --git a/packages/instabug_private_views/ios/Resources/PrivacyInfo.xcprivacy b/packages/instabug_private_views/ios/Resources/PrivacyInfo.xcprivacy new file mode 100644 index 000000000..a34b7e2e6 --- /dev/null +++ b/packages/instabug_private_views/ios/Resources/PrivacyInfo.xcprivacy @@ -0,0 +1,14 @@ + + + + + NSPrivacyTrackingDomains + + NSPrivacyAccessedAPITypes + + NSPrivacyCollectedDataTypes + + NSPrivacyTracking + + + diff --git a/packages/instabug_private_views/ios/instabug_private_views.podspec b/packages/instabug_private_views/ios/instabug_private_views.podspec new file mode 100644 index 000000000..39751464c --- /dev/null +++ b/packages/instabug_private_views/ios/instabug_private_views.podspec @@ -0,0 +1,30 @@ +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. +# Run `pod lib lint instabug_private_views.podspec` to validate before publishing. +# +Pod::Spec.new do |s| + s.name = 'instabug_private_views' + s.version = '0.0.1' + s.summary = 'A new Flutter plugin project.' + s.description = <<-DESC +A new Flutter plugin project. + DESC + s.homepage = 'http://example.com' + s.license = { :file => '../LICENSE' } + s.author = { 'Your Company' => 'email@example.com' } + s.source = { :path => '.' } + s.source_files = 'Classes/**/*' + s.public_header_files = 'Classes/**/*.h' + s.dependency 'Flutter' + s.dependency 'instabug_flutter' + s.platform = :ios, '12.0' + + # Flutter.framework does not contain a i386 slice. + s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } + + # If your plugin requires a privacy manifest, for example if it uses any + # required reason APIs, update the PrivacyInfo.xcprivacy file to describe your + # plugin's privacy impact, and then uncomment this line. For more information, + # see https://developer.apple.com/documentation/bundleresources/privacy_manifest_files + # s.resource_bundles = {'instabug_private_views_privacy' => ['Resources/PrivacyInfo.xcprivacy']} +end diff --git a/packages/instabug_private_views/lib/instabug_private_view.dart b/packages/instabug_private_views/lib/instabug_private_view.dart new file mode 100644 index 000000000..0be5a6278 --- /dev/null +++ b/packages/instabug_private_views/lib/instabug_private_view.dart @@ -0,0 +1,11 @@ +import 'package:instabug_private_views/src/generated/instabug_private_view.api.g.dart'; +import 'package:instabug_private_views/src/private_views_manager.dart'; + +export 'src/instabug_private_view.dart'; +export 'src/instabug_sliver_private_view.dart'; + +void enableInstabugMaskingPrivateViews() { + final api = InstabugPrivateViewHostApi(); + api.init(); + InstabugPrivateViewFlutterApi.setup(PrivateViewsManager.I); +} diff --git a/packages/instabug_private_views/lib/src/instabug_private_view.dart b/packages/instabug_private_views/lib/src/instabug_private_view.dart new file mode 100644 index 000000000..b2672b17d --- /dev/null +++ b/packages/instabug_private_views/lib/src/instabug_private_view.dart @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; +import 'package:instabug_private_views/src/private_views_manager.dart'; +import 'package:instabug_private_views/src/visibility_detector/visibility_detector.dart'; + +class InstabugPrivateView extends StatefulWidget { + final Widget child; + + // Making the constructor const prevents the VisibilityDetector from detecting changes in the view, + // ignore: prefer_const_constructors_in_immutables + InstabugPrivateView({required this.child}) : super(key: null); + + @override + State createState() => _InstabugPrivateViewState(); +} + +class _InstabugPrivateViewState extends State { + final GlobalKey _visibilityDetectorKey = GlobalKey(); + final GlobalKey _childKey = GlobalKey(); + + @override + void initState() { + _addPrivateView(); + super.initState(); + } + @override + void dispose() { + _removePrivateView(); + super.dispose(); + } + + void _addPrivateView() { + PrivateViewsManager.I.mask(_childKey); + } + + void _removePrivateView() { + PrivateViewsManager.I.unMask(_childKey); + } + + void _onVisibilityChanged(bool isVisible) { + if (isVisible) { + _addPrivateView(); + } else { + _removePrivateView(); + } + } + + @override + Widget build(BuildContext context) { + return VisibilityDetector( + key: _visibilityDetectorKey, + onVisibilityChanged: _onVisibilityChanged, + child: KeyedSubtree(key: _childKey, child: widget.child), + ); + } +} diff --git a/packages/instabug_private_views/lib/src/instabug_sliver_private_view.dart b/packages/instabug_private_views/lib/src/instabug_sliver_private_view.dart new file mode 100644 index 000000000..bc0237513 --- /dev/null +++ b/packages/instabug_private_views/lib/src/instabug_sliver_private_view.dart @@ -0,0 +1,58 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:instabug_private_views/src/private_views_manager.dart'; +import 'package:instabug_private_views/src/visibility_detector/sliver_visibility_detector.dart'; + +class InstabugSliverPrivateView extends StatefulWidget { + final Widget sliver; + + // Making the constructor const prevents the VisibilityDetector from detecting changes in the view, + // ignore: prefer_const_constructors_in_immutables, use_super_parameters + InstabugSliverPrivateView({Key? key, required this.sliver}) : super(key: key); + + @override + State createState() => + _InstabugSliverPrivateViewState(); +} + +class _InstabugSliverPrivateViewState extends State { + final key = GlobalKey(); + final GlobalKey _childKey = GlobalKey(); + + @override + void dispose() { + _removePrivateView(); + super.dispose(); + } + + @override + void initState() { + _addPrivateView(); + super.initState(); + } + + void _addPrivateView() { + PrivateViewsManager.I.mask(_childKey); + } + + void _removePrivateView() { + PrivateViewsManager.I.unMask(_childKey); + } + + void _onVisibilityChanged(bool isVisible) { + if (isVisible) { + _addPrivateView(); + } else { + _removePrivateView(); + } + } + + @override + Widget build(BuildContext context) { + return SliverVisibilityDetector( + key: key, + onVisibilityChanged: _onVisibilityChanged, + sliver: KeyedSubtree(key: _childKey, child: widget.sliver), + ); + } +} diff --git a/packages/instabug_private_views/lib/src/private_views_manager.dart b/packages/instabug_private_views/lib/src/private_views_manager.dart new file mode 100644 index 000000000..50815fbe8 --- /dev/null +++ b/packages/instabug_private_views/lib/src/private_views_manager.dart @@ -0,0 +1,83 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:instabug_private_views/src/generated/instabug_private_view.api.g.dart'; + +/// responsible for masking views +/// before they are sent to the native SDKs. +class PrivateViewsManager implements InstabugPrivateViewFlutterApi { + PrivateViewsManager._(); + + static PrivateViewsManager _instance = PrivateViewsManager._(); + + static PrivateViewsManager get instance => _instance; + + /// Shorthand for [instance] + static PrivateViewsManager get I => instance; + + final Set _keys = {}; + + @visibleForTesting + // ignore: use_setters_to_change_properties + static void setInstance(PrivateViewsManager instance) { + _instance = instance; + } + + Rect? getLayoutRectInfoFromKey(GlobalKey key) { + final renderObject = key.currentContext?.findRenderObject(); + + if (renderObject == null) { + return null; + } + + final globalOffset = _getRenderGlobalOffset(renderObject); + + if (renderObject is RenderProxyBox) { + if (renderObject.child == null) { + return null; + } + + return MatrixUtils.transformRect( + renderObject.child!.getTransformTo(renderObject), + Offset.zero & renderObject.child!.size, + ).shift(globalOffset); + } + + return renderObject.paintBounds.shift(globalOffset); + } + + // The is the same implementation used in RenderBox.localToGlobal (a subclass of RenderObject) + Offset _getRenderGlobalOffset(RenderObject renderObject) { + return MatrixUtils.transformPoint( + renderObject.getTransformTo(null), + Offset.zero, + ); + } + + void mask(GlobalKey key) { + _keys.add(key); + } + + void unMask(GlobalKey key) { + _keys.remove(key); + } + + @override + List getPrivateViews() { + final result = []; + + for (final view in _keys) { + final rect = getLayoutRectInfoFromKey(view); + + if (rect == null) continue; + + result.addAll([ + rect.left, + rect.top, + rect.right, + rect.bottom, + ]); + } + + return result; + } +} diff --git a/packages/instabug_private_views/lib/src/visibility_detector/base_render_visibility_detector.dart b/packages/instabug_private_views/lib/src/visibility_detector/base_render_visibility_detector.dart new file mode 100644 index 000000000..46b901235 --- /dev/null +++ b/packages/instabug_private_views/lib/src/visibility_detector/base_render_visibility_detector.dart @@ -0,0 +1,266 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:instabug_private_views/src/visibility_detector/visibillity_utils.dart'; + +typedef VisibilityChangedCallback = void Function(bool isVisible); + +mixin RenderVisibilityDetectorBase on RenderObject { + static int? get debugUpdateCount { + if (!kDebugMode) { + return null; + } + return _updates.length; + } + + static Duration updateInterval = const Duration(milliseconds: 500); + static final Map _updates = {}; + static final Map _lastVisibility = {}; + + static void forget(Key key) { + _updates.remove(key); + _lastVisibility.remove(key); + + if (_updates.isEmpty) { + _timer?.cancel(); + _timer = null; + } + } + + static Timer? _timer; + + static void _handleTimer() { + _timer = null; + // Ensure that work is done between frames so that calculations are + // performed from a consistent state. We use `scheduleTask` here instead + // of `addPostFrameCallback` or `scheduleFrameCallback` so that work will + // be done even if a new frame isn't scheduled and without unnecessarily + // scheduling a new frame. + SchedulerBinding.instance.scheduleTask( + _processCallbacks, + Priority.touch, + ); + } + + /// Executes visibility callbacks for all updated instances. + static void _processCallbacks() { + for (final callback in _updates.values) { + callback(); + } + + _updates.clear(); + } + + void _fireCallback(ContainerLayer? layer, Rect bounds) { + final oldInfo = _lastVisibility[key]; + final visible = _determineVisibility(layer, bounds); + + if (visible == oldInfo) { + return; + } + + if (visible) { + _lastVisibility[key] = visible; + } else { + // Track only visible items so that the map does not grow unbounded. + if (oldInfo != null) { + _lastVisibility.remove(key); + } + } + + onVisibilityChanged?.call(visible); + } + + /// The key for the corresponding [VisibilityDetector] widget. + Key get key; + + VoidCallback? _compositionCallbackCanceller; + + VisibilityChangedCallback? _onVisibilityChanged; + + // ignore: use_setters_to_change_properties + void init({required VisibilityChangedCallback? visibilityChangedCallback}) { + _onVisibilityChanged = visibilityChangedCallback; + } + + VisibilityChangedCallback? get onVisibilityChanged => _onVisibilityChanged; + + set onVisibilityChanged(VisibilityChangedCallback? value) { + _compositionCallbackCanceller?.call(); + _compositionCallbackCanceller = null; + _onVisibilityChanged = value; + + if (value == null) { + forget(key); + } else { + markNeedsPaint(); + // If an update is happening and some ancestor no longer paints this RO, + // the markNeedsPaint above will never cause the composition callback to + // fire and we could miss a hide event. This schedule will get + // over-written by subsequent updates in paint, if paint is called. + _scheduleUpdate(); + } + } + + int _debugScheduleUpdateCount = 0; + + /// The number of times the schedule update callback has been invoked from + /// [Layer.addCompositionCallback]. + /// + /// This is used for testing, and always returns null outside of debug mode. + @visibleForTesting + int? get debugScheduleUpdateCount { + if (kDebugMode) { + return _debugScheduleUpdateCount; + } + return null; + } + + void _scheduleUpdate([ContainerLayer? layer]) { + if (kDebugMode) { + _debugScheduleUpdateCount += 1; + } + final isFirstUpdate = _updates.isEmpty; + _updates[key] = () { + if (bounds == null) { + return; + } + _fireCallback(layer, bounds!); + }; + + if (updateInterval == Duration.zero) { + if (isFirstUpdate) { + // We're about to render a frame, so a post-frame callback is guaranteed + // to fire and will give us the better immediacy than `scheduleTask`. + SchedulerBinding.instance.addPostFrameCallback((timeStamp) { + _processCallbacks(); + }); + } + } else if (_timer == null) { + // We use a normal [Timer] instead of a [RestartableTimer] so that changes + // to the update duration will be picked up automatically. + _timer = Timer(updateInterval, _handleTimer); + } else { + assert(_timer!.isActive); + } + } + + bool _determineVisibility(ContainerLayer? layer, Rect bounds) { + if (_disposed || layer == null || layer.attached == false || !attached) { + // layer is detached and thus invisible. + return false; + } + final transform = Matrix4.identity(); + + // Check if any ancestors decided to skip painting this RenderObject. + if (parent != null) { + // ignore: unnecessary_cast + var ancestor = parent! as RenderObject; + RenderObject child = this; + while (ancestor.parent != null) { + if (!ancestor.paintsChild(child)) { + return false; + } + child = ancestor; + // ignore: unnecessary_cast + ancestor = ancestor.parent! as RenderObject; + } + } + + // Create a list of Layers from layer to the root, excluding the root + // since that has the DPR transform and we want to work with logical pixels. + // Add one extra leaf layer so that we can apply the transform of `layer` + // to the matrix. + ContainerLayer? ancestor = layer; + final ancestors = [ContainerLayer()]; + while (ancestor != null && ancestor.parent != null) { + ancestors.add(ancestor); + ancestor = ancestor.parent; + } + + var clip = Rect.largest; + for (var index = ancestors.length - 1; index > 0; index -= 1) { + final parent = ancestors[index]; + final child = ancestors[index - 1]; + final parentClip = parent.describeClipBounds(); + if (parentClip != null) { + clip = clip.intersect(MatrixUtils.transformRect(transform, parentClip)); + } + parent.applyTransform(child, transform); + } + + // Apply whatever transform/clip was on the canvas when painting. + if (_lastPaintClipBounds != null) { + clip = clip.intersect( + MatrixUtils.transformRect( + transform, + _lastPaintClipBounds!, + ), + ); + } + if (_lastPaintTransform != null) { + transform.multiply(_lastPaintTransform!); + } + return isWidgetVisible( + MatrixUtils.transformRect(transform, bounds), + clip, + ); + } + + /// Used to get the bounds of the render object when it is time to update + /// clients about visibility. + /// + /// A null value means bounds are not available. + Rect? get bounds; + + Matrix4? _lastPaintTransform; + Rect? _lastPaintClipBounds; + + @override + void paint(PaintingContext context, Offset offset) { + if (onVisibilityChanged != null) { + _lastPaintClipBounds = context.canvas.getLocalClipBounds(); + _lastPaintTransform = + Matrix4.fromFloat64List(context.canvas.getTransform()) + ..translate(offset.dx, offset.dy); + + _compositionCallbackCanceller?.call(); + _compositionCallbackCanceller = + context.addCompositionCallback((Layer layer) { + assert(!debugDisposed!); + final container = layer is ContainerLayer ? layer : layer.parent; + _scheduleUpdate(container); + }); + } + super.paint(context, offset); + } + + bool _disposed = false; + + @override + void dispose() { + _compositionCallbackCanceller?.call(); + _compositionCallbackCanceller = null; + _disposed = true; + super.dispose(); + } +} + +class RenderVisibilityDetector extends RenderProxyBox + with RenderVisibilityDetectorBase { + RenderVisibilityDetector({ + RenderBox? child, + required this.key, + required VisibilityChangedCallback? onVisibilityChanged, + }) : super(child) { + _onVisibilityChanged = onVisibilityChanged; + } + + @override + final Key key; + + @override + Rect? get bounds => hasSize ? semanticBounds : null; +} diff --git a/packages/instabug_private_views/lib/src/visibility_detector/sliver_visibility_detector.dart b/packages/instabug_private_views/lib/src/visibility_detector/sliver_visibility_detector.dart new file mode 100644 index 000000000..facdd68d1 --- /dev/null +++ b/packages/instabug_private_views/lib/src/visibility_detector/sliver_visibility_detector.dart @@ -0,0 +1,85 @@ +import 'dart:math' as math; + +import 'package:flutter/rendering.dart'; +import 'package:flutter/widgets.dart'; + +import 'package:instabug_private_views/src/visibility_detector/base_render_visibility_detector.dart'; + +class RenderSliverVisibilityDetector extends RenderProxySliver + with RenderVisibilityDetectorBase { + RenderSliverVisibilityDetector({ + RenderSliver? sliver, + required this.key, + required VisibilityChangedCallback? onVisibilityChanged, + }) : super(sliver) { + init(visibilityChangedCallback: onVisibilityChanged); + } + + @override + final Key key; + + @override + Rect? get bounds { + if (geometry == null) { + return null; + } + + Size widgetSize; + Offset widgetOffset; + switch (applyGrowthDirectionToAxisDirection( + constraints.axisDirection, + constraints.growthDirection, + )) { + case AxisDirection.down: + widgetOffset = Offset(0, -constraints.scrollOffset); + widgetSize = Size(constraints.crossAxisExtent, geometry!.scrollExtent); + break; + case AxisDirection.up: + final startOffset = geometry!.paintExtent + + constraints.scrollOffset - + geometry!.scrollExtent; + widgetOffset = Offset(0, math.min(startOffset, 0)); + widgetSize = Size(constraints.crossAxisExtent, geometry!.scrollExtent); + break; + case AxisDirection.right: + widgetOffset = Offset(-constraints.scrollOffset, 0); + widgetSize = Size(geometry!.scrollExtent, constraints.crossAxisExtent); + break; + case AxisDirection.left: + final startOffset = geometry!.paintExtent + + constraints.scrollOffset - + geometry!.scrollExtent; + widgetOffset = Offset(math.min(startOffset, 0), 0); + widgetSize = Size(geometry!.scrollExtent, constraints.crossAxisExtent); + break; + } + return widgetOffset & widgetSize; + } +} + +class SliverVisibilityDetector extends SingleChildRenderObjectWidget { + const SliverVisibilityDetector({ + required Key key, + required Widget sliver, + required this.onVisibilityChanged, + }) : super(key: key, child: sliver); + + final VisibilityChangedCallback? onVisibilityChanged; + + @override + RenderSliverVisibilityDetector createRenderObject(BuildContext context) { + return RenderSliverVisibilityDetector( + key: key!, + onVisibilityChanged: onVisibilityChanged, + ); + } + + @override + void updateRenderObject( + BuildContext context, + RenderSliverVisibilityDetector renderObject, + ) { + assert(renderObject.key == key); + renderObject.onVisibilityChanged = onVisibilityChanged; + } +} diff --git a/packages/instabug_private_views/lib/src/visibility_detector/visibility_detector.dart b/packages/instabug_private_views/lib/src/visibility_detector/visibility_detector.dart new file mode 100644 index 000000000..16b83b025 --- /dev/null +++ b/packages/instabug_private_views/lib/src/visibility_detector/visibility_detector.dart @@ -0,0 +1,49 @@ +import 'package:flutter/rendering.dart'; +import 'package:flutter/widgets.dart'; + +import 'package:instabug_private_views/src/visibility_detector/base_render_visibility_detector.dart'; + +class RenderVisibilityDetector extends RenderProxyBox + with RenderVisibilityDetectorBase { + RenderVisibilityDetector({ + RenderBox? child, + required this.key, + required VisibilityChangedCallback? onVisibilityChanged, + }) : super(child) { + init(visibilityChangedCallback: onVisibilityChanged); + } + + @override + final Key key; + + @override + Rect? get bounds => hasSize ? semanticBounds : null; +} + +class VisibilityDetector extends SingleChildRenderObjectWidget { + const VisibilityDetector({ + required Key key, + required Widget child, + required this.onVisibilityChanged, + }) : super(key: key, child: child); + + /// The callback to invoke when this widget's visibility changes. + final VisibilityChangedCallback? onVisibilityChanged; + + @override + RenderVisibilityDetector createRenderObject(BuildContext context) { + return RenderVisibilityDetector( + key: key!, + onVisibilityChanged: onVisibilityChanged, + ); + } + + @override + void updateRenderObject( + BuildContext context, + RenderVisibilityDetector renderObject, + ) { + assert(renderObject.key == key); + renderObject.onVisibilityChanged = onVisibilityChanged; + } +} diff --git a/packages/instabug_private_views/lib/src/visibility_detector/visibillity_utils.dart b/packages/instabug_private_views/lib/src/visibility_detector/visibillity_utils.dart new file mode 100644 index 000000000..855f625cf --- /dev/null +++ b/packages/instabug_private_views/lib/src/visibility_detector/visibillity_utils.dart @@ -0,0 +1,46 @@ +import 'dart:math'; + +import 'package:flutter/widgets.dart'; + +bool isWidgetVisible( + Rect widgetBounds, + Rect clipRect, +) { + final overlaps = widgetBounds.overlaps(clipRect); + // Compute the intersection in the widget's local coordinates. + final visibleBounds = overlaps + ? widgetBounds.intersect(clipRect).shift(-widgetBounds.topLeft) + : Rect.zero; + final visibleArea = _area(visibleBounds.size); + final maxVisibleArea = _area(widgetBounds.size); + + if (_floatNear(maxVisibleArea, 0)) { + return false; + } + + final visibleFraction = visibleArea / maxVisibleArea; + + if (_floatNear(visibleFraction, 0)) { + return false; + } else if (_floatNear(visibleFraction, 1)) { + return true; + } + + return true; +} + +/// Computes the area of a rectangle of the specified dimensions. +double _area(Size size) { + assert(size.width >= 0); + assert(size.height >= 0); + return size.width * size.height; +} + +/// Returns whether two floating-point values are approximately equal. +bool _floatNear(double f1, double f2) { + final absDiff = (f1 - f2).abs(); + return absDiff <= _kDefaultTolerance || + (absDiff / max(f1.abs(), f2.abs()) <= _kDefaultTolerance); +} + +const _kDefaultTolerance = 0.01; diff --git a/packages/instabug_private_views/pigeons/instabug_private_view.api.dart b/packages/instabug_private_views/pigeons/instabug_private_view.api.dart new file mode 100644 index 000000000..9a84a63f3 --- /dev/null +++ b/packages/instabug_private_views/pigeons/instabug_private_view.api.dart @@ -0,0 +1,11 @@ +import 'package:pigeon/pigeon.dart'; + +@FlutterApi() +abstract class InstabugPrivateViewFlutterApi { + List getPrivateViews(); +} + +@HostApi() +abstract class InstabugPrivateViewHostApi { + void init(); +} diff --git a/packages/instabug_private_views/pubspec.lock b/packages/instabug_private_views/pubspec.lock new file mode 100644 index 000000000..76aedff0e --- /dev/null +++ b/packages/instabug_private_views/pubspec.lock @@ -0,0 +1,716 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: ae92f5d747aee634b87f89d9946000c2de774be1d6ac3e58268224348cd0101a + url: "https://pub.dev" + source: hosted + version: "61.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: ea3d8652bda62982addfd92fdc2d0214e5f82e43325104990d4f4c4a2a313562 + url: "https://pub.dev" + source: hosted + version: "5.13.0" + args: + dependency: transitive + description: + name: args + sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6 + url: "https://pub.dev" + source: hosted + version: "2.6.0" + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + build: + dependency: transitive + description: + name: build + sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + build_config: + dependency: transitive + description: + name: build_config + sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 + url: "https://pub.dev" + source: hosted + version: "1.1.1" + build_daemon: + dependency: transitive + description: + name: build_daemon + sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + build_resolvers: + dependency: transitive + description: + name: build_resolvers + sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + build_runner: + dependency: "direct dev" + description: + name: build_runner + sha256: "028819cfb90051c6b5440c7e574d1896f8037e3c96cf17aaeb054c9311cfbf4d" + url: "https://pub.dev" + source: hosted + version: "2.4.13" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + sha256: f8126682b87a7282a339b871298cc12009cb67109cfa1614d6436fb0289193e0 + url: "https://pub.dev" + source: hosted + version: "7.3.2" + built_collection: + dependency: transitive + description: + name: built_collection + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.dev" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb + url: "https://pub.dev" + source: hosted + version: "8.9.2" + characters: + dependency: transitive + description: + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff + url: "https://pub.dev" + source: hosted + version: "2.0.3" + cli_util: + dependency: transitive + description: + name: cli_util + sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c + url: "https://pub.dev" + source: hosted + version: "0.4.2" + clock: + dependency: transitive + description: + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" + source: hosted + version: "1.1.1" + code_builder: + dependency: transitive + description: + name: code_builder + sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e" + url: "https://pub.dev" + source: hosted + version: "4.10.1" + collection: + dependency: transitive + description: + name: collection + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + url: "https://pub.dev" + source: hosted + version: "1.18.0" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + coverage: + dependency: transitive + description: + name: coverage + sha256: "88b0fddbe4c92910fefc09cc0248f5e7f0cd23e450ded4c28f16ab8ee8f83268" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + crypto: + dependency: transitive + description: + name: crypto + sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" + url: "https://pub.dev" + source: hosted + version: "3.0.6" + csslib: + dependency: transitive + description: + name: csslib + sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" + url: "https://pub.dev" + source: hosted + version: "1.0.2" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: "1efa911ca7086affd35f463ca2fc1799584fb6aa89883cf0af8e3664d6a02d55" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + fake_async: + dependency: "direct dev" + description: + name: fake_async + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 + url: "https://pub.dev" + source: hosted + version: "4.0.0" + glob: + dependency: transitive + description: + name: glob + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + graphs: + dependency: transitive + description: + name: graphs + sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + html: + dependency: transitive + description: + name: html + sha256: "1fc58edeaec4307368c60d59b7e15b9d658b57d7f3125098b6294153c75337ec" + url: "https://pub.dev" + source: hosted + version: "0.15.5" + http: + dependency: transitive + description: + name: http + sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 + url: "https://pub.dev" + source: hosted + version: "1.2.2" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" + url: "https://pub.dev" + source: hosted + version: "3.2.1" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + instabug_flutter: + dependency: "direct main" + description: + path: "../instabug_flutter" + relative: true + source: path + version: "14.0.0" + io: + dependency: transitive + description: + name: io + sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" + url: "https://pub.dev" + source: hosted + version: "1.0.4" + js: + dependency: transitive + description: + name: js + sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf + url: "https://pub.dev" + source: hosted + version: "0.7.1" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + url: "https://pub.dev" + source: hosted + version: "4.9.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" + url: "https://pub.dev" + source: hosted + version: "10.0.5" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" + url: "https://pub.dev" + source: hosted + version: "3.0.5" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + lint: + dependency: "direct dev" + description: + name: lint + sha256: "4a539aa34ec5721a2c7574ae2ca0336738ea4adc2a34887d54b7596310b33c85" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + lints: + dependency: transitive + description: + name: lints + sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290 + url: "https://pub.dev" + source: hosted + version: "3.0.0" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + markdown: + dependency: transitive + description: + name: markdown + sha256: ef2a1298144e3f985cc736b22e0ccdaf188b5b3970648f2d9dc13efd1d9df051 + url: "https://pub.dev" + source: hosted + version: "7.2.2" + matcher: + dependency: transitive + description: + name: matcher + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + url: "https://pub.dev" + source: hosted + version: "0.12.16+1" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" + source: hosted + version: "0.11.1" + meta: + dependency: transitive + description: + name: meta + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + url: "https://pub.dev" + source: hosted + version: "1.15.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + mockito: + dependency: "direct dev" + description: + name: mockito + sha256: "6841eed20a7befac0ce07df8116c8b8233ed1f4486a7647c7fc5a02ae6163917" + url: "https://pub.dev" + source: hosted + version: "5.4.4" + node_preamble: + dependency: transitive + description: + name: node_preamble + sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + package_config: + dependency: transitive + description: + name: package_config + sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + pana: + dependency: "direct dev" + description: + name: pana + sha256: "3fc3fe8e7a9fd4827fa4d625a423eec95d305b2bc3538a3adf7fd6c49217af97" + url: "https://pub.dev" + source: hosted + version: "0.21.45" + path: + dependency: transitive + description: + name: path + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + url: "https://pub.dev" + source: hosted + version: "1.9.0" + pigeon: + dependency: "direct dev" + description: + name: pigeon + sha256: "6eb9702acc25d5ec340bd1b751e511e2982189dfc40c12e2d69db6e05bab03be" + url: "https://pub.dev" + source: hosted + version: "10.1.5" + pool: + dependency: transitive + description: + name: pool + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + url: "https://pub.dev" + source: hosted + version: "1.5.1" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: c799b721d79eb6ee6fa56f00c04b472dcd44a30d258fac2174a6ec57302678f8 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + retry: + dependency: transitive + description: + name: retry + sha256: "822e118d5b3aafed083109c72d5f484c6dc66707885e07c0fbcb8b986bba7efc" + url: "https://pub.dev" + source: hosted + version: "3.1.2" + safe_url_check: + dependency: transitive + description: + name: safe_url_check + sha256: "49a3e060a7869cbafc8f4845ca1ecbbaaa53179980a32f4fdfeab1607e90f41d" + url: "https://pub.dev" + source: hosted + version: "1.1.2" + shelf: + dependency: transitive + description: + name: shelf + sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 + url: "https://pub.dev" + source: hosted + version: "1.4.1" + shelf_packages_handler: + dependency: transitive + description: + name: shelf_packages_handler + sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + shelf_static: + dependency: transitive + description: + name: shelf_static + sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3 + url: "https://pub.dev" + source: hosted + version: "1.1.3" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "073c147238594ecd0d193f3456a5fe91c4b0abbcc68bf5cd95b36c4e194ac611" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" + url: "https://pub.dev" + source: hosted + version: "1.5.0" + source_map_stack_trace: + dependency: transitive + description: + name: source_map_stack_trace + sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b + url: "https://pub.dev" + source: hosted + version: "2.1.2" + source_maps: + dependency: transitive + description: + name: source_maps + sha256: "708b3f6b97248e5781f493b765c3337db11c5d2c81c3094f10904bfa8004c703" + url: "https://pub.dev" + source: hosted + version: "0.10.12" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + url: "https://pub.dev" + source: hosted + version: "1.11.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + url: "https://pub.dev" + source: hosted + version: "2.1.2" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + tar: + dependency: transitive + description: + name: tar + sha256: "22f67e2d77b51050436620b2a5de521c58ca6f0b75af1d9ab3c8cae2eae58fcd" + url: "https://pub.dev" + source: hosted + version: "1.0.5" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test: + dependency: transitive + description: + name: test + sha256: "7ee44229615f8f642b68120165ae4c2a75fe77ae2065b1e55ae4711f6cf0899e" + url: "https://pub.dev" + source: hosted + version: "1.25.7" + test_api: + dependency: transitive + description: + name: test_api + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" + url: "https://pub.dev" + source: hosted + version: "0.7.2" + test_core: + dependency: transitive + description: + name: test_core + sha256: "55ea5a652e38a1dfb32943a7973f3681a60f872f8c3a05a14664ad54ef9c6696" + url: "https://pub.dev" + source: hosted + version: "0.6.4" + timing: + dependency: transitive + description: + name: timing + sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" + url: "https://pub.dev" + source: hosted + version: "14.2.5" + watcher: + dependency: transitive + description: + name: watcher + sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + web: + dependency: transitive + description: + name: web + sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb + url: "https://pub.dev" + source: hosted + version: "1.1.0" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83" + url: "https://pub.dev" + source: hosted + version: "0.1.6" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + yaml: + dependency: transitive + description: + name: yaml + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + url: "https://pub.dev" + source: hosted + version: "3.1.2" +sdks: + dart: ">=3.5.0 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/packages/instabug_private_views/pubspec.yaml b/packages/instabug_private_views/pubspec.yaml new file mode 100644 index 000000000..60288d069 --- /dev/null +++ b/packages/instabug_private_views/pubspec.yaml @@ -0,0 +1,36 @@ +name: instabug_private_views +description: "An extension for the Instabug Flutter SDK that enables support for masking private views during screen capturing." +version: 1.0.1 +homepage: https://www.instabug.com +repository: https://github.com/Instabug/Instabug-Flutter +documentation: https://docs.instabug.com/docs/flutter-overview + +environment: + sdk: ">=2.14.0 <4.0.0" + flutter: '>=3.3.0' + +dependencies: + flutter: + sdk: flutter + instabug_flutter: + +dev_dependencies: + build_runner: ^2.0.3 + fake_async: '>=1.2.0 <1.4.0' + flutter_test: + sdk: flutter + lint: ^1.0.0 + # mockito v5.2.0 is needed for running Flutter 2 tests on CI + mockito: '>=5.2.0 <5.5.0' + pana: ^0.21.0 + # pigeon v3.0.0 is needed for running Flutter 2 tests on CI + pigeon: '>=3.0.0 <=10.1.5' + +flutter: + plugin: + platforms: + android: + package: com.instabug.instabug_private_views + pluginClass: InstabugPrivateViewsPlugin + ios: + pluginClass: InstabugPrivateViewsPlugin \ No newline at end of file diff --git a/packages/instabug_private_views/pubspec_overrides.yaml b/packages/instabug_private_views/pubspec_overrides.yaml new file mode 100644 index 000000000..1f0beaf62 --- /dev/null +++ b/packages/instabug_private_views/pubspec_overrides.yaml @@ -0,0 +1,4 @@ +# melos_managed_dependency_overrides: instabug_flutter +dependency_overrides: + instabug_flutter: + path: ../instabug_flutter diff --git a/packages/instabug_private_views/release.sh b/packages/instabug_private_views/release.sh new file mode 100755 index 000000000..b30831736 --- /dev/null +++ b/packages/instabug_private_views/release.sh @@ -0,0 +1,13 @@ +#!/bin/sh +VERSION=$(egrep -o "version: ([0-9]-*.*)+[0-9]" pubspec.yaml | cut -d ":" -f 2) +if [ ! "${VERSION}" ] || [ -z "${VERSION}" ];then + echo "Instabug: err: Version Number not found." + exit 1 +else + mkdir -p $HOME/.config/dart + cat < $HOME/.config/dart/pub-credentials.json + ${PUB_CREDENTIALS} + +EOF + flutter packages pub publish -f +fi \ No newline at end of file diff --git a/packages/instabug_private_views/scripts/pigeon.sh b/packages/instabug_private_views/scripts/pigeon.sh new file mode 100644 index 000000000..5c7fb79b6 --- /dev/null +++ b/packages/instabug_private_views/scripts/pigeon.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +DIR_DART="lib/src/generated" +DIR_IOS="ios/Classes/Generated" +DIR_ANDROID="android/src/main/java/com/instabug/instabug_private_views/generated" +PKG_ANDROID="com.instabug.instabug_private_views.generated" + +mkdir -p $DIR_DART +mkdir -p $DIR_IOS +mkdir -p $DIR_ANDROID + +generate_pigeon() { + name_file=$1 + name_snake=$(basename $name_file .api.dart) + name_pascal=$(echo "$name_snake" | perl -pe 's/(^|_)./uc($&)/ge;s/_//g') + + dart run pigeon \ + --input "pigeons/$name_snake.api.dart" \ + --dart_out "$DIR_DART/$name_snake.api.g.dart" \ + --objc_header_out "$DIR_IOS/${name_pascal}Pigeon.h" \ + --objc_source_out "$DIR_IOS/${name_pascal}Pigeon.m" \ + --java_out "$DIR_ANDROID/${name_pascal}Pigeon.java" \ + --java_package $PKG_ANDROID || exit 1; + + echo "Generated $name_snake pigeon" + + # Generated files are not formatted by default, + # this affects pacakge score. + dart format "$DIR_DART/$name_snake.api.g.dart" +} + +for file in pigeons/** +do + generate_pigeon $file +done diff --git a/packages/instabug_private_views/test/instabug_private_view_test.dart b/packages/instabug_private_views/test/instabug_private_view_test.dart new file mode 100644 index 000000000..7f55c8c61 --- /dev/null +++ b/packages/instabug_private_views/test/instabug_private_view_test.dart @@ -0,0 +1,82 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:instabug_private_views/src/instabug_private_view.dart'; +import 'package:instabug_private_views/src/private_views_manager.dart'; +import 'package:instabug_private_views/src/visibility_detector/base_render_visibility_detector.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'instabug_private_view_test.mocks.dart'; + +@GenerateMocks([PrivateViewsManager]) +void main() { + testWidgets('should mask the view when it is visible', (tester) async { + await tester.runAsync(() async { + final mock = MockPrivateViewsManager(); + RenderVisibilityDetectorBase.updateInterval = Duration.zero; + PrivateViewsManager.setInstance(mock); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: InstabugPrivateView( + child: const Text('Text invisible'), + ), + ), + ), + ); + + verify( + mock.mask(any), + ).called( + 2, + ); // one for initState and the other for visibility is shown is true + }); + }); + + testWidgets("should un-mask the view when it is invisible", (tester) async { + await tester.runAsync(() async { + final mock = MockPrivateViewsManager(); + RenderVisibilityDetectorBase.updateInterval = Duration.zero; + PrivateViewsManager.setInstance(mock); + var isVisible = true; + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return ListView( + children: [ + Visibility( + visible: isVisible, + maintainState: true, + child: InstabugPrivateView( + child: const SizedBox( + width: 40, + height: 40, + ), + ), + ), + ElevatedButton( + onPressed: () { + setState(() { + isVisible = false; // make the widget invisible + }); + }, + child: const Text('Make invisible'), + ), + ], + ); + }, + ), + ), + ), + ); + await tester.tap(find.text('Make invisible')); + await tester.pump(const Duration(seconds: 1)); + verify( + mock.unMask(any), + ).called( + 1, + ); + }); + }); +} diff --git a/packages/instabug_private_views/test/instabug_sliver_private_view_test.dart b/packages/instabug_private_views/test/instabug_sliver_private_view_test.dart new file mode 100644 index 000000000..4e8bdab2b --- /dev/null +++ b/packages/instabug_private_views/test/instabug_sliver_private_view_test.dart @@ -0,0 +1,99 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:instabug_private_views/src/instabug_sliver_private_view.dart'; +import 'package:instabug_private_views/src/private_views_manager.dart'; +import 'package:instabug_private_views/src/visibility_detector/base_render_visibility_detector.dart'; +import 'package:mockito/mockito.dart'; + +import 'instabug_private_view_test.mocks.dart'; + +void main() { + testWidgets('should mask sliver view when it is visible', (tester) async { + await tester.runAsync(() async { + final mock = MockPrivateViewsManager(); + RenderVisibilityDetectorBase.updateInterval = Duration.zero; + PrivateViewsManager.setInstance(mock); + + await tester.pumpWidget( + MaterialApp( + home: CustomScrollView( + slivers: [ + InstabugSliverPrivateView( + sliver: const SliverToBoxAdapter( + child: SizedBox( + width: 20, + height: 40, + ), + ), + ), + ], + ), + ), + ); + + verify( + mock.mask(any), + ).called( + 2, + ); // one for initState and the other for visibility is shown is true + }); + }); + + testWidgets('should un-mask the sliver view when it is invisible', + (tester) async { + await tester.runAsync(() async { + final mock = MockPrivateViewsManager(); + RenderVisibilityDetectorBase.updateInterval = Duration.zero; + PrivateViewsManager.setInstance(mock); + var isVisible = true; + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: SafeArea( + child: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return CustomScrollView( + slivers: [ + SliverToBoxAdapter( + child: ElevatedButton( + onPressed: () { + setState(() { + isVisible = false; // make the widget invisible + }); + }, + child: const Text('Make invisible'), + ), + ), + SliverVisibility( + visible: isVisible, + maintainState: true, + sliver: InstabugSliverPrivateView( + sliver: const SliverToBoxAdapter( + child: SizedBox( + width: 40, + height: 40, + ), + ), + ), + ), + ], + ); + }, + ), + ), + ), + ), + ); + + await tester.tap(find.text('Make invisible')); + await tester.pump(const Duration(milliseconds: 300)); + + verify( + mock.unMask(any), + ).called( + 1, + ); + }); + }); +}