From 06f9966507713780506b5a48f4bef0c0d9baa435 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 27 May 2026 13:42:44 +0200 Subject: [PATCH 1/8] feat(*): wip --- .github/workflows/ci.yml | 116 +- .github/workflows/publish-angular.yml | 5 +- .github/workflows/publish-browser.yml | 5 +- .github/workflows/publish-flutter.yml | 5 + .github/workflows/publish-kmp.yml | 48 + .github/workflows/publish-maui.yml | 6 + .github/workflows/publish-react.yml | 5 +- .github/workflows/publish-reactnative.yml | 5 +- .github/workflows/publish-svelte.yml | 23 + .github/workflows/publish-vue.yml | 5 +- .gitignore | 34 + README.md | 32 +- commitlint.config.js | 4 +- docs/screeb-team-release.md | 161 + examples/example-android/.gitignore | 4 + examples/example-android/README.md | 70 +- examples/example-android/app/build.gradle | 42 + .../example-android/app/proguard-rules.pro | 4 + .../app/src/main/AndroidManifest.xml | 34 + .../kotlin/app/screeb/example/MainActivity.kt | 204 +- .../app/src/main/res/values/styles.xml | 9 + examples/example-android/build.gradle | 4 + examples/example-android/gradle.properties | 4 + .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 54329 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + examples/example-android/gradlew | 172 + examples/example-android/gradlew.bat | 84 + examples/example-android/settings.gradle | 29 + examples/example-angular/package.json | 4 +- examples/example-browser/package.json | 4 +- examples/example-expo/README.md | 64 +- examples/example-expo/app.json | 30 +- examples/example-expo/app/(tabs)/_layout.tsx | 35 - examples/example-expo/app/(tabs)/explore.tsx | 112 - examples/example-expo/app/(tabs)/index.tsx | 168 - examples/example-expo/app/_layout.tsx | 23 +- examples/example-expo/app/index.tsx | 150 + examples/example-expo/app/modal.tsx | 29 - .../assets/images/android-icon-background.png | Bin 17549 -> 0 bytes .../assets/images/android-icon-foreground.png | Bin 78796 -> 0 bytes .../assets/images/android-icon-monochrome.png | Bin 4140 -> 0 bytes .../example-expo/assets/images/favicon.png | Bin 1129 -> 0 bytes examples/example-expo/assets/images/icon.png | Bin 393493 -> 0 bytes .../assets/images/partial-react-logo.png | Bin 5075 -> 0 bytes .../example-expo/assets/images/react-logo.png | Bin 6341 -> 0 bytes .../assets/images/react-logo@2x.png | Bin 14225 -> 0 bytes .../assets/images/react-logo@3x.png | Bin 21252 -> 0 bytes .../assets/images/splash-icon.png | Bin 17547 -> 0 bytes examples/example-expo/babel.config.js | 1 - .../example-expo/components/external-link.tsx | 25 - .../example-expo/components/haptic-tab.tsx | 18 - .../example-expo/components/hello-wave.tsx | 19 - .../components/parallax-scroll-view.tsx | 79 - .../example-expo/components/themed-text.tsx | 60 - .../example-expo/components/themed-view.tsx | 14 - .../components/ui/collapsible.tsx | 45 - .../components/ui/icon-symbol.ios.tsx | 32 - .../components/ui/icon-symbol.tsx | 41 - examples/example-expo/constants/theme.ts | 53 - .../example-expo/hooks/use-color-scheme.ts | 1 - .../hooks/use-color-scheme.web.ts | 21 - .../example-expo/hooks/use-theme-color.ts | 21 - examples/example-expo/package.json | 23 +- .../plugins/withScreebLocalSdk.js | 101 + examples/example-expo/react-native.config.js | 4 +- .../example-expo/scripts/reset-project.js | 112 - examples/example-expo/tsconfig.json | 4 +- examples/example-flutter/README.md | 21 +- .../example-flutter/analysis_options.yaml | 27 - .../android/settings.gradle.kts | 10 + examples/example-flutter/ios/Podfile | 5 + examples/example-flutter/lib/main.dart | 89 +- examples/example-flutter/linux/.gitignore | 1 - examples/example-flutter/linux/CMakeLists.txt | 128 - .../linux/flutter/CMakeLists.txt | 88 - .../flutter/generated_plugin_registrant.cc | 11 - .../flutter/generated_plugin_registrant.h | 15 - .../linux/flutter/generated_plugins.cmake | 23 - .../linux/runner/CMakeLists.txt | 26 - examples/example-flutter/linux/runner/main.cc | 6 - .../linux/runner/my_application.cc | 130 - .../linux/runner/my_application.h | 18 - examples/example-flutter/macos/.gitignore | 7 - .../macos/Flutter/Flutter-Debug.xcconfig | 2 - .../macos/Flutter/Flutter-Release.xcconfig | 2 - .../Flutter/GeneratedPluginRegistrant.swift | 10 - examples/example-flutter/macos/Podfile | 42 - examples/example-flutter/macos/Podfile.lock | 16 - .../macos/Runner.xcodeproj/project.pbxproj | 783 --- .../xcshareddata/IDEWorkspaceChecks.plist | 8 - .../xcshareddata/xcschemes/Runner.xcscheme | 99 - .../contents.xcworkspacedata | 10 - .../xcshareddata/IDEWorkspaceChecks.plist | 8 - .../macos/Runner/AppDelegate.swift | 13 - .../AppIcon.appiconset/Contents.json | 68 - .../AppIcon.appiconset/app_icon_1024.png | Bin 102994 -> 0 bytes .../AppIcon.appiconset/app_icon_128.png | Bin 5680 -> 0 bytes .../AppIcon.appiconset/app_icon_16.png | Bin 520 -> 0 bytes .../AppIcon.appiconset/app_icon_256.png | Bin 14142 -> 0 bytes .../AppIcon.appiconset/app_icon_32.png | Bin 1066 -> 0 bytes .../AppIcon.appiconset/app_icon_512.png | Bin 36406 -> 0 bytes .../AppIcon.appiconset/app_icon_64.png | Bin 2218 -> 0 bytes .../macos/Runner/Base.lproj/MainMenu.xib | 343 -- .../macos/Runner/Configs/AppInfo.xcconfig | 14 - .../macos/Runner/Configs/Debug.xcconfig | 2 - .../macos/Runner/Configs/Release.xcconfig | 2 - .../macos/Runner/Configs/Warnings.xcconfig | 13 - .../macos/Runner/DebugProfile.entitlements | 12 - .../example-flutter/macos/Runner/Info.plist | 32 - .../macos/Runner/MainFlutterWindow.swift | 15 - .../macos/Runner/Release.entitlements | 8 - .../macos/RunnerTests/RunnerTests.swift | 12 - examples/example-flutter/pubspec.lock | 143 +- examples/example-flutter/pubspec.yaml | 81 +- .../example-flutter/test/widget_test.dart | 24 - examples/example-flutter/web/favicon.png | Bin 917 -> 0 bytes .../example-flutter/web/icons/Icon-192.png | Bin 5292 -> 0 bytes .../example-flutter/web/icons/Icon-512.png | Bin 8252 -> 0 bytes .../web/icons/Icon-maskable-192.png | Bin 5594 -> 0 bytes .../web/icons/Icon-maskable-512.png | Bin 20998 -> 0 bytes examples/example-flutter/web/index.html | 38 - examples/example-flutter/web/manifest.json | 35 - examples/example-flutter/windows/.gitignore | 17 - .../example-flutter/windows/CMakeLists.txt | 108 - .../windows/flutter/CMakeLists.txt | 109 - .../flutter/generated_plugin_registrant.cc | 11 - .../flutter/generated_plugin_registrant.h | 15 - .../windows/flutter/generated_plugins.cmake | 23 - .../windows/runner/CMakeLists.txt | 40 - .../example-flutter/windows/runner/Runner.rc | 121 - .../windows/runner/flutter_window.cpp | 71 - .../windows/runner/flutter_window.h | 33 - .../example-flutter/windows/runner/main.cpp | 43 - .../example-flutter/windows/runner/resource.h | 16 - .../windows/runner/resources/app_icon.ico | Bin 33772 -> 0 bytes .../windows/runner/runner.exe.manifest | 14 - .../example-flutter/windows/runner/utils.cpp | 65 - .../example-flutter/windows/runner/utils.h | 19 - .../windows/runner/win32_window.cpp | 288 - .../windows/runner/win32_window.h | 102 - examples/example-ionic/.eslintrc | 12 + examples/example-ionic/package.json | 4 +- examples/example-ionic/src/app.routes.ts | 4 +- examples/example-ionic/src/home.page.ts | 2 +- examples/example-ionic/src/main.ts | 24 +- examples/example-ionic/src/profile.page.ts | 4 +- examples/example-kmp/.gitignore | 7 + .../example-kmp/composeApp/build.gradle.kts | 42 + .../src/androidMain/AndroidManifest.xml | 17 + .../app/screeb/example/kmp/MainActivity.kt | 14 + .../kotlin/app/screeb/example/kmp/App.kt | 110 + .../screeb/example/kmp/MainViewController.kt | 7 + examples/example-kmp/gradle.properties | 3 + .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 54329 bytes .../gradle/wrapper/gradle-wrapper.properties | 7 + examples/example-kmp/gradlew | 172 + examples/example-kmp/gradlew.bat | 84 + examples/example-kmp/settings.gradle.kts | 35 + examples/example-maui/App.xaml.cs | 2 +- examples/example-maui/README.md | 1 - examples/example-maui/example-maui.csproj | 1 + examples/example-react/package.json | 4 +- .../example-reactnative/android/build.gradle | 13 + .../android/settings.gradle | 18 + examples/example-reactnative/ios/Podfile | 30 + examples/example-reactnative/ios/Podfile.lock | 12 +- examples/example-reactnative/jest.config.js | 3 - .../react-native.config.js | 4 +- examples/example-svelte/.eslintrc | 8 + examples/example-svelte/README.md | 27 + examples/example-svelte/index.html | 21 + examples/example-svelte/package.json | 44 + examples/example-svelte/src/App.svelte | 27 + examples/example-svelte/src/ScreebDemo.svelte | 103 + examples/example-svelte/src/app.css | 41 + examples/example-svelte/src/main.ts | 9 + examples/example-svelte/src/vite-env.d.ts | 2 + examples/example-svelte/svelte.config.js | 7 + examples/example-svelte/tsconfig.json | 22 + examples/example-svelte/tsconfig.node.json | 9 + examples/example-svelte/vite.config.ts | 7 + examples/example-vue/.eslintrc | 12 + examples/example-vue/package.json | 2 +- examples/example-vue/src/main.ts | 18 +- package-lock.json | 5281 ++--------------- package.json | 18 +- packages/eslint-config/package.json | 1 - packages/eslint-config/src/index.js | 4 +- packages/screeb-template-lib/package.json | 3 - packages/sdk-angular/README.md | 14 +- packages/sdk-angular/angular.json | 12 +- packages/sdk-angular/docs/.nojekyll | 1 - packages/sdk-angular/docs/README.md | 11 - packages/sdk-angular/docs/classes/Screeb.md | 631 -- .../sdk-angular/docs/classes/ScreebConfig.md | 100 - .../sdk-angular/docs/classes/ScreebModule.md | 69 - packages/sdk-angular/package.json | 15 +- .../projects/sdk-angular/src/lib/screeb.ts | 21 + .../projects/sdk-angular/tsconfig.spec.json | 14 - packages/sdk-angular/typedoc.json | 9 - packages/sdk-browser/README.md | 14 +- packages/sdk-browser/docs/.nojekyll | 1 - packages/sdk-browser/docs/README.md | 1197 ---- packages/sdk-browser/jest.config.ts | 6 - packages/sdk-browser/package.json | 13 +- packages/sdk-browser/src/index.test.ts | 58 - packages/sdk-browser/src/index.ts | 26 + packages/sdk-browser/typedoc.json | 9 - packages/sdk-flutter/README.md | 26 +- packages/sdk-flutter/android/build.gradle | 8 +- .../plugin_screeb/PluginScreebPlugin.kt | 110 +- .../ios/Classes/SwiftPluginScreebPlugin.swift | 86 +- .../sdk-flutter/ios/plugin_screeb.podspec | 6 +- packages/sdk-flutter/lib/plugin_screeb.dart | 227 +- packages/sdk-flutter/pubspec.yaml | 4 +- .../sdk-flutter/test/screeb_hooks_test.dart | 51 + .../test/screeb_privacy_widgets_test.dart | 51 + packages/sdk-kmp/README.md | 169 + packages/sdk-kmp/build.gradle.kts | 180 + packages/sdk-kmp/gradle.properties | 22 + .../sdk-kmp/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 54329 bytes .../gradle/wrapper/gradle-wrapper.properties | 7 + packages/sdk-kmp/gradlew | 172 + packages/sdk-kmp/gradlew.bat | 84 + packages/sdk-kmp/iosInterop/Screeb.def | 3 + packages/sdk-kmp/readme/screeb-logo.svg | 15 + packages/sdk-kmp/settings.gradle.kts | 27 + .../kotlin/app/screeb/sdk/kmp/HooksAndroid.kt | 37 + .../app/screeb/sdk/kmp/Platform.android.kt | 5 + .../app/screeb/sdk/kmp/Screeb.android.kt | 214 + .../app/screeb/sdk/kmp/ScreebView.android.kt | 19 + .../kotlin/app/screeb/sdk/kmp/Screeb.kt | 79 + .../kotlin/app/screeb/sdk/kmp/ScreebHooks.kt | 42 + .../app/screeb/sdk/kmp/ScreebInitOptions.kt | 6 + .../kotlin/app/screeb/sdk/kmp/ScreebUtils.kt | 21 + .../kotlin/app/screeb/sdk/kmp/SdkVersion.kt | 3 + .../app/screeb/sdk/kmp/HooksRegistryTest.kt | 64 + .../app/screeb/sdk/kmp/ScreebUtilsTest.kt | 40 + .../kotlin/app/screeb/sdk/kmp/HooksIOS.kt | 38 + .../kotlin/app/screeb/sdk/kmp/Platform.ios.kt | 5 + .../kotlin/app/screeb/sdk/kmp/Screeb.ios.kt | 236 + .../app/screeb/sdk/kmp/ScreebView.ios.kt | 28 + .../Platforms/Android/HooksAndroid.cs | 80 +- .../Platforms/Android/Screeb.Android.cs | 29 +- .../Platforms/Android/ScreebView.Android.cs | 49 + .../sdk-maui/Platforms/iOS/ApiDefinitions.cs | 28 + packages/sdk-maui/Platforms/iOS/HooksIOS.cs | 58 +- packages/sdk-maui/Platforms/iOS/Screeb.iOS.cs | 21 +- .../sdk-maui/Platforms/iOS/ScreebClassPtr.cs | 5 + .../sdk-maui/Platforms/iOS/ScreebView.iOS.cs | 39 + packages/sdk-maui/README.md | 78 +- packages/sdk-maui/ScreebMaui.csproj | 74 +- packages/sdk-maui/ScreebUtils.cs | 7 +- packages/sdk-maui/ScreebViewExtensions.cs | 35 + .../sdk-maui/Transforms/AndroidMetadata.xml | 9 + packages/sdk-maui/readme/screeb-logo.svg | 15 + packages/sdk-maui/tests/ScreebHooksTests.cs | 67 + packages/sdk-maui/tests/ScreebUtilsTests.cs | 19 + .../sdk-maui/tests/ScreebUtilsTests.csproj | 3 +- packages/sdk-react/README.md | 12 +- packages/sdk-react/docs/.nojekyll | 1 - packages/sdk-react/docs/README.md | 774 --- packages/sdk-react/jest.config.ts | 8 - packages/sdk-react/package.json | 11 +- packages/sdk-react/src/provider.tsx | 9 +- packages/sdk-react/src/types.ts | 6 + packages/sdk-react/typedoc.json | 9 - packages/sdk-reactnative/README.md | 38 +- .../sdk-reactnative/ScreebReactNative.podspec | 6 +- packages/sdk-reactnative/android/build.gradle | 8 +- .../reactnative/ScreebReactNativeModule.kt | 109 +- packages/sdk-reactnative/eslint.config.mjs | 47 +- .../sdk-reactnative/ios/ScreebReactNative.m | 60 +- .../ios/ScreebReactNative.swift | 125 +- packages/sdk-reactnative/package.json | 28 +- packages/sdk-reactnative/src/index.tsx | 237 +- packages/sdk-reactnative/test-types/hooks.ts | 13 + .../test-types/privacy-helpers.tsx | 14 + packages/sdk-svelte/.eslintrc | 8 + packages/sdk-svelte/README.md | 123 + packages/sdk-svelte/package.json | 64 + packages/sdk-svelte/readme/screeb-logo.svg | 15 + packages/sdk-svelte/src/client.ts | 168 + packages/sdk-svelte/src/constants.ts | 3 + packages/sdk-svelte/src/context.ts | 35 + packages/sdk-svelte/src/index.ts | 8 + packages/sdk-svelte/src/logger.ts | 8 + packages/sdk-svelte/src/types.ts | 134 + packages/sdk-svelte/src/utils.ts | 1 + packages/sdk-svelte/tsconfig.json | 10 + packages/sdk-vue/README.md | 66 +- packages/sdk-vue/docs/.nojekyll | 1 - packages/sdk-vue/docs/README.md | 452 -- packages/sdk-vue/package.json | 6 +- packages/sdk-vue/readme/screeb-logo.svg | 15 + packages/sdk-vue/src/plugin.ts | 11 +- packages/sdk-vue/src/types.ts | 6 + packages/sdk-vue/typedoc.json | 9 - scripts/build-local-ios-xcframework.mjs | 106 + scripts/report-sdk-sizes.mjs | 211 + scripts/sync-sdk-versions.mjs | 174 + scripts/update-public-docs-reference.mjs | 1349 +++++ scripts/verify-release.mjs | 185 + sdk-versions.json | 13 + 304 files changed, 8641 insertions(+), 13227 deletions(-) create mode 100644 .github/workflows/publish-kmp.yml create mode 100644 .github/workflows/publish-svelte.yml create mode 100644 docs/screeb-team-release.md create mode 100644 examples/example-android/.gitignore create mode 100644 examples/example-android/app/build.gradle create mode 100644 examples/example-android/app/proguard-rules.pro create mode 100644 examples/example-android/app/src/main/AndroidManifest.xml create mode 100644 examples/example-android/app/src/main/res/values/styles.xml create mode 100644 examples/example-android/build.gradle create mode 100644 examples/example-android/gradle.properties create mode 100644 examples/example-android/gradle/wrapper/gradle-wrapper.jar create mode 100644 examples/example-android/gradle/wrapper/gradle-wrapper.properties create mode 100755 examples/example-android/gradlew create mode 100644 examples/example-android/gradlew.bat create mode 100644 examples/example-android/settings.gradle delete mode 100644 examples/example-expo/app/(tabs)/_layout.tsx delete mode 100644 examples/example-expo/app/(tabs)/explore.tsx delete mode 100644 examples/example-expo/app/(tabs)/index.tsx create mode 100644 examples/example-expo/app/index.tsx delete mode 100644 examples/example-expo/app/modal.tsx delete mode 100644 examples/example-expo/assets/images/android-icon-background.png delete mode 100644 examples/example-expo/assets/images/android-icon-foreground.png delete mode 100644 examples/example-expo/assets/images/android-icon-monochrome.png delete mode 100644 examples/example-expo/assets/images/favicon.png delete mode 100644 examples/example-expo/assets/images/icon.png delete mode 100644 examples/example-expo/assets/images/partial-react-logo.png delete mode 100644 examples/example-expo/assets/images/react-logo.png delete mode 100644 examples/example-expo/assets/images/react-logo@2x.png delete mode 100644 examples/example-expo/assets/images/react-logo@3x.png delete mode 100644 examples/example-expo/assets/images/splash-icon.png delete mode 100644 examples/example-expo/components/external-link.tsx delete mode 100644 examples/example-expo/components/haptic-tab.tsx delete mode 100644 examples/example-expo/components/hello-wave.tsx delete mode 100644 examples/example-expo/components/parallax-scroll-view.tsx delete mode 100644 examples/example-expo/components/themed-text.tsx delete mode 100644 examples/example-expo/components/themed-view.tsx delete mode 100644 examples/example-expo/components/ui/collapsible.tsx delete mode 100644 examples/example-expo/components/ui/icon-symbol.ios.tsx delete mode 100644 examples/example-expo/components/ui/icon-symbol.tsx delete mode 100644 examples/example-expo/constants/theme.ts delete mode 100644 examples/example-expo/hooks/use-color-scheme.ts delete mode 100644 examples/example-expo/hooks/use-color-scheme.web.ts delete mode 100644 examples/example-expo/hooks/use-theme-color.ts create mode 100644 examples/example-expo/plugins/withScreebLocalSdk.js delete mode 100755 examples/example-expo/scripts/reset-project.js delete mode 100644 examples/example-flutter/linux/.gitignore delete mode 100644 examples/example-flutter/linux/CMakeLists.txt delete mode 100644 examples/example-flutter/linux/flutter/CMakeLists.txt delete mode 100644 examples/example-flutter/linux/flutter/generated_plugin_registrant.cc delete mode 100644 examples/example-flutter/linux/flutter/generated_plugin_registrant.h delete mode 100644 examples/example-flutter/linux/flutter/generated_plugins.cmake delete mode 100644 examples/example-flutter/linux/runner/CMakeLists.txt delete mode 100644 examples/example-flutter/linux/runner/main.cc delete mode 100644 examples/example-flutter/linux/runner/my_application.cc delete mode 100644 examples/example-flutter/linux/runner/my_application.h delete mode 100644 examples/example-flutter/macos/.gitignore delete mode 100644 examples/example-flutter/macos/Flutter/Flutter-Debug.xcconfig delete mode 100644 examples/example-flutter/macos/Flutter/Flutter-Release.xcconfig delete mode 100644 examples/example-flutter/macos/Flutter/GeneratedPluginRegistrant.swift delete mode 100644 examples/example-flutter/macos/Podfile delete mode 100644 examples/example-flutter/macos/Podfile.lock delete mode 100644 examples/example-flutter/macos/Runner.xcodeproj/project.pbxproj delete mode 100644 examples/example-flutter/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist delete mode 100644 examples/example-flutter/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme delete mode 100644 examples/example-flutter/macos/Runner.xcworkspace/contents.xcworkspacedata delete mode 100644 examples/example-flutter/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist delete mode 100644 examples/example-flutter/macos/Runner/AppDelegate.swift delete mode 100644 examples/example-flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json delete mode 100644 examples/example-flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png delete mode 100644 examples/example-flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png delete mode 100644 examples/example-flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png delete mode 100644 examples/example-flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png delete mode 100644 examples/example-flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png delete mode 100644 examples/example-flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png delete mode 100644 examples/example-flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png delete mode 100644 examples/example-flutter/macos/Runner/Base.lproj/MainMenu.xib delete mode 100644 examples/example-flutter/macos/Runner/Configs/AppInfo.xcconfig delete mode 100644 examples/example-flutter/macos/Runner/Configs/Debug.xcconfig delete mode 100644 examples/example-flutter/macos/Runner/Configs/Release.xcconfig delete mode 100644 examples/example-flutter/macos/Runner/Configs/Warnings.xcconfig delete mode 100644 examples/example-flutter/macos/Runner/DebugProfile.entitlements delete mode 100644 examples/example-flutter/macos/Runner/Info.plist delete mode 100644 examples/example-flutter/macos/Runner/MainFlutterWindow.swift delete mode 100644 examples/example-flutter/macos/Runner/Release.entitlements delete mode 100644 examples/example-flutter/macos/RunnerTests/RunnerTests.swift delete mode 100644 examples/example-flutter/test/widget_test.dart delete mode 100644 examples/example-flutter/web/favicon.png delete mode 100644 examples/example-flutter/web/icons/Icon-192.png delete mode 100644 examples/example-flutter/web/icons/Icon-512.png delete mode 100644 examples/example-flutter/web/icons/Icon-maskable-192.png delete mode 100644 examples/example-flutter/web/icons/Icon-maskable-512.png delete mode 100644 examples/example-flutter/web/index.html delete mode 100644 examples/example-flutter/web/manifest.json delete mode 100644 examples/example-flutter/windows/.gitignore delete mode 100644 examples/example-flutter/windows/CMakeLists.txt delete mode 100644 examples/example-flutter/windows/flutter/CMakeLists.txt delete mode 100644 examples/example-flutter/windows/flutter/generated_plugin_registrant.cc delete mode 100644 examples/example-flutter/windows/flutter/generated_plugin_registrant.h delete mode 100644 examples/example-flutter/windows/flutter/generated_plugins.cmake delete mode 100644 examples/example-flutter/windows/runner/CMakeLists.txt delete mode 100644 examples/example-flutter/windows/runner/Runner.rc delete mode 100644 examples/example-flutter/windows/runner/flutter_window.cpp delete mode 100644 examples/example-flutter/windows/runner/flutter_window.h delete mode 100644 examples/example-flutter/windows/runner/main.cpp delete mode 100644 examples/example-flutter/windows/runner/resource.h delete mode 100644 examples/example-flutter/windows/runner/resources/app_icon.ico delete mode 100644 examples/example-flutter/windows/runner/runner.exe.manifest delete mode 100644 examples/example-flutter/windows/runner/utils.cpp delete mode 100644 examples/example-flutter/windows/runner/utils.h delete mode 100644 examples/example-flutter/windows/runner/win32_window.cpp delete mode 100644 examples/example-flutter/windows/runner/win32_window.h create mode 100644 examples/example-ionic/.eslintrc create mode 100644 examples/example-kmp/.gitignore create mode 100644 examples/example-kmp/composeApp/build.gradle.kts create mode 100644 examples/example-kmp/composeApp/src/androidMain/AndroidManifest.xml create mode 100644 examples/example-kmp/composeApp/src/androidMain/kotlin/app/screeb/example/kmp/MainActivity.kt create mode 100644 examples/example-kmp/composeApp/src/commonMain/kotlin/app/screeb/example/kmp/App.kt create mode 100644 examples/example-kmp/composeApp/src/iosMain/kotlin/app/screeb/example/kmp/MainViewController.kt create mode 100644 examples/example-kmp/gradle.properties create mode 100644 examples/example-kmp/gradle/wrapper/gradle-wrapper.jar create mode 100644 examples/example-kmp/gradle/wrapper/gradle-wrapper.properties create mode 100755 examples/example-kmp/gradlew create mode 100644 examples/example-kmp/gradlew.bat create mode 100644 examples/example-kmp/settings.gradle.kts delete mode 100644 examples/example-reactnative/jest.config.js create mode 100644 examples/example-svelte/.eslintrc create mode 100644 examples/example-svelte/README.md create mode 100644 examples/example-svelte/index.html create mode 100644 examples/example-svelte/package.json create mode 100644 examples/example-svelte/src/App.svelte create mode 100644 examples/example-svelte/src/ScreebDemo.svelte create mode 100644 examples/example-svelte/src/app.css create mode 100644 examples/example-svelte/src/main.ts create mode 100644 examples/example-svelte/src/vite-env.d.ts create mode 100644 examples/example-svelte/svelte.config.js create mode 100644 examples/example-svelte/tsconfig.json create mode 100644 examples/example-svelte/tsconfig.node.json create mode 100644 examples/example-svelte/vite.config.ts create mode 100644 examples/example-vue/.eslintrc delete mode 100644 packages/sdk-angular/docs/.nojekyll delete mode 100644 packages/sdk-angular/docs/README.md delete mode 100644 packages/sdk-angular/docs/classes/Screeb.md delete mode 100644 packages/sdk-angular/docs/classes/ScreebConfig.md delete mode 100644 packages/sdk-angular/docs/classes/ScreebModule.md delete mode 100644 packages/sdk-angular/projects/sdk-angular/tsconfig.spec.json delete mode 100644 packages/sdk-angular/typedoc.json delete mode 100644 packages/sdk-browser/docs/.nojekyll delete mode 100644 packages/sdk-browser/docs/README.md delete mode 100644 packages/sdk-browser/jest.config.ts delete mode 100644 packages/sdk-browser/src/index.test.ts delete mode 100644 packages/sdk-browser/typedoc.json create mode 100644 packages/sdk-flutter/test/screeb_hooks_test.dart create mode 100644 packages/sdk-flutter/test/screeb_privacy_widgets_test.dart create mode 100644 packages/sdk-kmp/README.md create mode 100644 packages/sdk-kmp/build.gradle.kts create mode 100644 packages/sdk-kmp/gradle.properties create mode 100644 packages/sdk-kmp/gradle/wrapper/gradle-wrapper.jar create mode 100644 packages/sdk-kmp/gradle/wrapper/gradle-wrapper.properties create mode 100755 packages/sdk-kmp/gradlew create mode 100644 packages/sdk-kmp/gradlew.bat create mode 100644 packages/sdk-kmp/iosInterop/Screeb.def create mode 100644 packages/sdk-kmp/readme/screeb-logo.svg create mode 100644 packages/sdk-kmp/settings.gradle.kts create mode 100644 packages/sdk-kmp/src/androidMain/kotlin/app/screeb/sdk/kmp/HooksAndroid.kt create mode 100644 packages/sdk-kmp/src/androidMain/kotlin/app/screeb/sdk/kmp/Platform.android.kt create mode 100644 packages/sdk-kmp/src/androidMain/kotlin/app/screeb/sdk/kmp/Screeb.android.kt create mode 100644 packages/sdk-kmp/src/androidMain/kotlin/app/screeb/sdk/kmp/ScreebView.android.kt create mode 100644 packages/sdk-kmp/src/commonMain/kotlin/app/screeb/sdk/kmp/Screeb.kt create mode 100644 packages/sdk-kmp/src/commonMain/kotlin/app/screeb/sdk/kmp/ScreebHooks.kt create mode 100644 packages/sdk-kmp/src/commonMain/kotlin/app/screeb/sdk/kmp/ScreebInitOptions.kt create mode 100644 packages/sdk-kmp/src/commonMain/kotlin/app/screeb/sdk/kmp/ScreebUtils.kt create mode 100644 packages/sdk-kmp/src/commonMain/kotlin/app/screeb/sdk/kmp/SdkVersion.kt create mode 100644 packages/sdk-kmp/src/commonTest/kotlin/app/screeb/sdk/kmp/HooksRegistryTest.kt create mode 100644 packages/sdk-kmp/src/commonTest/kotlin/app/screeb/sdk/kmp/ScreebUtilsTest.kt create mode 100644 packages/sdk-kmp/src/iosMain/kotlin/app/screeb/sdk/kmp/HooksIOS.kt create mode 100644 packages/sdk-kmp/src/iosMain/kotlin/app/screeb/sdk/kmp/Platform.ios.kt create mode 100644 packages/sdk-kmp/src/iosMain/kotlin/app/screeb/sdk/kmp/Screeb.ios.kt create mode 100644 packages/sdk-kmp/src/iosMain/kotlin/app/screeb/sdk/kmp/ScreebView.ios.kt create mode 100644 packages/sdk-maui/Platforms/Android/ScreebView.Android.cs create mode 100644 packages/sdk-maui/Platforms/iOS/ScreebView.iOS.cs create mode 100644 packages/sdk-maui/ScreebViewExtensions.cs create mode 100644 packages/sdk-maui/Transforms/AndroidMetadata.xml create mode 100644 packages/sdk-maui/readme/screeb-logo.svg create mode 100644 packages/sdk-maui/tests/ScreebHooksTests.cs delete mode 100644 packages/sdk-react/docs/.nojekyll delete mode 100644 packages/sdk-react/docs/README.md delete mode 100644 packages/sdk-react/jest.config.ts delete mode 100644 packages/sdk-react/typedoc.json create mode 100644 packages/sdk-reactnative/test-types/hooks.ts create mode 100644 packages/sdk-reactnative/test-types/privacy-helpers.tsx create mode 100644 packages/sdk-svelte/.eslintrc create mode 100644 packages/sdk-svelte/README.md create mode 100644 packages/sdk-svelte/package.json create mode 100644 packages/sdk-svelte/readme/screeb-logo.svg create mode 100644 packages/sdk-svelte/src/client.ts create mode 100644 packages/sdk-svelte/src/constants.ts create mode 100644 packages/sdk-svelte/src/context.ts create mode 100644 packages/sdk-svelte/src/index.ts create mode 100644 packages/sdk-svelte/src/logger.ts create mode 100644 packages/sdk-svelte/src/types.ts create mode 100644 packages/sdk-svelte/src/utils.ts create mode 100644 packages/sdk-svelte/tsconfig.json delete mode 100644 packages/sdk-vue/docs/.nojekyll delete mode 100644 packages/sdk-vue/docs/README.md create mode 100644 packages/sdk-vue/readme/screeb-logo.svg delete mode 100644 packages/sdk-vue/typedoc.json create mode 100755 scripts/build-local-ios-xcframework.mjs create mode 100755 scripts/report-sdk-sizes.mjs create mode 100755 scripts/sync-sdk-versions.mjs create mode 100644 scripts/update-public-docs-reference.mjs create mode 100644 scripts/verify-release.mjs create mode 100644 sdk-versions.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6d0287a..8e48638 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,47 +5,94 @@ on: branches: [master] pull_request: branches: [master] + workflow_dispatch: + inputs: + run_macos_native: + description: 'Run macOS-only MAUI iOS and full KMP checks' + required: false + type: boolean + default: false + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + HUSKY: 0 + NX_DAEMON: false + NX_NO_CLOUD: true + NX_DISABLE_DB: true jobs: js: name: JS/TS packages - runs-on: ubuntu-latest - env: - HUSKY: 0 - NX_DAEMON: false - NX_NO_CLOUD: true - NX_DISABLE_DB: true + runs-on: [self-hosted, linux] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '20' + - run: npm ci --ignore-scripts + - run: npm run ci:js + - run: npm run examples:check + + react-native: + name: React Native release check + runs-on: [self-hosted, linux] steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '20' - - run: npm install --ignore-scripts - - run: npm run build - - run: npm test + - run: npm ci --ignore-scripts + - run: npm run versions:check + - run: npm run release:check --workspace=packages/sdk-reactnative flutter: name: Flutter package - runs-on: ubuntu-latest + runs-on: [self-hosted, linux] steps: - uses: actions/checkout@v4 + - uses: android-actions/setup-android@v3 - uses: subosito/flutter-action@v2 with: flutter-version: '3.x' channel: 'stable' - run: cd packages/sdk-flutter && flutter pub get - run: cd packages/sdk-flutter && flutter analyze + - run: cd packages/sdk-flutter && flutter test - maui: - name: .NET MAUI SDK + maui-android: + name: .NET MAUI Android SDK + runs-on: [self-hosted, linux] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-dotnet@v4 + with: + dotnet-version: '9.0.x' + - name: Install MAUI workloads + run: dotnet workload install android + - name: Restore + run: dotnet restore packages/sdk-maui/ScreebMaui.csproj + - name: Build Android + run: dotnet build packages/sdk-maui/ScreebMaui.csproj -f net9.0-android --no-restore + - name: Run unit tests + run: dotnet test packages/sdk-maui/tests/ScreebUtilsTests.csproj + + maui-ios: + name: .NET MAUI iOS SDK runs-on: macos-latest + if: ${{ github.event_name == 'workflow_dispatch' && inputs.run_macos_native }} steps: - uses: actions/checkout@v4 - uses: actions/setup-dotnet@v4 with: dotnet-version: '9.0.x' - name: Install MAUI workloads - run: dotnet workload install android ios + run: dotnet workload install ios - name: Download Screeb iOS XCFramework run: | SCREEB_IOS_URL=$(curl -sL https://api.github.com/repos/ScreebApp/sdk-ios-public/releases/latest | python3 -c "import sys,json; print(next(a['browser_download_url'] for a in json.load(sys.stdin)['assets'] if a['name']=='Screeb.zip'))") @@ -55,9 +102,44 @@ jobs: cp -r /tmp/screeb_ios/Screeb.xcframework packages/sdk-maui/native/ios/ - name: Restore run: dotnet restore packages/sdk-maui/ScreebMaui.csproj - - name: Build Android - run: dotnet build packages/sdk-maui/ScreebMaui.csproj -f net9.0-android --no-restore - name: Build iOS run: dotnet build packages/sdk-maui/ScreebMaui.csproj -f net9.0-ios --no-restore - - name: Run unit tests - run: dotnet test packages/sdk-maui/tests/ScreebUtilsTests.csproj + + kmp-android: + name: Kotlin Multiplatform Android SDK + runs-on: [self-hosted, linux] + steps: + - uses: actions/checkout@v4 + - uses: android-actions/setup-android@v3 + - uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + - name: Make gradlew executable + run: chmod +x packages/sdk-kmp/gradlew + - name: Build Android target + run: cd packages/sdk-kmp && ./gradlew assembleRelease testDebugUnitTest --no-daemon + + kmp-full: + name: Kotlin Multiplatform full SDK + runs-on: macos-latest + if: ${{ github.event_name == 'workflow_dispatch' && inputs.run_macos_native }} + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + - name: Download Screeb iOS XCFramework + run: | + set -euo pipefail + SCREEB_IOS_SDK_VERSION=$(grep '^SCREEB_IOS_SDK_VERSION=' packages/sdk-kmp/gradle.properties | cut -d= -f2) + SCREEB_IOS_URL=$(curl -sL "https://api.github.com/repos/ScreebApp/sdk-ios-public/releases/tags/v${SCREEB_IOS_SDK_VERSION}" | python3 -c "import sys,json; print(next(a['browser_download_url'] for a in json.load(sys.stdin)['assets'] if a['name']=='Screeb.zip'))") + curl -sL "$SCREEB_IOS_URL" -o /tmp/Screeb.zip + unzip -q /tmp/Screeb.zip -d /tmp/screeb_ios + mkdir -p packages/sdk-kmp/native/ios + cp -r /tmp/screeb_ios/Screeb.xcframework packages/sdk-kmp/native/ios/ + - name: Make gradlew executable + run: chmod +x packages/sdk-kmp/gradlew + - name: Build SDK + run: cd packages/sdk-kmp && ./gradlew clean build --no-daemon diff --git a/.github/workflows/publish-angular.yml b/.github/workflows/publish-angular.yml index 0cb22ea..7477f15 100644 --- a/.github/workflows/publish-angular.yml +++ b/.github/workflows/publish-angular.yml @@ -17,6 +17,7 @@ jobs: with: node-version: '20' registry-url: 'https://registry.npmjs.org' - - run: npm install --ignore-scripts - - run: npm run build --workspace=packages/sdk-angular + - run: npm ci --ignore-scripts + - run: npm run versions:check + - run: npm run release:check --workspace=packages/sdk-angular - run: npm publish --workspace=packages/sdk-angular --access public --provenance diff --git a/.github/workflows/publish-browser.yml b/.github/workflows/publish-browser.yml index 2369ce2..3a709c1 100644 --- a/.github/workflows/publish-browser.yml +++ b/.github/workflows/publish-browser.yml @@ -17,6 +17,7 @@ jobs: with: node-version: '20' registry-url: 'https://registry.npmjs.org' - - run: npm install --ignore-scripts - - run: npm run build --workspace=packages/sdk-browser + - run: npm ci --ignore-scripts + - run: npm run versions:check + - run: npm run release:check --workspace=packages/sdk-browser - run: npm publish --workspace=packages/sdk-browser --access public --provenance diff --git a/.github/workflows/publish-flutter.yml b/.github/workflows/publish-flutter.yml index a4c9cd9..c18a98a 100644 --- a/.github/workflows/publish-flutter.yml +++ b/.github/workflows/publish-flutter.yml @@ -10,10 +10,14 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '20' - uses: subosito/flutter-action@v2 with: flutter-version: '3.x' channel: 'stable' + - run: npm run versions:check - name: Setup pub.dev credentials run: | mkdir -p ~/.config/dart @@ -27,4 +31,5 @@ jobs: } EOF - run: cd packages/sdk-flutter && flutter pub get + - run: cd packages/sdk-flutter && flutter analyze - run: cd packages/sdk-flutter && flutter pub publish --force diff --git a/.github/workflows/publish-kmp.yml b/.github/workflows/publish-kmp.yml new file mode 100644 index 0000000..7e19661 --- /dev/null +++ b/.github/workflows/publish-kmp.yml @@ -0,0 +1,48 @@ +name: Publish Screeb KMP SDK + +on: + push: + tags: + - 'sdk-kmp/v*' + +jobs: + publish: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: '20' + + - uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + + - run: npm run versions:check + + - name: Download Screeb iOS XCFramework + run: | + set -euo pipefail + SCREEB_IOS_SDK_VERSION=$(grep '^SCREEB_IOS_SDK_VERSION=' packages/sdk-kmp/gradle.properties | cut -d= -f2) + SCREEB_IOS_URL=$(curl -sL "https://api.github.com/repos/ScreebApp/sdk-ios-public/releases/tags/v${SCREEB_IOS_SDK_VERSION}" \ + | python3 -c "import sys,json; print(next(a['browser_download_url'] for a in json.load(sys.stdin)['assets'] if a['name']=='Screeb.zip'))") + curl -sL "$SCREEB_IOS_URL" -o /tmp/Screeb.zip + unzip -q /tmp/Screeb.zip -d /tmp/screeb_ios + mkdir -p packages/sdk-kmp/native/ios + cp -r /tmp/screeb_ios/Screeb.xcframework packages/sdk-kmp/native/ios/ + test -d packages/sdk-kmp/native/ios/Screeb.xcframework || (echo "XCFramework not found after download" && exit 1) + + - name: Make gradlew executable + run: chmod +x packages/sdk-kmp/gradlew + + - name: Publish to Maven Central + working-directory: packages/sdk-kmp + env: + OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }} + OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }} + GPG_KEY_ID: ${{ secrets.GPG_KEY_ID }} + GPG_KEY: ${{ secrets.GPG_KEY }} + GPG_PASSWORD: ${{ secrets.GPG_PASSWORD }} + run: ./gradlew publishAllPublicationsToOSSRHRepository --no-daemon diff --git a/.github/workflows/publish-maui.yml b/.github/workflows/publish-maui.yml index 6edd3d1..268e4c8 100644 --- a/.github/workflows/publish-maui.yml +++ b/.github/workflows/publish-maui.yml @@ -11,10 +11,16 @@ jobs: steps: - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '20' + - uses: actions/setup-dotnet@v4 with: dotnet-version: '9.0.x' + - run: npm run versions:check + - name: Install MAUI workloads run: dotnet workload install android ios diff --git a/.github/workflows/publish-react.yml b/.github/workflows/publish-react.yml index f7d04be..6cd377d 100644 --- a/.github/workflows/publish-react.yml +++ b/.github/workflows/publish-react.yml @@ -17,6 +17,7 @@ jobs: with: node-version: '20' registry-url: 'https://registry.npmjs.org' - - run: npm install --ignore-scripts - - run: npm run build --workspace=packages/sdk-react + - run: npm ci --ignore-scripts + - run: npm run versions:check + - run: npm run release:check --workspace=packages/sdk-react - run: npm publish --workspace=packages/sdk-react --access public --provenance diff --git a/.github/workflows/publish-reactnative.yml b/.github/workflows/publish-reactnative.yml index d19f2af..505c012 100644 --- a/.github/workflows/publish-reactnative.yml +++ b/.github/workflows/publish-reactnative.yml @@ -17,6 +17,7 @@ jobs: with: node-version: '20' registry-url: 'https://registry.npmjs.org' - - run: npm install --ignore-scripts - - run: npm run build --workspace=packages/sdk-reactnative + - run: npm ci --ignore-scripts + - run: npm run versions:check + - run: npm run release:check --workspace=packages/sdk-reactnative - run: npm publish --workspace=packages/sdk-reactnative --access public --provenance diff --git a/.github/workflows/publish-svelte.yml b/.github/workflows/publish-svelte.yml new file mode 100644 index 0000000..ef2c3bb --- /dev/null +++ b/.github/workflows/publish-svelte.yml @@ -0,0 +1,23 @@ +name: Publish @screeb/sdk-svelte + +on: + push: + tags: + - 'sdk-svelte/v*' + +jobs: + publish: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '20' + registry-url: 'https://registry.npmjs.org' + - run: npm ci --ignore-scripts + - run: npm run versions:check + - run: npm run release:check --workspace=packages/sdk-svelte + - run: npm publish --workspace=packages/sdk-svelte --access public --provenance diff --git a/.github/workflows/publish-vue.yml b/.github/workflows/publish-vue.yml index 61d7db9..781d63f 100644 --- a/.github/workflows/publish-vue.yml +++ b/.github/workflows/publish-vue.yml @@ -17,6 +17,7 @@ jobs: with: node-version: '20' registry-url: 'https://registry.npmjs.org' - - run: npm install --ignore-scripts - - run: npm run build --workspace=packages/sdk-vue + - run: npm ci --ignore-scripts + - run: npm run versions:check + - run: npm run release:check --workspace=packages/sdk-vue - run: npm publish --workspace=packages/sdk-vue --access public --provenance diff --git a/.gitignore b/.gitignore index 2c00c19..44c43e2 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ coverage build dist storybook-static +.local/ # misc .DS_Store @@ -24,6 +25,7 @@ lerna-debug.log* npm-debug.log* yarn-debug.log* yarn-error.log* +java_pid*.hprof .npmrc .idea @@ -36,8 +38,40 @@ packages/sdk-maui/native/ios/Screeb.xcframework/ packages/sdk-maui/native/ios/Screeb.zip packages/sdk-maui/native/ios/sdk-ios-public-*/ +# KMP SDK - native binaries (downloaded/built at build time) +packages/sdk-kmp/native/ios/Screeb.xcframework/ +packages/sdk-kmp/native/ios/Screeb.zip +packages/sdk-kmp/native/ios/sdk-ios-public-*/ + # .NET build artifacts +obj/ packages/sdk-maui/bin/ packages/sdk-maui/obj/ +packages/sdk-maui/tests/bin/ +packages/sdk-maui/tests/obj/ examples/example-maui/bin/ examples/example-maui/obj/ + +# Android example build artifacts +examples/example-android/.gradle/ +examples/example-android/build/ +examples/example-android/app/build/ +examples/example-android/local.properties + +# Expo generated artifacts +examples/example-expo/.expo/ +examples/example-expo/android/ +examples/example-expo/ios/ + +# Android / Gradle generated files +**/.gradle/ +**/.kotlin/ +**/.cxx/ +**/.externalNativeBuild/ +**/local.properties +captures/ + +# iOS generated files +**/ios/Pods/ +**/ios/build/ +**/ios/.xcode.env.local diff --git a/README.md b/README.md index 0b3c64d..ab991b4 100644 --- a/README.md +++ b/README.md @@ -8,16 +8,23 @@ Public SDKs for [Screeb](https://screeb.app) — the Product Discovery platform. | SDK | Package | Registry | Docs | |---|---|---|---| -| Browser | [`@screeb/sdk-browser`](packages/sdk-browser) | [npm](https://www.npmjs.com/package/@screeb/sdk-browser) | [Install](https://developers.screeb.app/sdk-js/install) | -| Angular | [`@screeb/sdk-angular`](packages/sdk-angular) | [npm](https://www.npmjs.com/package/@screeb/sdk-angular) | [Install](https://developers.screeb.app/sdk-js/install) | -| React | [`@screeb/sdk-react`](packages/sdk-react) | [npm](https://www.npmjs.com/package/@screeb/sdk-react) | [Install](https://developers.screeb.app/sdk-js/install) | -| Vue | [`@screeb/sdk-vue`](packages/sdk-vue) | [npm](https://www.npmjs.com/package/@screeb/sdk-vue) | [Install](https://developers.screeb.app/sdk-vue/install) | -| Ionic | Uses `@screeb/sdk-angular` / `@screeb/sdk-react` / `@screeb/sdk-browser` | — | [Install](https://developers.screeb.app/sdk-js/sdk-ionic) | -| React Native | [`@screeb/react-native`](packages/sdk-reactnative) | [npm](https://www.npmjs.com/package/@screeb/react-native) | [Install](https://developers.screeb.app/sdk-react-native/install) | -| .NET MAUI | [`Screeb.Maui`](packages/sdk-maui) | [NuGet](https://www.nuget.org/packages/Screeb.Maui) | [Install](https://developers.screeb.app/sdk-maui/install) | -| Flutter | [`plugin_screeb`](packages/sdk-flutter) | [pub.dev](https://pub.dev/packages/plugin_screeb) | [Install](https://developers.screeb.app/sdk-flutter/install) | -| iOS | Closed source ([`sdk-ios-public`](https://github.com/ScreebApp/sdk-ios-public) — SPM mirror) | [SPM](https://github.com/ScreebApp/sdk-ios-public) | [Install](https://developers.screeb.app/sdk-ios/install) | -| Android | Closed source | [Maven](https://central.sonatype.com/artifact/app.screeb.sdk/survey) | [Install](https://developers.screeb.app/sdk-android/install) | +| Browser | [`@screeb/sdk-browser`](packages/sdk-browser) | [npm](https://www.npmjs.com/package/@screeb/sdk-browser) | [Install](https://developers.screeb.app/sdk-browser/install) · [Reference](https://developers.screeb.app/sdk-browser/reference) | +| Angular | [`@screeb/sdk-angular`](packages/sdk-angular) | [npm](https://www.npmjs.com/package/@screeb/sdk-angular) | [Install](https://developers.screeb.app/sdk-angular/install) · [Reference](https://developers.screeb.app/sdk-angular/reference) | +| React | [`@screeb/sdk-react`](packages/sdk-react) | [npm](https://www.npmjs.com/package/@screeb/sdk-react) | [Install](https://developers.screeb.app/sdk-react/install) · [Reference](https://developers.screeb.app/sdk-react/reference) | +| Vue | [`@screeb/sdk-vue`](packages/sdk-vue) | [npm](https://www.npmjs.com/package/@screeb/sdk-vue) | [Install](https://developers.screeb.app/sdk-vue/install) · [Reference](https://developers.screeb.app/sdk-vue/reference) | +| Svelte | [`@screeb/sdk-svelte`](packages/sdk-svelte) | [npm](https://www.npmjs.com/package/@screeb/sdk-svelte) | [Install](https://developers.screeb.app/sdk-svelte/install) · [Reference](https://developers.screeb.app/sdk-svelte/reference) | +| Ionic | Uses `@screeb/sdk-angular` / `@screeb/sdk-react` / `@screeb/sdk-browser` | - | [Install](https://developers.screeb.app/sdk-ionic/install) · [Reference](https://developers.screeb.app/sdk-ionic/reference) | +| React Native | [`@screeb/react-native`](packages/sdk-reactnative) | [npm](https://www.npmjs.com/package/@screeb/react-native) | [Install](https://developers.screeb.app/sdk-react-native/install) · [Reference](https://developers.screeb.app/sdk-react-native/reference) | +| .NET MAUI | [`Screeb.Maui`](packages/sdk-maui) | [NuGet](https://www.nuget.org/packages/Screeb.Maui) | [Install](https://developers.screeb.app/sdk-maui/install) · [Reference](https://developers.screeb.app/sdk-maui/reference) | +| Flutter | [`plugin_screeb`](packages/sdk-flutter) | [pub.dev](https://pub.dev/packages/plugin_screeb) | [Install](https://developers.screeb.app/sdk-flutter/install) · [Reference](https://developers.screeb.app/sdk-flutter/reference) | +| iOS | Native SDK ([`sdk-ios-public`](https://github.com/ScreebApp/sdk-ios-public) - SPM mirror) | [SPM](https://github.com/ScreebApp/sdk-ios-public) | [Install](https://developers.screeb.app/sdk-ios/install) · [Reference](https://developers.screeb.app/sdk-ios/reference) | +| Android | Native SDK | [Maven](https://central.sonatype.com/artifact/app.screeb.sdk/survey) | [Install](https://developers.screeb.app/sdk-android/install) · [Reference](https://developers.screeb.app/sdk-android/reference) | + +## Battery usage + +Screeb is optimized to minimize battery impact. Most features are event-driven, and session replay adapts automatically to app activity and device conditions. + +When session replay is enabled, the SDK reduces work while idle and under Low Power Mode, Battery Saver, thermal pressure, or memory pressure. It prioritizes reducing image quality, resolution, and changed-region processing before lowering active capture cadence. ## Examples @@ -27,6 +34,7 @@ Public SDKs for [Screeb](https://screeb.app) — the Product Discovery platform. | Angular | Angular 16 | [`examples/example-angular`](examples/example-angular) | | React | React 18 | [`examples/example-react`](examples/example-react) | | Vue | Vue 3 | [`examples/example-vue`](examples/example-vue) | +| Svelte | Svelte 4 | [`examples/example-svelte`](examples/example-svelte) | | Ionic | Angular 16 + Capacitor | [`examples/example-ionic`](examples/example-ionic) | | Expo | React Native + Expo | [`examples/example-expo`](examples/example-expo) | | React Native | React Native CLI | [`examples/example-reactnative`](examples/example-reactnative) | @@ -38,3 +46,7 @@ Public SDKs for [Screeb](https://screeb.app) — the Product Discovery platform. ## Contributing See [CONTRIBUTING.md](CONTRIBUTING.md). + +## Screeb Team + +Internal release and local SDK testing notes are in [docs/screeb-team-release.md](docs/screeb-team-release.md). diff --git a/commitlint.config.js b/commitlint.config.js index 464f831..a1234ee 100644 --- a/commitlint.config.js +++ b/commitlint.config.js @@ -15,7 +15,9 @@ module.exports = { "sdk-vue", "example-vue", "sdk-maui", - "example-maui" + "example-maui", + "sdk-kmp", + "example-kmp" ]], "scope-empty": [2, "never"], "scope-min-length": [2, "always", 1], diff --git a/docs/screeb-team-release.md b/docs/screeb-team-release.md new file mode 100644 index 0000000..ed24091 --- /dev/null +++ b/docs/screeb-team-release.md @@ -0,0 +1,161 @@ +# Screeb team release workflow + +Internal notes for updating wrapper versions and testing local native SDK changes before publishing. + +## Version source of truth + +Native and wrapper versions live in `sdk-versions.json`. + +```bash +npm run versions:sync +npm run versions:check +``` + +Edit `sdk-versions.json`, then run `versions:sync` to update generated version references. Android and iOS native SDK versions are independent: use `native.android` for Maven Central and `native.ios` for CocoaPods/SPM. + +## Local native SDK testing + +Keep the repositories next to each other for the default local flow: + +```text +Screeb/ + sdk/ + sdk-android/ + sdk-ios/ +``` + +Then build the wrapper or example with: + +```bash +SCREEB_USE_LOCAL_SDK=true +``` + +Optional overrides: + +```bash +SCREEB_ANDROID_SDK_PATH=/absolute/path/to/sdk-android +SCREEB_IOS_SDK_PATH=/absolute/path/to/sdk-ios +``` + +No native SDK release is required for this flow. + +## Useful local commands + +```bash +# Android native example +SCREEB_USE_LOCAL_SDK=true ./gradlew :app:assembleDebug --no-daemon \ + -p examples/example-android + +# React Native Android +cd examples/example-reactnative/android +SCREEB_USE_LOCAL_SDK=true ./gradlew :app:assembleDebug --no-daemon + +# Flutter Android +cd examples/example-flutter/android +SCREEB_USE_LOCAL_SDK=true ./gradlew :app:assembleDebug --no-daemon + +# Expo Android +cd examples/example-expo +SCREEB_USE_LOCAL_SDK=true npx expo prebuild --platform android --clean --no-install +cd android +SCREEB_USE_LOCAL_SDK=true ./gradlew :app:assembleDebug --no-daemon + +# KMP +SCREEB_USE_LOCAL_SDK=true ./gradlew build --no-daemon \ + -p packages/sdk-kmp + +# MAUI package +SCREEB_USE_LOCAL_SDK=true dotnet build packages/sdk-maui/ScreebMaui.csproj \ + -f net9.0-android +SCREEB_USE_LOCAL_SDK=true dotnet build packages/sdk-maui/ScreebMaui.csproj \ + -f net9.0-ios +``` + +## How the local switch works + +Android: + +- Android and KMP Gradle builds use a local Gradle composite for `../sdk-android`. +- Flutter, React Native and Expo automatically publish `../sdk-android` to Maven local during native project configuration. This keeps their normal Maven dependency path while avoiding a manual release. +- MAUI builds the local Android AAR from `../sdk-android` and binds that AAR directly. + +iOS: + +- Flutter, React Native and Expo use the local `../sdk-ios` pod when `SCREEB_USE_LOCAL_SDK=true`. +- KMP and MAUI build a temporary `Screeb.xcframework` from `../sdk-ios` through `scripts/build-local-ios-xcframework.mjs`. +- Generated local artifacts stay under ignored build folders. + +## SDK size report + +To build wrapper artifacts and inspect local sizes: + +```bash +npm run size:sdks +``` + +This is informational only. Use `npm run size:sdks -- --no-build` to read existing local artifacts without rebuilding. + +Current iOS native release reference: + +- full `Screeb.xcframework`: 1.36 MB +- iOS app embed impact: about 449.4 KB, because release apps embed only the device slice; the simulator slice is build-time only + +## Public documentation references + +Public API reference pages live in the docs repository: + +```text +../screeb/docs/public/docs//reference.md +``` + +Regenerate them from the SDK source files after any public API, hook payload, wrapper, or documentation-link change: + +```bash +npm run docs:reference:update +npm run docs:reference:coverage +``` + +The generator is `scripts/update-public-docs-reference.mjs`. Keep extraction source-driven when possible; only edit the SDK-specific descriptions, groups, links, or known capability rules in that script. The expected coverage result is complete for web SDKs; mobile SDKs may still report the intentional `Targeting check` gap until that API is public there. + +Before publishing docs, validate the public docs app: + +```bash +cd ../screeb/docs/public +pnpm typecheck +pnpm build +``` + +## Release validation + +Run the release matrix before publishing native SDKs or wrappers: + +```bash +npm run verify:release +``` + +To inspect the matrix without running it: + +```bash +npm run verify:release -- --list +``` + +To run one area only: + +```bash +npm run verify:release -- --scope=android +npm run verify:release -- --scope=flutter,react-native +``` + +The matrix uses `SCREEB_USE_LOCAL_SDK=true` by default and expects the `sdk`, `sdk-android`, and `sdk-ios` repositories to be siblings. +Set `SCREEB_IOS_TEST_DESTINATION` to override the simulator used by the iOS SDK test step. + +## Release checklist + +1. Bump the native SDK versions in `../sdk-android` and/or `../sdk-ios`. +2. Edit `sdk-versions.json`. +3. Run `npm run versions:sync`. +4. Run `npm run versions:check`. +5. Run `npm run verify:release`. +6. Run `npm run size:sdks`. +7. Release the native SDKs. +8. Release the public wrappers with the updated native dependency versions. diff --git a/examples/example-android/.gitignore b/examples/example-android/.gitignore new file mode 100644 index 0000000..c5bfecd --- /dev/null +++ b/examples/example-android/.gitignore @@ -0,0 +1,4 @@ +.gradle/ +local.properties +build/ +app/build/ diff --git a/examples/example-android/README.md b/examples/example-android/README.md index c62f611..d07d842 100644 --- a/examples/example-android/README.md +++ b/examples/example-android/README.md @@ -1,57 +1,53 @@ -# example-android +# Screeb Android Example -Minimal Android example showing Screeb SDK integration. +Complete native Android example for the Screeb Android SDK. -> The Android SDK is closed source. See [developers.screeb.app/sdk-android/install](https://developers.screeb.app/sdk-android/install) for the full documentation. +Full documentation: [developers.screeb.app/sdk-android/install](https://developers.screeb.app/sdk-android/install) + +## What This Example Covers + +- SDK initialization with visitor properties +- Deep link handling for the Screeb editor +- Identity and visitor property updates +- Event and screen tracking +- Programmatic survey/message start +- Session replay start/stop +- SDK debug command +- Optional camera/microphone permissions for media questions ## Requirements -- Android SDK 19+ (Android 4.4+) +- Android Studio +- Android SDK 35 +- JDK 17 -## Setup +## Run -`build.gradle` (project level): +From this directory: -```gradle -allprojects { - repositories { - mavenCentral() - } -} +```bash +./gradlew :app:installDebug ``` -`app/build.gradle`: +To verify the SDK consumer ProGuard rules in a minified app build: -```gradle -dependencies { - implementation 'app.screeb.sdk:survey:x.x.x' -} +```bash +./gradlew :app:assembleRelease ``` -## Permissions +## Deep Links -`AndroidManifest.xml`: +The manifest registers: ```xml - - - - - - + ``` -## Deep links (In-App Message editor) +The example uses the same demo channel ID in the manifest and in `Screeb.initSdk`. -Add to your main Activity in `AndroidManifest.xml`: - -```xml - - - - - - -``` +## Files -## Usage +- [settings.gradle](settings.gradle): plugin and repository configuration +- [app/build.gradle](app/build.gradle): app module and Screeb dependency +- [AndroidManifest.xml](app/src/main/AndroidManifest.xml): permissions and deep links +- [MainActivity.kt](app/src/main/kotlin/app/screeb/example/MainActivity.kt): complete SDK usage sample diff --git a/examples/example-android/app/build.gradle b/examples/example-android/app/build.gradle new file mode 100644 index 0000000..4c8423c --- /dev/null +++ b/examples/example-android/app/build.gradle @@ -0,0 +1,42 @@ +plugins { + id 'com.android.application' + id 'org.jetbrains.kotlin.android' +} + +android { + namespace 'app.screeb.example' + compileSdk 35 + + defaultConfig { + applicationId 'app.screeb.example' + minSdk 23 + targetSdk 35 + versionCode 1 + versionName '1.0' + + manifestPlaceholders = [ + screebChannelId: '0e2b609a-8dce-4695-a80f-966fbfa87a88', + ] + } + + buildTypes { + release { + minifyEnabled true + shrinkResources true + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + } + + kotlinOptions { + jvmTarget = '17' + } +} + +dependencies { + implementation 'app.screeb.sdk:survey:4.0.0' +} diff --git a/examples/example-android/app/proguard-rules.pro b/examples/example-android/app/proguard-rules.pro new file mode 100644 index 0000000..7964410 --- /dev/null +++ b/examples/example-android/app/proguard-rules.pro @@ -0,0 +1,4 @@ +# Example app rules. +# +# Screeb's published AAR provides its own consumer ProGuard rules, so the app +# does not need Screeb-specific keep rules here. diff --git a/examples/example-android/app/src/main/AndroidManifest.xml b/examples/example-android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..24b21c6 --- /dev/null +++ b/examples/example-android/app/src/main/AndroidManifest.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/example-android/app/src/main/kotlin/app/screeb/example/MainActivity.kt b/examples/example-android/app/src/main/kotlin/app/screeb/example/MainActivity.kt index d1d8f9d..a05f801 100644 --- a/examples/example-android/app/src/main/kotlin/app/screeb/example/MainActivity.kt +++ b/examples/example-android/app/src/main/kotlin/app/screeb/example/MainActivity.kt @@ -1,38 +1,202 @@ package app.screeb.example +import android.Manifest +import android.app.Activity import android.content.Intent +import android.content.pm.PackageManager +import android.os.Build import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity +import android.view.View +import android.widget.Button +import android.widget.LinearLayout +import android.widget.ScrollView +import android.widget.TextView +import android.widget.Toast +import app.screeb.sdk.InitOptions import app.screeb.sdk.Screeb -import app.screeb.sdk.VisitorProperties -import java.util.Date -class MainActivity : AppCompatActivity() { +class MainActivity : Activity() { + private lateinit var status: TextView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.activity_main) - - Screeb.initSdk( - this, - "", - "", // optional - VisitorProperties().apply { // optional - this["firstname"] = "" - this["lastname"] = "" - this["plan"] = "" - this["age"] = 42 - this["logged_at"] = Date() - this["authenticated"] = true - }, - language = "en" // optional - ) + setContentView(createContentView()) + requestOptionalMediaPermissions() + initializeScreeb() Screeb.handleDeepLink(intent) } override fun onNewIntent(intent: Intent?) { super.onNewIntent(intent) Screeb.handleDeepLink(intent) + setStatus("Deep link handled") + } + + private fun initializeScreeb() { + val visitorProperties = hashMapOf( + "firstname" to "Ada", + "lastname" to "Lovelace", + "plan" to "public-example", + "authenticated" to true, + ) + + Screeb.initSdk( + context = this, + channelId = SCREEB_CHANNEL_ID, + visitorId = "android-example-user", + visitorProperties = visitorProperties, + initOptions = InitOptions(isDebugMode = false, disableMirror = false), + hooks = null, + language = "en", + ) + + setStatus("Screeb initialized with channel $SCREEB_CHANNEL_ID") + } + + private fun createContentView(): View { + val density = resources.displayMetrics.density + val root = ScrollView(this) + val content = LinearLayout(this).apply { + orientation = LinearLayout.VERTICAL + setPadding((20 * density).toInt(), (24 * density).toInt(), (20 * density).toInt(), (32 * density).toInt()) + } + root.addView(content) + + content.addView(title("Screeb Android Example")) + content.addView(body("A complete native Android integration sample using the public Maven artifact.")) + + status = body("Starting...") + content.addView(status) + + content.addView(action("Set identity") { + Screeb.setIdentity( + "android-example-user", + hashMapOf("role" to "tester", "source" to "native-android-example"), + ) + setStatus("Identity sent") + }) + + content.addView(action("Set visitor properties") { + Screeb.setVisitorProperties( + hashMapOf("company" to "Screeb", "example_session" to System.currentTimeMillis()), + ) + setStatus("Visitor properties sent") + }) + + content.addView(action("Track event") { + Screeb.trackEvent( + "android_example_button_clicked", + hashMapOf("button" to "track_event", "screen" to "home"), + ) + setStatus("Event tracked") + }) + + content.addView(action("Track screen") { + Screeb.trackScreen("Android Example", hashMapOf("tab" to "main")) + setStatus("Screen tracked") + }) + + content.addView(action("Start survey") { + Screeb.startSurvey( + surveyId = "replace-with-survey-id", + allowMultipleResponses = true, + hiddenFields = hashMapOf("example" to "android"), + ignoreSurveyStatus = true, + hooks = null, + language = "en", + distributionId = null, + ) + setStatus("Survey start requested") + }) + + content.addView(action("Start message") { + Screeb.startMessage( + messageId = "replace-with-message-id", + allowMultipleResponses = true, + hiddenFields = hashMapOf("example" to "android"), + ignoreMessageStatus = true, + hooks = null, + language = "en", + distributionId = null, + ) + setStatus("Message start requested") + }) + + content.addView(action("Session replay start") { + Screeb.sessionReplayStart() + setStatus("Session replay start requested") + }) + + content.addView(action("Session replay stop") { + Screeb.sessionReplayStop() + setStatus("Session replay stop requested") + }) + + content.addView(action("Debug SDK") { + Screeb.debug { result, error -> + runOnUiThread { + setStatus(error?.message ?: result.ifBlank { "Debug command sent" }) + } + } + }) + + content.addView(action("Close SDK") { + Screeb.closeSdk() + setStatus("SDK closed") + }) + + return root + } + + private fun title(text: String): TextView = + TextView(this).apply { + this.text = text + textSize = 24f + setTextColor(0xFF111827.toInt()) + setPadding(0, 0, 0, 20) + } + + private fun body(text: String): TextView = + TextView(this).apply { + this.text = text + textSize = 15f + setTextColor(0xFF374151.toInt()) + setPadding(0, 0, 0, 18) + } + + private fun action(label: String, onClick: () -> Unit): Button = + Button(this).apply { + text = label + isAllCaps = false + setOnClickListener { + try { + onClick() + } catch (e: Exception) { + setStatus("Error: ${e.message ?: e.javaClass.simpleName}") + } + } + } + + private fun setStatus(message: String) { + status.text = "Status: $message" + Toast.makeText(this, message, Toast.LENGTH_SHORT).show() + } + + private fun requestOptionalMediaPermissions() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return + val permissions = arrayOf( + Manifest.permission.CAMERA, + Manifest.permission.RECORD_AUDIO, + ).filter { checkSelfPermission(it) != PackageManager.PERMISSION_GRANTED } + + if (permissions.isNotEmpty()) { + requestPermissions(permissions.toTypedArray(), REQUEST_MEDIA_PERMISSIONS) + } + } + + private companion object { + const val SCREEB_CHANNEL_ID = "0e2b609a-8dce-4695-a80f-966fbfa87a88" + const val REQUEST_MEDIA_PERMISSIONS = 42 } } diff --git a/examples/example-android/app/src/main/res/values/styles.xml b/examples/example-android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..1b707d5 --- /dev/null +++ b/examples/example-android/app/src/main/res/values/styles.xml @@ -0,0 +1,9 @@ + + + + diff --git a/examples/example-android/build.gradle b/examples/example-android/build.gradle new file mode 100644 index 0000000..a534651 --- /dev/null +++ b/examples/example-android/build.gradle @@ -0,0 +1,4 @@ +plugins { + id 'com.android.application' version '8.7.3' apply false + id 'org.jetbrains.kotlin.android' version '2.1.0' apply false +} diff --git a/examples/example-android/gradle.properties b/examples/example-android/gradle.properties new file mode 100644 index 0000000..8f2e28c --- /dev/null +++ b/examples/example-android/gradle.properties @@ -0,0 +1,4 @@ +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +android.useAndroidX=true +android.nonTransitiveRClass=true +kotlin.code.style=official diff --git a/examples/example-android/gradle/wrapper/gradle-wrapper.jar b/examples/example-android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..f6b961fd5a86aa5fbfe90f707c3138408be7c718 GIT binary patch literal 54329 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

<5G2M1WZP5 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 F{|7XC?P&l2 literal 0 HcmV?d00001 diff --git a/examples/example-android/gradle/wrapper/gradle-wrapper.properties b/examples/example-android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..adf1ca0 --- /dev/null +++ b/examples/example-android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Tue Oct 24 15:14:15 CEST 2023 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/examples/example-android/gradlew b/examples/example-android/gradlew new file mode 100755 index 0000000..cccdd3d --- /dev/null +++ b/examples/example-android/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# 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 + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# 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 +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +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" -a "$nonstop" = "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 + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/examples/example-android/gradlew.bat b/examples/example-android/gradlew.bat new file mode 100644 index 0000000..e95643d --- /dev/null +++ b/examples/example-android/gradlew.bat @@ -0,0 +1,84 @@ +@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 + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@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= + +@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 Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_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=%* + +: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/examples/example-android/settings.gradle b/examples/example-android/settings.gradle new file mode 100644 index 0000000..d57a9b2 --- /dev/null +++ b/examples/example-android/settings.gradle @@ -0,0 +1,29 @@ +pluginManagement { + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + if (providers.gradleProperty("SCREEB_USE_LOCAL_SDK").orElse(providers.environmentVariable("SCREEB_USE_LOCAL_SDK")).orNull == "true") { + mavenLocal() + } + google() + mavenCentral() + } +} + +if (providers.gradleProperty("SCREEB_USE_LOCAL_SDK").orElse(providers.environmentVariable("SCREEB_USE_LOCAL_SDK")).orNull == "true") { + includeBuild("../../../sdk-android") { + dependencySubstitution { + substitute(module("app.screeb.sdk:survey")).using(project(":sdk")) + } + } +} + +rootProject.name = 'screeb-example-android' +include ':app' diff --git a/examples/example-angular/package.json b/examples/example-angular/package.json index ae44394..5b2f61e 100644 --- a/examples/example-angular/package.json +++ b/examples/example-angular/package.json @@ -19,7 +19,7 @@ "scripts": { "build": "tsc && vite build", "clean": "rm -Rf dist", - "lint": "eslint .", + "lint": "eslint \"src/**/*.ts\"", "start": "vite" }, "dependencies": { @@ -37,10 +37,8 @@ "@typescript-eslint/eslint-plugin": "^6.7.5", "eslint": "^8.51.0", "eslint-plugin-import": "^2.28.1", - "eslint-plugin-jest": "^27.4.2", "eslint-plugin-jsx-a11y": "^6.7.1", "eslint-plugin-prettier": "^5.0.1", - "jest": "^29.7.0", "typescript": "^5.2.2", "vite": "^4.4.11" } diff --git a/examples/example-browser/package.json b/examples/example-browser/package.json index 325bc27..4955afe 100644 --- a/examples/example-browser/package.json +++ b/examples/example-browser/package.json @@ -19,7 +19,7 @@ "scripts": { "build": "vite build", "clean": "rm -Rf dist", - "lint": "eslint .", + "lint": "eslint \"src/**/*.{ts,js}\"", "start": "vite" }, "dependencies": { @@ -32,11 +32,9 @@ "@typescript-eslint/eslint-plugin": "^6.7.5", "eslint": "^8.51.0", "eslint-plugin-import": "^2.28.1", - "eslint-plugin-jest": "^27.4.2", "eslint-plugin-jsx-a11y": "^6.7.1", "eslint-plugin-prettier": "^5.0.1", "eslint-plugin-svelte": "^2.34.0", - "jest": "^29.7.0", "svelte": "^4.2.1", "svelte-check": "^3.5.2", "tslib": "^2.6.2", diff --git a/examples/example-expo/README.md b/examples/example-expo/README.md index e06d562..f263aa8 100644 --- a/examples/example-expo/README.md +++ b/examples/example-expo/README.md @@ -1,55 +1,41 @@ -# Welcome to your Expo app 👋 +# `@screeb/react-native` Expo example -This is an [Expo](https://expo.dev) project created with [`create-expo-app`](https://www.npmjs.com/package/create-expo-app). +Minimal Expo app used to validate the Screeb React Native SDK with a generated native project. -> Screeb SDK documentation: [developers.screeb.app/sdk-react-native/install](https://developers.screeb.app/sdk-react-native/install) +> Documentation: [developers.screeb.app/sdk-react-native/install](https://developers.screeb.app/sdk-react-native/install) -## Get started +## Run -1. Install dependencies from the repository root (all workspaces share the same lockfile). +Install dependencies from the repository root: - ```bash - npm install - ``` - -2. Recreate the native project (new architecture is enabled by default and Screeb is autolinked via `react-native.config.js`). - - ```bash - npm run prebuild --workspace=example-expo - npm run ios --workspace=example-expo # or npm run android --workspace=example-expo - ``` - -3. Start the Metro server. - - ```bash - npm run example:expo --workspace=@screeb/react-native - ``` - -The generated build installs a fully native binary; Expo Go cannot load the Screeb TurboModule. Always launch the app produced by `expo run:ios` / `expo run:android` after the `prebuild` step. -If native sources fall out of sync, re-run `npm run prebuild --workspace=example-expo -- --clean` to regenerate the iOS and Android folders from scratch. - -You can start developing by editing the files inside the **app** directory. This project uses [file-based routing](https://docs.expo.dev/router/introduction). - -## Get a fresh project +```bash +npm install +``` -When you're ready, run: +Recreate the native project: ```bash -npm run reset-project --workspace=example-expo +npm run prebuild --workspace=example-expo ``` -This command will move the starter code to the **app-example** directory and create a blank **app** directory where you can start developing. +Run the native app: -## Learn more +```bash +npm run ios --workspace=example-expo +# or +npm run android --workspace=example-expo +``` -To learn more about developing your project with Expo, look at the following resources: +Start Metro from the React Native SDK workspace: -- [Expo documentation](https://docs.expo.dev/): Learn fundamentals, or go into advanced topics with our [guides](https://docs.expo.dev/guides). -- [Learn Expo tutorial](https://docs.expo.dev/tutorial/introduction/): Follow a step-by-step tutorial where you'll create a project that runs on Android, iOS, and the web. +```bash +npm run example:expo --workspace=@screeb/react-native +``` -## Join the community +Expo Go cannot load the Screeb TurboModule. Use the app produced by `expo run:ios` or `expo run:android`. -Join our community of developers creating universal apps. +If native sources fall out of sync, regenerate them with: -- [Expo on GitHub](https://github.com/expo/expo): View our open source platform and contribute. -- [Discord community](https://chat.expo.dev): Chat with Expo users and ask questions. +```bash +npm run prebuild --workspace=example-expo -- --clean +``` diff --git a/examples/example-expo/app.json b/examples/example-expo/app.json index 83ffbac..06686df 100644 --- a/examples/example-expo/app.json +++ b/examples/example-expo/app.json @@ -4,47 +4,23 @@ "slug": "example-expo", "version": "1.0.0", "orientation": "portrait", - "icon": "./assets/images/icon.png", "scheme": "exampleexpo", - "userInterfaceStyle": "automatic", "newArchEnabled": true, "ios": { "supportsTablet": true, "bundleIdentifier": "com.anonymous.example-expo" }, "android": { - "adaptiveIcon": { - "backgroundColor": "#E6F4FE", - "foregroundImage": "./assets/images/android-icon-foreground.png", - "backgroundImage": "./assets/images/android-icon-background.png", - "monochromeImage": "./assets/images/android-icon-monochrome.png" - }, "edgeToEdgeEnabled": true, "predictiveBackGestureEnabled": false, "package": "com.anonymous.exampleexpo" }, - "web": { - "output": "static", - "favicon": "./assets/images/favicon.png" - }, "plugins": [ - "expo-router", - [ - "expo-splash-screen", - { - "image": "./assets/images/splash-icon.png", - "imageWidth": 200, - "resizeMode": "contain", - "backgroundColor": "#ffffff", - "dark": { - "backgroundColor": "#000000" - } - } - ] + "./plugins/withScreebLocalSdk", + "expo-router" ], "experiments": { - "typedRoutes": true, - "reactCompiler": true + "typedRoutes": true } } } diff --git a/examples/example-expo/app/(tabs)/_layout.tsx b/examples/example-expo/app/(tabs)/_layout.tsx deleted file mode 100644 index 54e11d0..0000000 --- a/examples/example-expo/app/(tabs)/_layout.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { Tabs } from 'expo-router'; -import React from 'react'; - -import { HapticTab } from '@/components/haptic-tab'; -import { IconSymbol } from '@/components/ui/icon-symbol'; -import { Colors } from '@/constants/theme'; -import { useColorScheme } from '@/hooks/use-color-scheme'; - -export default function TabLayout() { - const colorScheme = useColorScheme(); - - return ( - - , - }} - /> - , - }} - /> - - ); -} diff --git a/examples/example-expo/app/(tabs)/explore.tsx b/examples/example-expo/app/(tabs)/explore.tsx deleted file mode 100644 index 71518f9..0000000 --- a/examples/example-expo/app/(tabs)/explore.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import { Image } from 'expo-image'; -import { Platform, StyleSheet } from 'react-native'; - -import { Collapsible } from '@/components/ui/collapsible'; -import { ExternalLink } from '@/components/external-link'; -import ParallaxScrollView from '@/components/parallax-scroll-view'; -import { ThemedText } from '@/components/themed-text'; -import { ThemedView } from '@/components/themed-view'; -import { IconSymbol } from '@/components/ui/icon-symbol'; -import { Fonts } from '@/constants/theme'; - -export default function TabTwoScreen() { - return ( - - }> - - - Explore - - - This app includes example code to help you get started. - - - This app has two screens:{' '} - app/(tabs)/index.tsx and{' '} - app/(tabs)/explore.tsx - - - The layout file in app/(tabs)/_layout.tsx{' '} - sets up the tab navigator. - - - Learn more - - - - - You can open this project on Android, iOS, and the web. To open the web version, press{' '} - w in the terminal running this project. - - - - - For static images, you can use the @2x and{' '} - @3x suffixes to provide files for - different screen densities - - - - Learn more - - - - - This template has light and dark mode support. The{' '} - useColorScheme() hook lets you inspect - what the user's current color scheme is, and so you can adjust UI colors accordingly. - - - Learn more - - - - - This template includes an example of an animated component. The{' '} - components/HelloWave.tsx component uses - the powerful{' '} - - react-native-reanimated - {' '} - library to create a waving hand animation. - - {Platform.select({ - ios: ( - - The components/ParallaxScrollView.tsx{' '} - component provides a parallax effect for the header image. - - ), - })} - - - ); -} - -const styles = StyleSheet.create({ - headerImage: { - color: '#808080', - bottom: -90, - left: -35, - position: 'absolute', - }, - titleContainer: { - flexDirection: 'row', - gap: 8, - }, -}); diff --git a/examples/example-expo/app/(tabs)/index.tsx b/examples/example-expo/app/(tabs)/index.tsx deleted file mode 100644 index 461243c..0000000 --- a/examples/example-expo/app/(tabs)/index.tsx +++ /dev/null @@ -1,168 +0,0 @@ -import * as Screeb from "@screeb/react-native"; -import { useEffect } from "react"; -import { Alert, Button, StyleSheet, View } from "react-native"; - -import ParallaxScrollView from "@/components/parallax-scroll-view"; -import { ThemedText } from "@/components/themed-text"; -import { ThemedView } from "@/components/themed-view"; - -const PROJECT_TOKEN = "0e2b609a-8dce-4695-a80f-966fbfa87a88"; -const RESPONDENT_ID = "0021de43-6e44-443c-9903-2ab99f9c4233"; -const SURVEY_ID = "8dd42ae1-f716-429c-9843-fad62adf2ac4"; - -// Shared helper to run the Screeb setup with demo identifiers that work in preview environments. -const initScreeb = async () => { - try { - await Screeb.initSdk( - PROJECT_TOKEN, - RESPONDENT_ID, - { - premium: true, - locale: "fr-FR", - }, - { - version: "1.0.0", - onReady: (payload: unknown) => { - console.log("Screeb ready", payload); - }, - onSurveyDisplayAllowed: () => { - console.log("Survey display allowed"); - return true; - }, - }, - {}, - ); - } catch (error) { - console.error("Failed to init Screeb", error); - Alert.alert("Screeb init failed", "Check the Metro logs for details."); - } -}; - -export default function HomeScreen() { - useEffect(() => { - initScreeb(); - }, []); - - return ( - } - > - - @screeb/react-native + Expo - - This screen boots the Screeb SDK when the component mounts and exposes - the main helper methods below so you can try them quickly from an Expo - development build. - - - - Try the SDK - -