diff --git a/CHANGELOG.md b/CHANGELOG.md index 55cd87389..5af4b937c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,25 @@ # Changelog +## [Unreleased](https://github.com/Instabug/Instabug-React-Native/compare/v15.0.2...dev) + +### Added + +- Add support for App variant. ([#1409](https://github.com/Instabug/Instabug-React-Native/pull/1409)) + +- Add Support Advanced UI customization. ([#1411](https://github.com/Instabug/Instabug-React-Native/pull/1411)) + +### Changed + +- **BREAKING** Remove deprecated APIs ([#1424](https://github.com/Instabug/Instabug-React-Native/pull/1424)). See migration guide for more details. + +- Bump Instabug iOS SDK to v16.0.1 ([#1436](https://github.com/Instabug/Instabug-React-Native/pull/1436)). [See release notes](https://github.com/Instabug/Instabug-iOS/releases/tag/16.0.1). + +- Bump Instabug Android SDK to v16.0.0 ([#1436](https://github.com/Instabug/Instabug-React-Native/pull/1436)). [See release notes](https://github.com/Instabug/Instabug-Android/releases/tag/v16.0.1). + +### Fixed + +- Masking private views on newer React native Versions ([#1403](https://github.com/Instabug/Instabug-React-Native/pull/1403)) + ## [15.0.2](https://github.com/Instabug/Instabug-React-Native/compare/v15.2.0...dev) ### Added diff --git a/FONTS_SETUP_GUIDE.md b/FONTS_SETUP_GUIDE.md new file mode 100644 index 000000000..5c0c6c903 --- /dev/null +++ b/FONTS_SETUP_GUIDE.md @@ -0,0 +1,522 @@ +# Complete Font Setup Guide for Instabug React Native + +This guide covers all ways to use fonts with the `setTheme` function in Instabug React Native, including Google Fonts, custom fonts, system fonts, and both Expo and regular React Native projects. + +## Table of Contents + +1. [Overview](#overview) +2. [Font Types Supported](#font-types-supported) +3. [Regular React Native Setup](#regular-react-native-setup) +4. [Expo Setup](#expo-setup) +5. [Asset Linking Options](#asset-linking-options) +6. [Usage Examples](#usage-examples) +7. [Troubleshooting](#troubleshooting) +8. [Platform Compatibility Notes](#platform-compatibility-notes) + +## Overview + +The Instabug React Native bridge supports font loading from multiple sources: + +- **App Bundle**: Fonts included in your app assets +- **System Fonts**: Built-in platform fonts +- **Custom Fonts**: Any TTF/OTF font files +- **Google Fonts**: Downloaded TTF files from Google Fonts + +## Font Types Supported + +### 1. Google Fonts + +- Download TTF files from [Google Fonts](https://fonts.google.com/) +- Examples: Roboto, Inter, Nunito, Open Sans, Lato + +### 2. Custom Fonts + +- Any TTF/OTF font files +- Commercial fonts, free fonts, custom designs + +### 3. System Fonts + +- Platform default fonts +- No setup required +- Examples: San Francisco (iOS), Roboto (Android) + +## Regular React Native Setup + +### Method 1: Bundle Fonts (Recommended) + +#### Step 1: Download Font Files + +```bash +# Create fonts directory +mkdir fonts + +# Download your desired fonts (example with Google Fonts) +# Download from Google Fonts or any font provider +# Place TTF files in the fonts directory +``` + +#### Step 2: Add to Android + +```bash +# Create assets/fonts directory +mkdir -p android/app/src/main/assets/fonts + +# Copy font files +cp fonts/*.ttf android/app/src/main/assets/fonts/ +``` + +#### Step 3: Add to iOS + +1. **Add to Xcode Project:** + + - Open your iOS project in Xcode + - Right-click on your project → "Add Files to [ProjectName]" + - Select your TTF files + - Make sure "Add to target" is checked + +2. **Update Info.plist:** + +```xml +UIAppFonts + + Roboto-Regular.ttf + Roboto-Bold.ttf + Inter-Regular.ttf + Inter-Bold.ttf + +``` + +#### Step 4: Use with setTheme + +```typescript +import Instabug from 'instabug-reactnative'; +import { Platform } from 'react-native'; + +const applyCustomTheme = () => { + Instabug.setTheme({ + // Colors + primaryColor: '#2196F3', + backgroundColor: '#FFFFFF', + primaryTextColor: '#333333', + + // Text styles (Android only) + primaryTextStyle: 'normal', + secondaryTextStyle: 'normal', + ctaTextStyle: 'bold', + + // Fonts - Android (use font paths) + ...(Platform.OS === 'android' && { + primaryFontPath: '/data/user/0/com.yourapp/files/fonts/Roboto-Regular.ttf', + secondaryFontPath: '/data/user/0/com.yourapp/files/fonts/Roboto-Light.ttf', + ctaFontPath: '/data/user/0/com.yourapp/files/fonts/Roboto-Bold.ttf', + }), + + // Fonts - iOS (use font paths, not assets) + ...(Platform.OS === 'ios' && { + primaryFontPath: 'fonts/Roboto-Regular.ttf', + secondaryFontPath: 'fonts/Roboto-Light.ttf', + ctaFontPath: 'fonts/Roboto-Bold.ttf', + }), + }); +}; +``` + +### Method 2: System Fonts Only + +```typescript +import Instabug from 'instabug-reactnative'; + +const applySystemTheme = () => { + Instabug.setTheme({ + // Colors only - uses system fonts + primaryColor: '#2196F3', + backgroundColor: '#FFFFFF', + primaryTextColor: '#333333', + secondaryTextColor: '#666666', + titleTextColor: '#000000', + + // Text styles (Android only) + primaryTextStyle: 'normal', + secondaryTextStyle: 'normal', + ctaTextStyle: 'bold', + + // No font paths = uses system fonts + }); +}; +``` + +## Expo Setup + +### Method 1: Expo Fonts (Recommended for Expo) + +#### Step 1: Install Expo Fonts + +```bash +npx expo install expo-font +``` + +#### Step 2: Download and Add Fonts + +```bash +# Create fonts directory +mkdir fonts + +# Download your fonts and place them in the fonts directory +# Example: Roboto-Regular.ttf, Roboto-Bold.ttf, Inter-Regular.ttf +``` + +#### Step 3: Configure app.json + +```json +{ + "expo": { + "fonts": [ + { + "asset": "./fonts/Roboto-Regular.ttf", + "family": "Roboto-Regular" + }, + { + "asset": "./fonts/Roboto-Bold.ttf", + "family": "Roboto-Bold" + }, + { + "asset": "./fonts/Inter-Regular.ttf", + "family": "Inter-Regular" + } + ] + } +} +``` + +#### Step 4: Load Fonts in Your App + +```typescript +import * as Font from 'expo-font'; +import { useEffect, useState } from 'react'; + +export default function App() { + const [fontsLoaded, setFontsLoaded] = useState(false); + + useEffect(() => { + async function loadFonts() { + await Font.loadAsync({ + 'Roboto-Regular': require('./fonts/Roboto-Regular.ttf'), + 'Roboto-Bold': require('./fonts/Roboto-Bold.ttf'), + 'Inter-Regular': require('./fonts/Inter-Regular.ttf'), + }); + setFontsLoaded(true); + } + loadFonts(); + }, []); + + if (!fontsLoaded) { + return ; + } + + return ; +} +``` + +#### Step 5: Use with setTheme + +```typescript +import Instabug from 'instabug-reactnative'; +import { Platform } from 'react-native'; + +const applyExpoTheme = () => { + Instabug.setTheme({ + // Colors + primaryColor: '#2196F3', + backgroundColor: '#FFFFFF', + primaryTextColor: '#333333', + + // Text styles (Android only) + primaryTextStyle: 'normal', + secondaryTextStyle: 'normal', + ctaTextStyle: 'bold', + + // Fonts - use font paths for both platforms + primaryFontPath: 'fonts/Roboto-Regular.ttf', + secondaryFontPath: 'fonts/Inter-Regular.ttf', + ctaFontPath: 'fonts/Roboto-Bold.ttf', + }); +}; +``` + +### Method 2: Expo with Bundle Fonts + +Same as Regular React Native Method 1, but fonts are automatically included in the Expo build. + +## Asset Linking Options + +### Option 1: Manual Copy (Current Method) + +- Copy TTF files to native directories +- Update Info.plist manually +- Works with all setups + +### Option 2: React Native CLI Linking + +```bash +# Create a react-native.config.js file +module.exports = { + assets: ['./fonts/'], +}; +``` + +Then run: + +```bash +npx react-native link +``` + +### Option 3: Expo Asset Linking + +```json +{ + "expo": { + "fonts": [ + { + "asset": "./fonts/Roboto-Regular.ttf", + "family": "Roboto-Regular" + } + ] + } +} +``` + +### Option 4: Metro Asset Plugin + +```bash +npm install --save-dev react-native-asset +``` + +Create `react-native.config.js`: + +```javascript +module.exports = { + assets: ['./fonts/'], +}; +``` + +## Usage Examples + +### Example 1: Google Fonts (Roboto) + +```typescript +// Download: Roboto-Regular.ttf, Roboto-Bold.ttf, Roboto-Light.ttf +// Add to project using any method above + +const applyRobotoTheme = () => { + Instabug.setTheme({ + primaryColor: '#1976D2', + backgroundColor: '#FAFAFA', + primaryTextColor: '#212121', + secondaryTextColor: '#757575', + titleTextColor: '#000000', + + // Text styles (Android only) + primaryTextStyle: 'normal', + secondaryTextStyle: 'normal', + ctaTextStyle: 'bold', + + // Font paths for both platforms + primaryFontPath: 'fonts/Roboto-Regular.ttf', + secondaryFontPath: 'fonts/Roboto-Light.ttf', + ctaFontPath: 'fonts/Roboto-Bold.ttf', + }); +}; +``` + +### Example 2: Custom Fonts (Inter) + +```typescript +// Download: Inter-Regular.ttf, Inter-Bold.ttf, Inter-Medium.ttf +// Add to project using any method above + +const applyInterTheme = () => { + Instabug.setTheme({ + primaryColor: '#3B82F6', + backgroundColor: '#FFFFFF', + primaryTextColor: '#1F2937', + secondaryTextColor: '#6B7280', + titleTextColor: '#111827', + + // Text styles (Android only) + primaryTextStyle: 'normal', + secondaryTextStyle: 'normal', + ctaTextStyle: 'bold', + + // Font paths for both platforms + primaryFontPath: 'fonts/Inter-Regular.ttf', + secondaryFontPath: 'fonts/Inter-Medium.ttf', + ctaFontPath: 'fonts/Inter-Bold.ttf', + }); +}; +``` + +### Example 3: System Fonts Only + +```typescript +const applySystemTheme = () => { + Instabug.setTheme({ + primaryColor: '#007AFF', + backgroundColor: '#F2F2F7', + primaryTextColor: '#000000', + secondaryTextColor: '#8E8E93', + titleTextColor: '#000000', + + // Text styles (Android only) - no font paths = uses system fonts + primaryTextStyle: 'normal', + secondaryTextStyle: 'normal', + ctaTextStyle: 'bold', + }); +}; +``` + +### Example 4: Mixed Approach + +```typescript +const applyMixedTheme = () => { + Instabug.setTheme({ + primaryColor: '#FF6B6B', + backgroundColor: '#FFFFFF', + primaryTextColor: '#2D3436', + secondaryTextColor: '#636E72', + titleTextColor: '#2D3436', + + // Text styles (Android only) + primaryTextStyle: 'normal', + secondaryTextStyle: 'normal', + ctaTextStyle: 'bold', + + // Custom font only for CTA - rest use system fonts + ctaFontPath: 'fonts/Roboto-Bold.ttf', + }); +}; +``` + +## Platform Compatibility Notes + +### **Important: iOS Font Asset Limitation** + +The iOS implementation currently **only supports** `primaryFontPath`, `secondaryFontPath`, and `ctaFontPath` properties. The `*FontAsset` properties are **not supported** on iOS. + +**Android**: Supports both `*FontPath` and `*FontAsset` properties +**iOS**: Only supports `*FontPath` properties + +### **Recommended Approach** + +Use `*FontPath` properties for both platforms to ensure compatibility: + +```typescript +// ✅ Works on both platforms +Instabug.setTheme({ + primaryFontPath: 'fonts/Roboto-Regular.ttf', + secondaryFontPath: 'fonts/Roboto-Light.ttf', + ctaFontPath: 'fonts/Roboto-Bold.ttf', +}); + +// ❌ iOS doesn't support these +Instabug.setTheme({ + primaryFontAsset: 'fonts/Roboto-Regular.ttf', // iOS ignores this + secondaryFontAsset: 'fonts/Roboto-Light.ttf', // iOS ignores this + ctaFontAsset: 'fonts/Roboto-Bold.ttf', // iOS ignores this +}); +``` + +### **Font Path Format** + +- **Android**: Can use full paths or just filenames +- **iOS**: Use relative paths like `fonts/Roboto-Regular.ttf` + +## Troubleshooting + +### Common Issues + +#### 1. Font Not Loading + +**Symptoms:** Font appears as system font or doesn't change +**Solutions:** + +- Check font filename matches exactly (case-sensitive) +- Verify font is added to both Android and iOS +- For iOS, check Info.plist entries +- For Expo, ensure fonts are loaded before using setTheme +- **iOS users**: Make sure you're using `*FontPath` properties, not `*FontAsset` + +#### 2. Font Loading in Expo + +**Symptoms:** Font works in development but not in production +**Solutions:** + +- Use `expo-font` to load fonts before app starts +- Ensure fonts are included in app.json +- Test with `expo build` or EAS Build + +#### 3. Font File Issues + +**Symptoms:** App crashes or font doesn't load +**Solutions:** + +- Verify TTF file is not corrupted +- Check file size (should be reasonable, not 0 bytes) +- Ensure font file is valid TTF/OTF format + +#### 4. Performance Issues + +**Symptoms:** App slow to start or font loading delays +**Solutions:** + +- Use system fonts for better performance +- Limit number of custom fonts +- Preload fonts in app initialization + +### Debug Steps + +1. **Check Font Loading:** + +```typescript +// Add this to debug font loading +console.log('Available fonts:', Instabug.getAvailableFonts()); // If available +``` + +2. **Verify File Paths:** + +```bash +# Check if fonts are in the right place +ls -la android/app/src/main/assets/fonts/ +ls -la ios/YourApp/ +``` + +3. **Test with System Fonts First:** + +```typescript +// Test with system fonts to ensure setTheme works +Instabug.setTheme({ + primaryColor: '#FF0000', + // No fontFamily = system fonts +}); +``` + +## Best Practices + +1. **Use System Fonts When Possible:** Better performance and consistency +2. **Limit Custom Fonts:** Use 1-2 custom fonts maximum +3. **Preload Fonts:** Load fonts before app starts +4. **Test on Both Platforms:** Fonts may behave differently on iOS vs Android +5. **Use Standard Font Weights:** Regular, Bold, Light are most reliable +6. **Keep Font Files Small:** Optimize TTF files for mobile +7. **Use \*FontPath Properties:** Ensures compatibility with both platforms + +## Summary + +- **Regular React Native:** Use bundle fonts or system fonts +- **Expo:** Use expo-font or bundle fonts +- **Asset Linking:** Available through CLI tools and Expo config +- **Google Fonts:** Download TTF files and add to project +- **Custom Fonts:** Any TTF/OTF file works the same way +- **System Fonts:** No setup required, best performance +- **Platform Compatibility:** Use `*FontPath` properties for both platforms + +The native bridge handles all font loading automatically once fonts are properly added to your project! diff --git a/android/native.gradle b/android/native.gradle index faa3246cd..f4b2e9818 100644 --- a/android/native.gradle +++ b/android/native.gradle @@ -1,5 +1,5 @@ project.ext.instabug = [ - version: '15.0.1' + version: '16.0.0' ] dependencies { diff --git a/android/sourcemaps.gradle b/android/sourcemaps.gradle index f544974d2..5558c01ba 100644 --- a/android/sourcemaps.gradle +++ b/android/sourcemaps.gradle @@ -1,6 +1,6 @@ import org.apache.tools.ant.taskdefs.condition.Os -gradle.projectsEvaluated { +project.afterEvaluate { // Works for both `bundleReleaseJsAndAssets` and `createBundleReleaseJsAndAssets` and product flavors def suffix = 'ReleaseJsAndAssets' def bundleTasks = project(':app').tasks.findAll { diff --git a/android/src/main/java/com/instabug/reactlibrary/RNInstabug.java b/android/src/main/java/com/instabug/reactlibrary/RNInstabug.java index 7c0901936..28de7d950 100644 --- a/android/src/main/java/com/instabug/reactlibrary/RNInstabug.java +++ b/android/src/main/java/com/instabug/reactlibrary/RNInstabug.java @@ -59,18 +59,30 @@ public void init( @NonNull Application application, @NonNull String applicationToken, int logLevel, + String codePushVersion, + String appVariant, Boolean ignoreSecureFlag, @NonNull InstabugInvocationEvent... InvocationEvent + + ) { try { setBaseUrlForDeprecationLogs(); setCurrentPlatform(); - Instabug.Builder builder = new Instabug.Builder(application, applicationToken) + Instabug.Builder builder= new Instabug.Builder(application, applicationToken) .setInvocationEvents(InvocationEvent) .setSdkDebugLogsLevel(logLevel); + if(codePushVersion!=null){ + builder.setCodePushVersion(codePushVersion); + } + if(appVariant!=null) + builder.setAppVariant(appVariant); + + + if (ignoreSecureFlag != null) { builder.ignoreFlagSecure(ignoreSecureFlag); } @@ -107,9 +119,11 @@ public void init( public void init( @NonNull Application application, @NonNull String applicationToken, + String codePushVersion, + String appVariant, @NonNull InstabugInvocationEvent... invocationEvent ) { - init(application, applicationToken, LogLevel.ERROR,null, invocationEvent); + init(application, applicationToken, LogLevel.ERROR,codePushVersion,appVariant, null,invocationEvent); } @VisibleForTesting @@ -165,6 +179,11 @@ public static class Builder { * The events that trigger the SDK's user interface. */ private InstabugInvocationEvent[] invocationEvents; + /** + * The App variant name to be used for all reports. + */ + private String appVariant; + private Boolean ignoreFlagSecure; @@ -237,6 +256,16 @@ public Builder setInvocationEvents(InstabugInvocationEvent... invocationEvents) return this; } + /** + * Sets the the current App variant + * + * @param appVariant the current App variant to work with. + */ + public Builder setAppVariant(String appVariant) { + this.appVariant = appVariant; + return this; + } + /** * Builds the Instabug instance with the provided configurations. */ @@ -252,6 +281,9 @@ public void build() { if (codePushVersion != null) { instabugBuilder.setCodePushVersion(codePushVersion); } + if(appVariant!=null){ + instabugBuilder.setAppVariant(appVariant); + } if (ignoreFlagSecure != null) { instabugBuilder.ignoreFlagSecure(ignoreFlagSecure); diff --git a/android/src/main/java/com/instabug/reactlibrary/RNInstabugAPMModule.java b/android/src/main/java/com/instabug/reactlibrary/RNInstabugAPMModule.java index d75b4f75b..6ed3541cb 100644 --- a/android/src/main/java/com/instabug/reactlibrary/RNInstabugAPMModule.java +++ b/android/src/main/java/com/instabug/reactlibrary/RNInstabugAPMModule.java @@ -12,7 +12,6 @@ import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.ReadableMap; import com.instabug.apm.APM; -import com.instabug.apm.model.ExecutionTrace; import com.instabug.apm.networking.APMNetworkLogger; import com.instabug.apm.networkinterception.cp.APMCPNetworkLog; import com.instabug.reactlibrary.utils.EventEmitterModule; @@ -33,8 +32,6 @@ public RNInstabugAPMModule(ReactApplicationContext reactApplicationContext) { super(reactApplicationContext); } - @Deprecated - HashMap traces = new HashMap(); @Nonnull @Override @@ -207,81 +204,6 @@ public void run() { }); } - /** - * Starts an execution trace - * - * @param name string name of the trace. - * - * @deprecated see {@link #startFlow(String)} - */ - @Deprecated - @ReactMethod - public void startExecutionTrace(final String name, final String id, final Promise promise) { - MainThreadHandler.runOnMainThread(new Runnable() { - @Override - public void run() { - try { - String result = ""; - ExecutionTrace trace = APM.startExecutionTrace(name); - if (trace != null) { - result = id; - traces.put(id, trace); - } - promise.resolve(result); - } catch (Exception e) { - e.printStackTrace(); - promise.resolve(null); - } - } - }); - } - - /** - * Adds a new attribute to trace - * - * @param id String id of the trace. - * @param key attribute key - * @param value attribute value. Null to remove attribute - * - * @deprecated see {@link #setFlowAttribute} - */ - @Deprecated - @ReactMethod - public void setExecutionTraceAttribute(final String id, final String key, final String value) { - MainThreadHandler.runOnMainThread(new Runnable() { - @Override - public void run() { - try { - traces.get(id).setAttribute(key, value); - } catch (Exception e) { - e.printStackTrace(); - } - } - }); - } - - /** - * Ends a trace - * - * @param id string id of the trace. - * - * @deprecated see {@link #endFlow} - */ - @Deprecated - @ReactMethod - public void endExecutionTrace(final String id) { - MainThreadHandler.runOnMainThread(new Runnable() { - @Override - public void run() { - try { - traces.get(id).end(); - } catch (Exception e) { - e.printStackTrace(); - } - } - }); - } - /** * Starts a UI trace * diff --git a/android/src/main/java/com/instabug/reactlibrary/RNInstabugBugReportingModule.java b/android/src/main/java/com/instabug/reactlibrary/RNInstabugBugReportingModule.java index 0dd9270e0..420011ddb 100644 --- a/android/src/main/java/com/instabug/reactlibrary/RNInstabugBugReportingModule.java +++ b/android/src/main/java/com/instabug/reactlibrary/RNInstabugBugReportingModule.java @@ -389,7 +389,6 @@ public void run() { } }); } - /** * Sets a minimum number of characters as a requirement for the comments field in the different report types. * @param limit int number of characters. @@ -410,8 +409,7 @@ public void run() { typesInts[i] = types.get(i); } - BugReporting.setCommentMinimumCharacterCount(limit, typesInts); - } catch (Exception e) { + BugReporting.setCommentMinimumCharacterCountForBugReportType(limit, typesInts); } catch (Exception e) { e.printStackTrace(); } } diff --git a/android/src/main/java/com/instabug/reactlibrary/RNInstabugReactnativeModule.java b/android/src/main/java/com/instabug/reactlibrary/RNInstabugReactnativeModule.java index 21bcf4f44..0dffd60c2 100644 --- a/android/src/main/java/com/instabug/reactlibrary/RNInstabugReactnativeModule.java +++ b/android/src/main/java/com/instabug/reactlibrary/RNInstabugReactnativeModule.java @@ -6,10 +6,14 @@ import android.app.Application; import android.graphics.Bitmap; +import android.graphics.Color; +import android.graphics.Typeface; import android.net.Uri; import android.util.Log; import android.view.View; +import com.facebook.react.bridge.ReactApplicationContext; + import androidx.annotation.NonNull; import androidx.annotation.UiThread; @@ -20,10 +24,12 @@ import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.UIManager; import com.facebook.react.bridge.WritableArray; import com.facebook.react.bridge.WritableMap; import com.facebook.react.bridge.WritableNativeArray; import com.facebook.react.bridge.WritableNativeMap; +import com.facebook.react.uimanager.UIManagerHelper; import com.facebook.react.uimanager.UIManagerModule; import com.instabug.apm.InternalAPM; import com.instabug.apm.configuration.cp.APMFeature; @@ -45,6 +51,7 @@ import com.instabug.library.internal.module.InstabugLocale; import com.instabug.library.invocation.InstabugInvocationEvent; import com.instabug.library.logging.InstabugLog; +import com.instabug.library.model.IBGTheme; import com.instabug.library.model.NetworkLog; import com.instabug.library.model.Report; import com.instabug.library.ui.onboarding.WelcomeMessage; @@ -149,6 +156,7 @@ public void init( final String logLevel, final boolean useNativeNetworkInterception, @Nullable final String codePushVersion, + @Nullable final String appVariant, final ReadableMap map ) { MainThreadHandler.runOnMainThread(new Runnable() { @@ -178,6 +186,9 @@ public void run() { builder.setCodePushVersion(codePushVersion); } } + if (appVariant != null) { + builder.setAppVariant(appVariant); + } builder.build(); } }); @@ -286,26 +297,6 @@ public void run() { }); } - /** - * Set the primary color that the SDK will use to tint certain UI elements in the SDK - * - * @param primaryColor The value of the primary color , - * whatever this color was parsed from a resource color or hex color - * or RGB color values - */ - @ReactMethod - public void setPrimaryColor(final int primaryColor) { - MainThreadHandler.runOnMainThread(new Runnable() { - @Override - public void run() { - try { - Instabug.setPrimaryColor(primaryColor); - } catch (Exception e) { - e.printStackTrace(); - } - } - }); - } /** * Gets tags. @@ -505,6 +496,8 @@ public void run() { }); } + + /** * Removes user attribute if exists. * @@ -954,14 +947,22 @@ public void networkLogAndroid(final String url, @UiThread @Nullable private View resolveReactView(final int reactTag) { + try { final ReactApplicationContext reactContext = getReactApplicationContext(); final UIManagerModule uiManagerModule = reactContext.getNativeModule(UIManagerModule.class); if (uiManagerModule == null) { + UIManager uiNewManagerModule = UIManagerHelper.getUIManagerForReactTag(reactContext, reactTag); + if (uiNewManagerModule != null) { + return uiNewManagerModule.resolveView(reactTag); + } return null; } return uiManagerModule.resolveView(reactTag); + } catch (Exception e) { + return null; + } } @@ -973,7 +974,9 @@ public void run() { try { final View view = resolveReactView(reactTag); + if(view !=null){ Instabug.addPrivateViews(view); + } } catch (Exception e) { e.printStackTrace(); } @@ -988,8 +991,10 @@ public void removePrivateView(final int reactTag) { public void run() { try { final View view = resolveReactView(reactTag); + if(view !=null){ Instabug.removePrivateViews(view); + } } catch (Exception e) { e.printStackTrace(); } @@ -1041,60 +1046,7 @@ public void run() { }); } - /** - * @deprecated see {@link #addFeatureFlags(ReadableArray)} - */ - @ReactMethod - public void addExperiments(final ReadableArray experiments) { - MainThreadHandler.runOnMainThread(new Runnable() { - @Override - public void run() { - try { - Object[] objectArray = ArrayUtil.toArray(experiments); - String[] stringArray = Arrays.copyOf(objectArray, objectArray.length, String[].class); - Instabug.addExperiments(Arrays.asList(stringArray)); - } catch (Exception e) { - e.printStackTrace(); - } - } - }); - } - - /** - * @deprecated see {@link #removeFeatureFlags(ReadableArray)} - */ - @ReactMethod - public void removeExperiments(final ReadableArray experiments) { - MainThreadHandler.runOnMainThread(new Runnable() { - @Override - public void run() { - try { - Object[] objectArray = ArrayUtil.toArray(experiments); - String[] stringArray = Arrays.copyOf(objectArray, objectArray.length, String[].class); - Instabug.removeExperiments(Arrays.asList(stringArray)); - } catch (Exception e) { - e.printStackTrace(); - } - } - }); - } - /** - * @deprecated see {@link #removeAllFeatureFlags()} - */ - @ReactMethod - public void clearAllExperiments() { - MainThreadHandler.runOnMainThread(new Runnable() { - @Override - public void run() { - try { - Instabug.clearAllExperiments(); - } catch (Exception e) { - e.printStackTrace(); - } - } - }); - } @ReactMethod public void addFeatureFlags(final ReadableMap featureFlagsMap) { @@ -1356,4 +1308,270 @@ public void run() { } }); } + + /** + * Sets current App variant + * + * @param appVariant The app variant name . + */ + @ReactMethod + public void setAppVariant(@NonNull String appVariant) { + try { + Instabug.setAppVariant(appVariant); + + } catch (Exception e) { + e.printStackTrace(); + } + } + /** + * Sets the theme for Instabug using a configuration object. + * + * @param themeConfig A ReadableMap containing theme properties such as colors, fonts, and text styles. + */ + @ReactMethod + public void setTheme(final ReadableMap themeConfig) { + MainThreadHandler.runOnMainThread(new Runnable() { + @Override + public void run() { + try { + com.instabug.library.model.IBGTheme.Builder builder = new com.instabug.library.model.IBGTheme.Builder(); + + // Apply colors + applyColorIfPresent(themeConfig, builder, "primaryColor", (themeBuilder, color) -> themeBuilder.setPrimaryColor(color)); + applyColorIfPresent(themeConfig, builder, "secondaryTextColor", (themeBuilder, color) -> themeBuilder.setSecondaryTextColor(color)); + applyColorIfPresent(themeConfig, builder, "primaryTextColor", (themeBuilder, color) -> themeBuilder.setPrimaryTextColor(color)); + applyColorIfPresent(themeConfig, builder, "titleTextColor", (themeBuilder, color) -> themeBuilder.setTitleTextColor(color)); + applyColorIfPresent(themeConfig, builder, "backgroundColor", (themeBuilder, color) -> themeBuilder.setBackgroundColor(color)); + + // Apply text styles + applyTextStyleIfPresent(themeConfig, builder, "primaryTextStyle", (themeBuilder, style) -> themeBuilder.setPrimaryTextStyle(style)); + applyTextStyleIfPresent(themeConfig, builder, "secondaryTextStyle", (themeBuilder, style) -> themeBuilder.setSecondaryTextStyle(style)); + applyTextStyleIfPresent(themeConfig, builder, "ctaTextStyle", (themeBuilder, style) -> themeBuilder.setCtaTextStyle(style)); + setFontIfPresent(themeConfig, builder, "primaryFontPath", "primaryFontAsset", "primary"); + setFontIfPresent(themeConfig, builder, "secondaryFontPath", "secondaryFontAsset", "secondary"); + setFontIfPresent(themeConfig, builder, "ctaFontPath", "ctaFontAsset", "CTA"); + + IBGTheme theme = builder.build(); + Instabug.setTheme(theme); + + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + + /** + * Retrieves a color value from the ReadableMap. + * + * @param map The ReadableMap object. + * @param key The key to look for. + * @return The parsed color as an integer, or black if missing or invalid. + */ + private int getColor(ReadableMap map, String key) { + try { + if (map != null && map.hasKey(key) && !map.isNull(key)) { + String colorString = map.getString(key); + return Color.parseColor(colorString); + } + } catch (Exception e) { + e.printStackTrace(); + } + return Color.BLACK; + } + + /** + * Retrieves a text style from the ReadableMap. + * + * @param map The ReadableMap object. + * @param key The key to look for. + * @return The corresponding Typeface style, or Typeface.NORMAL if missing or invalid. + */ + private int getTextStyle(ReadableMap map, String key) { + try { + if (map != null && map.hasKey(key) && !map.isNull(key)) { + String style = map.getString(key); + switch (style.toLowerCase()) { + case "bold": + return Typeface.BOLD; + case "italic": + return Typeface.ITALIC; + case "bold_italic": + return Typeface.BOLD_ITALIC; + case "normal": + default: + return Typeface.NORMAL; + } + } + } catch (Exception e) { + e.printStackTrace(); + } + return Typeface.NORMAL; + } + + + + /** + * Applies a color to the theme builder if present in the configuration. + * + * @param themeConfig The theme configuration map + * @param builder The theme builder + * @param key The configuration key + * @param setter The color setter function + */ + private void applyColorIfPresent(ReadableMap themeConfig, com.instabug.library.model.IBGTheme.Builder builder, + String key, java.util.function.BiConsumer setter) { + if (themeConfig.hasKey(key)) { + int color = getColor(themeConfig, key); + setter.accept(builder, color); + } + } + + /** + * Applies a text style to the theme builder if present in the configuration. + * + * @param themeConfig The theme configuration map + * @param builder The theme builder + * @param key The configuration key + * @param setter The text style setter function + */ + private void applyTextStyleIfPresent(ReadableMap themeConfig, com.instabug.library.model.IBGTheme.Builder builder, + String key, java.util.function.BiConsumer setter) { + if (themeConfig.hasKey(key)) { + int style = getTextStyle(themeConfig, key); + setter.accept(builder, style); + } + } + + /** + * Sets a font on the theme builder if the font configuration is present in the theme config. + * + * @param themeConfig The theme configuration map + * @param builder The theme builder + * @param fileKey The key for font file path + * @param assetKey The key for font asset path + * @param fontType The type of font (for logging purposes) + */ + private void setFontIfPresent(ReadableMap themeConfig, com.instabug.library.model.IBGTheme.Builder builder, + String fileKey, String assetKey, String fontType) { + if (themeConfig.hasKey(fileKey) || themeConfig.hasKey(assetKey)) { + Typeface typeface = getTypeface(themeConfig, fileKey, assetKey); + if (typeface != null) { + switch (fontType) { + case "primary": + builder.setPrimaryTextFont(typeface); + break; + case "secondary": + builder.setSecondaryTextFont(typeface); + break; + case "CTA": + builder.setCtaTextFont(typeface); + break; + } + } else { + Log.e("InstabugModule", "Failed to load " + fontType + " font"); + } + } + } + + /** + * Loads a Typeface from a file path. + * + * @param fileName The filename to load + * @return The loaded Typeface or null if failed + */ + private Typeface loadTypefaceFromFile(String fileName) { + try { + Typeface typeface = Typeface.create(fileName, Typeface.NORMAL); + if (typeface != null && !typeface.equals(Typeface.DEFAULT)) { + return typeface; + } + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + /** + * Loads a Typeface from assets. + * + * @param fileName The filename in assets/fonts/ directory + * @return The loaded Typeface or null if failed + */ + private Typeface loadTypefaceFromAssets(String fileName) { + try { + return Typeface.createFromAsset(getReactApplicationContext().getAssets(), "fonts/" + fileName); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + private Typeface getTypeface(ReadableMap map, String fileKey, String assetKey) { + try { + if (fileKey != null && map.hasKey(fileKey) && !map.isNull(fileKey)) { + String fontPath = map.getString(fileKey); + String fileName = getFileName(fontPath); + + // Try loading from file first + Typeface typeface = loadTypefaceFromFile(fileName); + if (typeface != null) { + return typeface; + } + + // Try loading from assets + typeface = loadTypefaceFromAssets(fileName); + if (typeface != null) { + return typeface; + } + } + + if (assetKey != null && map.hasKey(assetKey) && !map.isNull(assetKey)) { + String assetPath = map.getString(assetKey); + String fileName = getFileName(assetPath); + return loadTypefaceFromAssets(fileName); + } + } catch (Exception e) { + e.printStackTrace(); + } + + return Typeface.DEFAULT; + } + +/** + * Extracts the filename from a path, removing any directory prefixes. + * + * @param path The full path to the file + * @return Just the filename with extension + */ +private String getFileName(String path) { + if (path == null || path.isEmpty()) { + return path; + } + + int lastSeparator = Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\')); + if (lastSeparator >= 0 && lastSeparator < path.length() - 1) { + return path.substring(lastSeparator + 1); + } + + return path; +} + + /** + * Enables or disables displaying in full-screen mode, hiding the status and navigation bars. + * @param isEnabled A boolean to enable/disable setFullscreen. + */ + @ReactMethod + public void setFullscreen(final boolean isEnabled) { + MainThreadHandler.runOnMainThread(new Runnable() { + @Override + public void run() { + try { + Instabug.setFullscreen(isEnabled); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } } diff --git a/android/src/test/java/com/instabug/reactlibrary/RNInstabugAPMModuleTest.java b/android/src/test/java/com/instabug/reactlibrary/RNInstabugAPMModuleTest.java index 85ca1384d..b10058f48 100644 --- a/android/src/test/java/com/instabug/reactlibrary/RNInstabugAPMModuleTest.java +++ b/android/src/test/java/com/instabug/reactlibrary/RNInstabugAPMModuleTest.java @@ -114,17 +114,6 @@ public void givenTruesetEnabled_whenQuery_thenShouldCallNativeApiWithEnabled() { APM.endAppLaunch(); } - @Test - public void givenString$startExecutionTrace_whenQuery_thenShouldCallNativeApi() { - Promise promise = mock(Promise.class); - // when - apmModule.startExecutionTrace("trace", "1", promise); - // then - verify(APM.class, times(1)); - APM.startExecutionTrace("trace"); - verify(promise).resolve(any()); - } - @Test public void testStartFlow() { String appFlowName = "appFlowName"; @@ -156,34 +145,6 @@ public void testSetFlowAttribute() { mockAPM.verifyNoMoreInteractions(); } - // @Test - // public void givenString$setExecutionTraceAttribute_whenQuery_thenShouldCallNativeApiWithIntArgs() { - // // given - // PowerMockito.mockStatic(APM.class); - // ExecutionTrace trace = mock(ExecutionTrace.class); - // Callback callback = mock(Callback.class); - // // when - // PowerMockito.whenNew(ExecutionTrace.class).withArguments("trace").thenReturn(trace); - // apmModule.startExecutionTrace("trace", "1", callback); - // apmModule.setExecutionTraceAttribute("trace", "key", "value"); - // // then - // verify(trace).setAttribute("key", "value"); - // } - - // @Test - // public void givenTrace$endExecutionTrace_whenQuery_thenShouldCallNativeApiWithIntArgs() { - // // given - // PowerMockito.mockStatic(APM.class); - // ExecutionTrace trace = mock(ExecutionTrace.class); - // Callback callback = mock(Callback.class); - // // when - // PowerMockito.whenNew(ExecutionTrace.class).withArguments("trace").thenReturn(trace); - // apmModule.startExecutionTrace("trace", "1", callback); - // apmModule.endExecutionTrace("1"); - // // then - // verify(trace).end(); - // } - @Test public void givenString$startUITrace_whenQuery_thenShouldCallNativeApiWithEnabled() { diff --git a/android/src/test/java/com/instabug/reactlibrary/RNInstabugBugReportingModuleTest.java b/android/src/test/java/com/instabug/reactlibrary/RNInstabugBugReportingModuleTest.java index dc55e81a5..15cef0e2f 100644 --- a/android/src/test/java/com/instabug/reactlibrary/RNInstabugBugReportingModuleTest.java +++ b/android/src/test/java/com/instabug/reactlibrary/RNInstabugBugReportingModuleTest.java @@ -359,8 +359,8 @@ public Object answer(InvocationOnMock invocation) { // then verify(BugReporting.class, VerificationModeFactory.times(1)); int type1 = args.get(keysArray[0]); - - BugReporting.setCommentMinimumCharacterCount(count, type1); + + BugReporting.setCommentMinimumCharacterCountForBugReportType(count, type1); } @Test public void TestAddUserConsent() { diff --git a/android/src/test/java/com/instabug/reactlibrary/RNInstabugReactnativeModuleTest.java b/android/src/test/java/com/instabug/reactlibrary/RNInstabugReactnativeModuleTest.java index f4f6f9bc1..3a0edc5f0 100644 --- a/android/src/test/java/com/instabug/reactlibrary/RNInstabugReactnativeModuleTest.java +++ b/android/src/test/java/com/instabug/reactlibrary/RNInstabugReactnativeModuleTest.java @@ -193,18 +193,6 @@ public void tearDown() { Instabug.setUserData(data); } - @Test - public void givenArg$setPrimaryColor_whenQuery_thenShouldCallNativeApiWithArg() { - // given - - int color = 3902; - // when - rnModule.setPrimaryColor(color); - // then - verify(Instabug.class,times(1)); - Instabug.setPrimaryColor(color); - } - @Test public void testSetCodePushVersion() { String codePushVersion = "123"; @@ -535,51 +523,6 @@ public void testIdentifyUserWithId() { } - @Test - public void givenArg$addExperiments_whenQuery_thenShouldCallNativeApiWithArg() { - // given - JavaOnlyArray array = new JavaOnlyArray(); - array.pushString("exp1"); - array.pushString("exp2"); - - // when - rnModule.addExperiments(array); - - // then - verify(Instabug.class,times(1)); - List expectedList = new ArrayList(); - expectedList.add("exp1"); - expectedList.add("exp2"); - Instabug.addExperiments(expectedList); - } - - @Test - public void givenArg$removeExperiments_whenQuery_thenShouldCallNativeApiWithArg() { - // given - JavaOnlyArray array = new JavaOnlyArray(); - array.pushString("exp1"); - array.pushString("exp2"); - - // when - rnModule.removeExperiments(array); - - // then - verify(Instabug.class,times(1)); - List expectedList = new ArrayList(); - expectedList.add("exp1"); - expectedList.add("exp2"); - Instabug.removeExperiments(expectedList); - } - - @Test - public void given$clearAllExperiments_whenQuery_thenShouldCallNativeApi() { - // when - rnModule.clearAllExperiments(); - - // then - verify(Instabug.class,times(1)); - Instabug.clearAllExperiments(); - } @Test public void testAddFeatureFlags() { @@ -631,6 +574,16 @@ public void testRemoveAllFeatureFlags() { mockInstabug.verify(() -> Instabug.removeAllFeatureFlags()); } + @Test + public void testSetAppVariant() { + String appVariant="App-variant"; + // when + rnModule.setAppVariant(appVariant); + + // then + mockInstabug.verify(() -> Instabug.setAppVariant(appVariant)); + } + @Test public void testWillRedirectToStore() { // when @@ -678,7 +631,7 @@ public void testSetNetworkLogBodyDisabled() { mockInstabug.verify(() -> Instabug.setNetworkLogBodyEnabled(false)); } - + @Test public void testEnableAutoMasking(){ @@ -704,4 +657,72 @@ public void testGetNetworkBodyMaxSize_resolvesPromiseWithExpectedValue() { verify(promise).resolve(expected); } + @Test + public void testEnablSetFullScreen() { + boolean isEnabled = true; + + // when + rnModule.setFullscreen(isEnabled); + + // then + verify(Instabug.class, times(1)); + Instabug.setFullscreen(isEnabled); + } + + @Test + public void testDisableSetFullScreen() { + // given + boolean isEnabled = false; + + // when + rnModule.setFullscreen(isEnabled); + + // then + verify(Instabug.class, times(1)); + Instabug.setFullscreen(isEnabled); + } + + @Test + public void testSetTheme() { + // given + JavaOnlyMap themeConfig = new JavaOnlyMap(); + themeConfig.putString("primaryColor", "#FF0000"); + themeConfig.putString("primaryTextColor", "#00FF00"); + themeConfig.putString("secondaryTextColor", "#0000FF"); + themeConfig.putString("titleTextColor", "#FFFF00"); + themeConfig.putString("backgroundColor", "#FF00FF"); + themeConfig.putString("primaryTextStyle", "bold"); + themeConfig.putString("secondaryTextStyle", "italic"); + themeConfig.putString("ctaTextStyle", "bold"); + + // Mock IBGTheme.Builder + com.instabug.library.model.IBGTheme.Builder mockBuilder = mock(com.instabug.library.model.IBGTheme.Builder.class); + com.instabug.library.model.IBGTheme mockTheme = mock(com.instabug.library.model.IBGTheme.class); + + try (MockedConstruction mockedBuilder = mockConstruction( + com.instabug.library.model.IBGTheme.Builder.class, + (mock, context) -> { + when(mock.setPrimaryColor(anyInt())).thenReturn(mock); + when(mock.setPrimaryTextColor(anyInt())).thenReturn(mock); + when(mock.setSecondaryTextColor(anyInt())).thenReturn(mock); + when(mock.setTitleTextColor(anyInt())).thenReturn(mock); + when(mock.setBackgroundColor(anyInt())).thenReturn(mock); + when(mock.setPrimaryTextStyle(anyInt())).thenReturn(mock); + when(mock.setSecondaryTextStyle(anyInt())).thenReturn(mock); + when(mock.setCtaTextStyle(anyInt())).thenReturn(mock); + when(mock.setPrimaryTextFont(any())).thenReturn(mock); + when(mock.setSecondaryTextFont(any())).thenReturn(mock); + when(mock.setCtaTextFont(any())).thenReturn(mock); + when(mock.build()).thenReturn(mockTheme); + })) { + + // when + rnModule.setTheme(themeConfig); + + // then + verify(Instabug.class, times(1)); + Instabug.setTheme(mockTheme); + } + } + } diff --git a/android/src/test/java/com/instabug/reactlibrary/RNInstabugTest.java b/android/src/test/java/com/instabug/reactlibrary/RNInstabugTest.java index 625eab1c9..643a1e136 100644 --- a/android/src/test/java/com/instabug/reactlibrary/RNInstabugTest.java +++ b/android/src/test/java/com/instabug/reactlibrary/RNInstabugTest.java @@ -67,7 +67,7 @@ public void testInitWithLogLevel() { when(mock.setInvocationEvents(any())).thenReturn(mock); }); - sut.init(mContext, token, logLevel, true, invocationEvents); + sut.init(mContext, token, logLevel, null, null,true, invocationEvents); Instabug.Builder builder = mInstabugBuilder.constructed().get(0); @@ -89,16 +89,19 @@ public void testInitWithoutLogLevel() { final InstabugInvocationEvent[] invocationEvents = new InstabugInvocationEvent[]{InstabugInvocationEvent.FLOATING_BUTTON}; final String token = "fde...."; final int defaultLogLevel = LogLevel.ERROR; + final String appVariant = "app-variant"; MockedConstruction mInstabugBuilder = mockConstruction( Instabug.Builder.class, (mock, context) -> { when(mock.setSdkDebugLogsLevel(anyInt())).thenReturn(mock); when(mock.setInvocationEvents(any())).thenReturn(mock); + when(mock.setAppVariant(any())).thenReturn(mock); + }); - sut.init(mContext, token, invocationEvents); + sut.init(mContext, token, null, appVariant, invocationEvents); - verify(sut).init(mContext, token, defaultLogLevel, null,invocationEvents); + verify(sut).init(mContext, token, defaultLogLevel, null, appVariant, null,invocationEvents); mInstabugBuilder.close(); } diff --git a/examples/default/ios/InstabugExample.xcodeproj/project.pbxproj b/examples/default/ios/InstabugExample.xcodeproj/project.pbxproj index fb1253b31..0778e9a59 100644 --- a/examples/default/ios/InstabugExample.xcodeproj/project.pbxproj +++ b/examples/default/ios/InstabugExample.xcodeproj/project.pbxproj @@ -430,7 +430,7 @@ name = "[CP-User] [instabug-reactnative] Upload Sourcemap"; runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "#!/bin/sh\n\nmain() {\n # Read environment variables from ios/.xcode.env if it exists\n env_path=\"$PODS_ROOT/../.xcode.env\"\n if [ -f \"$env_path\" ]; then\n source \"$env_path\"\n fi\n\n # Read environment variables from ios/.xcode.env.local if it exists\n local_env_path=\"${ENV_PATH}.local\"\n if [ -f \"$local_env_path\" ]; then\n source \"$local_env_path\"\n fi\n\n if [[ \"$INSTABUG_SOURCEMAPS_UPLOAD_DISABLE\" = true ]]; then\n echo \"[Info] \\`INSTABUG_SOURCEMAPS_UPLOAD_DISABLE\\` was set to true, skipping sourcemaps upload...\"\n exit 0\n fi\n\n if [[ \"$CONFIGURATION\" = \"Debug\" ]]; then\n echo \"[Info] Building in debug mode, skipping sourcemaps upload...\"\n exit 0\n fi\n\n if [[ -z \"$INFOPLIST_FILE\" ]] || [[ -z \"$PROJECT_DIR\" ]]; then\n echo \"[Error] Instabug sourcemaps script must be invoked by Xcode\"\n exit 0\n fi\n\n local source_map_file=$(generate_sourcemaps | tail -n 1)\n\n local js_project_dir=\"$PROJECT_DIR/..\"\n local instabug_dir=$(dirname $(node -p \"require.resolve('instabug-reactnative/package.json')\"))\n local inferred_token=$(cd $js_project_dir && source $instabug_dir/scripts/find-token.sh)\n local app_token=$(resolve_var \"App Token\" \"INSTABUG_APP_TOKEN\" \"$inferred_token\" | tail -n 1)\n\n local inferred_name=$(/usr/libexec/PlistBuddy -c 'print CFBundleShortVersionString' \"$PROJECT_DIR/$INFOPLIST_FILE\")\n local version_name=$(resolve_var \"Version Name\" \"INSTABUG_APP_VERSION_NAME\" \"$inferred_name\" | tail -n 1)\n\n local inferred_code=$(/usr/libexec/PlistBuddy -c 'print CFBundleVersion' \"$PROJECT_DIR/$INFOPLIST_FILE\")\n local version_code=$(resolve_var \"Version Code\" \"INSTABUG_APP_VERSION_CODE\" \"$inferred_code\" | tail -n 1)\n\n node $instabug_dir/bin/index.js upload-sourcemaps \\\n --platform ios \\\n --file $source_map_file \\\n --token $app_token \\\n --name $version_name \\\n --code $version_code\n}\n\ngenerate_sourcemaps() {\n local react_native_dir=$(dirname $(node -p \"require.resolve('react-native/package.json')\"))\n\n # Fixes an issue with react-native prior to v0.67.0\n # For more info: https://github.com/facebook/react-native/issues/32168\n export RN_DIR=$react_native_dir\n\n # Used withing `react-native-xcode.sh` to generate sourcemap file\n export SOURCEMAP_FILE=\"$(pwd)/main.jsbundle.map\";\n\n source \"$react_native_dir/scripts/react-native-xcode.sh\"\n\n if [[ ! -f \"$SOURCEMAP_FILE\" ]]; then\n echo \"[Error] Unable to find source map file at: $SOURCEMAP_FILE\"\n exit 0\n fi\n\n echo $SOURCEMAP_FILE\n}\n\nresolve_var() {\n local name=$1\n local env_key=$2\n local default_value=$3\n\n local env_value=\"${!env_key}\"\n\n if [[ -n \"$env_value\" ]] && [[ -n \"$default_value\" ]] && [[ \"$env_value\" != default_value ]]; then\n echo \"[Warning] Environment variable \\`$env_key\\` might have incorrect value, make sure this was intentional:\"\n echo \" Environment Value: $env_value\"\n echo \" Default Value: $default_value\"\n fi\n\n local value=\"${env_value:-$default_value}\"\n\n if [[ -z \"$value\" ]]; then\n echo \"[Error] Unable to find $name! Set the environment variable \\`$env_key\\` and try again.\"\n exit 0\n fi\n\n echo $value\n}\n\nmain \"$@\"; exit\n"; + shellScript = "#!/bin/sh\n\n\nexport SOURCEMAP_FILE=\"$DERIVED_FILE_DIR/main.jsbundle.map\"\n\nmain() {\n # Read environment variables from ios/.xcode.env if it exists\n env_path=\"$PODS_ROOT/../.xcode.env\"\n if [ -f \"$env_path\" ]; then\n source \"$env_path\"\n fi\n\n # Read environment variables from ios/.xcode.env.local if it exists\n local_env_path=\"${ENV_PATH}.local\"\n if [ -f \"$local_env_path\" ]; then\n source \"$local_env_path\"\n fi\n\n if [[ \"$INSTABUG_SOURCEMAPS_UPLOAD_DISABLE\" = true ]]; then\n echo \"[Info] \\`INSTABUG_SOURCEMAPS_UPLOAD_DISABLE\\` was set to true, skipping sourcemaps upload...\"\n exit 0\n fi\n\n if [[ \"$CONFIGURATION\" = \"Debug\" ]]; then\n echo \"[Info] Building in debug mode, skipping sourcemaps upload...\"\n exit 0\n fi\n\n if [[ -z \"$INFOPLIST_FILE\" ]] || [[ -z \"$PROJECT_DIR\" ]]; then\n echo \"[Error] Instabug sourcemaps script must be invoked by Xcode\"\n exit 0\n fi\n\n\nlocal sourcemap_file=\"\"\n # Use existing sourcemap if available\n if [[ -f \"$SOURCEMAP_FILE\" ]]; then\n sourcemap_file=\"$SOURCEMAP_FILE\"\n else\n sourcemap_file=$(generate_sourcemaps | tail -n 1)\nfi\n\n local js_project_dir=\"$PROJECT_DIR/..\"\n local instabug_dir=$(dirname $(node -p \"require.resolve('instabug-reactnative/package.json')\"))\n local inferred_token=$(cd $js_project_dir && source $instabug_dir/scripts/find-token.sh)\n local app_token=$(resolve_var \"App Token\" \"INSTABUG_APP_TOKEN\" \"$inferred_token\" | tail -n 1)\n\n local inferred_name=$(/usr/libexec/PlistBuddy -c 'print CFBundleShortVersionString' \"$PROJECT_DIR/$INFOPLIST_FILE\")\n local version_name=$(resolve_var \"Version Name\" \"INSTABUG_APP_VERSION_NAME\" \"$inferred_name\" | tail -n 1)\n\n local inferred_code=$(/usr/libexec/PlistBuddy -c 'print CFBundleVersion' \"$PROJECT_DIR/$INFOPLIST_FILE\")\n local version_code=$(resolve_var \"Version Code\" \"INSTABUG_APP_VERSION_CODE\" \"$inferred_code\" | tail -n 1)\n\nif [ -n \"$sourcemap_file\" ]; then\n node $instabug_dir/bin/index.js upload-sourcemaps \\\n --platform ios \\\n --file $sourcemap_file \\\n --token $app_token \\\n --name $version_name \\\n --code $version_code\n fi\n}\n\ngenerate_sourcemaps() {\n local react_native_dir=$(dirname $(node -p \"require.resolve('react-native/package.json')\"))\n\n # Fixes an issue with react-native prior to v0.67.0\n # For more info: https://github.com/facebook/react-native/issues/32168\n export RN_DIR=$react_native_dir\n\n # Used withing `react-native-xcode.sh` to generate sourcemap file\n export SOURCEMAP_FILE=\"$(pwd)/main.jsbundle.map\";\n\n source \"$react_native_dir/scripts/react-native-xcode.sh\"\n\n if [[ ! -f \"$SOURCEMAP_FILE\" ]]; then\n echo \"[Error] Unable to find source map file at: $SOURCEMAP_FILE\"\n exit 0\n fi\n\n echo $SOURCEMAP_FILE\n}\n\nresolve_var() {\n local name=$1\n local env_key=$2\n local default_value=$3\n\n local env_value=\"${!env_key}\"\n\n if [[ -n \"$env_value\" ]] && [[ -n \"$default_value\" ]] && [[ \"$env_value\" != default_value ]]; then\n echo \"[Warning] Environment variable \\`$env_key\\` might have incorrect value, make sure this was intentional:\"\n echo \" Environment Value: $env_value\"\n echo \" Default Value: $default_value\"\n fi\n\n local value=\"${env_value:-$default_value}\"\n\n if [[ -z \"$value\" ]]; then\n echo \"[Error] Unable to find $name! Set the environment variable \\`$env_key\\` and try again.\"\n exit 0\n fi\n\n echo $value\n}\n\nmain \"$@\"; exit\n"; }; B77A7BA143DBD17E8AAFD0B4 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; diff --git a/examples/default/ios/InstabugTests/InstabugAPMTests.m b/examples/default/ios/InstabugTests/InstabugAPMTests.m index 949393adb..40fae6129 100644 --- a/examples/default/ios/InstabugTests/InstabugAPMTests.m +++ b/examples/default/ios/InstabugTests/InstabugAPMTests.m @@ -86,53 +86,6 @@ - (void) testSetAutoUITraceEnabled { OCMVerify([mock setAutoUITraceEnabled:isEnabled]); } -- (void) testStartExecutionTrace { - id mock = OCMClassMock([IBGAPM class]); - NSString* traceName = @"Trace_1"; - NSString* traceKey = @"1"; - RCTPromiseResolveBlock resolve = ^(id result) {}; - RCTPromiseRejectBlock reject = ^(NSString *code, NSString *message, NSError *error) {}; - - OCMStub([mock startExecutionTraceWithName:traceName]); - [self.instabugBridge startExecutionTrace:traceName :traceKey :resolve :reject]; - OCMVerify([mock startExecutionTraceWithName:traceName]); -} - -- (void) testSetExecutionTraceAttribute { - NSString* traceName = @"Trace_1"; - NSString* traceId = @"Id_1"; - NSString* traceKey = @"Key_1"; - NSString* traceValue = @"1"; - RCTPromiseResolveBlock resolve = ^(id result) {}; - RCTPromiseRejectBlock reject = ^(NSString *code, NSString *message, NSError *error) {}; - IBGExecutionTrace * trace = [IBGExecutionTrace alloc]; - id mock = OCMClassMock([IBGAPM class]); - id traceMock = OCMPartialMock(trace); - - OCMStub([mock startExecutionTraceWithName:traceName]).andReturn(trace); - [self.instabugBridge startExecutionTrace:traceName :traceId :resolve :reject]; - - OCMStub([traceMock setAttributeWithKey:traceKey value:traceValue]); - [self.instabugBridge setExecutionTraceAttribute:traceId :traceKey :traceValue]; - OCMVerify([traceMock setAttributeWithKey:traceKey value:traceValue]); -} - -- (void) testEndExecutionTrace { - NSString* traceName = @"Trace_1"; - NSString* traceId = @"Id_1"; - RCTPromiseResolveBlock resolve = ^(id result) {}; - RCTPromiseRejectBlock reject = ^(NSString *code, NSString *message, NSError *error) {}; - IBGExecutionTrace * trace = [IBGExecutionTrace alloc]; - id apmMock = OCMClassMock([IBGAPM class]); - id traceMock = OCMPartialMock(trace); - - OCMStub([apmMock startExecutionTraceWithName:traceName]).andReturn(trace); - [self.instabugBridge startExecutionTrace:traceName :traceId :resolve :reject]; - - OCMStub([traceMock end]); - [self.instabugBridge endExecutionTrace:traceId]; - OCMVerify([traceMock end]); -} - (void) testStartFlow { id mock = OCMClassMock([IBGAPM class]); diff --git a/examples/default/ios/InstabugTests/InstabugBugReportingTests.m b/examples/default/ios/InstabugTests/InstabugBugReportingTests.m index f0b6f97ec..4d0250dc1 100644 --- a/examples/default/ios/InstabugTests/InstabugBugReportingTests.m +++ b/examples/default/ios/InstabugTests/InstabugBugReportingTests.m @@ -175,18 +175,6 @@ - (void) testSetDisclaimerText { OCMVerify([mock setDisclaimerText:text]); } -- (void) testSetCommentMinimumCharacterCount { - id mock = OCMClassMock([IBGBugReporting class]); - NSNumber *limit = [NSNumber numberWithInt:20]; - NSArray *reportTypesArr = [NSArray arrayWithObjects: @(IBGReportTypeBug), nil]; - IBGBugReportingReportType reportTypes = 0; - for (NSNumber *reportType in reportTypesArr) { - reportTypes |= [reportType intValue]; - } - OCMStub([mock setCommentMinimumCharacterCountForReportTypes:reportTypes withLimit:limit.intValue]); - [self.instabugBridge setCommentMinimumCharacterCount:limit reportTypes:reportTypesArr]; - OCMVerify([mock setCommentMinimumCharacterCountForReportTypes:reportTypes withLimit:limit.intValue]); -} - (void)testAddUserConsentWithKey { id mock = OCMClassMock([IBGBugReporting class]); diff --git a/examples/default/ios/InstabugTests/InstabugSampleTests.m b/examples/default/ios/InstabugTests/InstabugSampleTests.m index 34fe9cfe3..50e1cb2b9 100644 --- a/examples/default/ios/InstabugTests/InstabugSampleTests.m +++ b/examples/default/ios/InstabugTests/InstabugSampleTests.m @@ -69,17 +69,19 @@ - (void)testInit { IBGInvocationEvent floatingButtonInvocationEvent = IBGInvocationEventFloatingButton; NSString *appToken = @"app_token"; NSString *codePushVersion = @"1.0.0(1)"; + NSString *appVariant = @"variant 1"; + NSArray *invocationEvents = [NSArray arrayWithObjects:[NSNumber numberWithInteger:floatingButtonInvocationEvent], nil]; BOOL useNativeNetworkInterception = YES; IBGSDKDebugLogsLevel sdkDebugLogsLevel = IBGSDKDebugLogsLevelDebug; OCMStub([mock setCodePushVersion:codePushVersion]); - [self.instabugBridge init:appToken invocationEvents:invocationEvents debugLogsLevel:sdkDebugLogsLevel useNativeNetworkInterception:useNativeNetworkInterception codePushVersion:codePushVersion - options:nil - ]; + [self.instabugBridge init:appToken invocationEvents:invocationEvents debugLogsLevel:sdkDebugLogsLevel useNativeNetworkInterception:useNativeNetworkInterception codePushVersion:codePushVersion appVariant:appVariant options:nil ]; OCMVerify([mock setCodePushVersion:codePushVersion]); + XCTAssertEqual(Instabug.appVariant, appVariant); + OCMVerify([self.mRNInstabug initWithToken:appToken invocationEvents:floatingButtonInvocationEvent debugLogsLevel:sdkDebugLogsLevel useNativeNetworkInterception:useNativeNetworkInterception]); } @@ -101,6 +103,14 @@ - (void)testSetUserData { OCMVerify([mock setUserData:userData]); } +- (void)testSetAppVariant { + id mock = OCMClassMock([Instabug class]); + NSString *appVariant = @"appVariant"; + + [self.instabugBridge setAppVariant: appVariant]; + XCTAssertEqual(Instabug.appVariant, appVariant); +} + - (void)testSetTrackUserSteps { id mock = OCMClassMock([Instabug class]); BOOL isEnabled = true; @@ -143,19 +153,6 @@ - (void)testSetColorTheme { [self waitForExpectationsWithTimeout:EXPECTATION_TIMEOUT handler:nil]; } -- (void)testSetPrimaryColor { - UIColor *color = [UIColor whiteColor]; - XCTestExpectation *expectation = [self expectationWithDescription:@"Testing [Instabug setPrimaryColor]"]; - - [self.instabugBridge setPrimaryColor:color]; - [[NSRunLoop mainRunLoop] performBlock:^{ - XCTAssertEqualObjects(Instabug.tintColor, color); - [expectation fulfill]; - }]; - - [self waitForExpectationsWithTimeout:EXPECTATION_TIMEOUT handler:nil]; -} - - (void)testAppendTags { id mock = OCMClassMock([Instabug class]); NSArray *tags = @[@"tag1", @"tag2"]; @@ -199,7 +196,7 @@ - (void)testIdentifyUser { NSString *email = @"em@il.com"; NSString *name = @"this is my name"; - OCMStub([mock identifyUserWithEmail:email name:name]); + OCMStub([mock identifyUserWithID:nil email:email name:name]); [self.instabugBridge identifyUser:email name:name userId:nil]; OCMVerify([mock identifyUserWithID:nil email:email name:name]); } @@ -241,7 +238,7 @@ - (void)testSetReproStepsConfig { [self.instabugBridge setReproStepsConfig:bugMode :crashMode :sessionReplayMode]; OCMVerify([mock setReproStepsFor:IBGIssueTypeBug withMode:bugMode]); - OCMVerify([mock setReproStepsFor:IBGIssueTypeCrash withMode:crashMode]); + OCMVerify([mock setReproStepsFor:IBGIssueTypeAllCrashes withMode:crashMode]); OCMVerify([mock setReproStepsFor:IBGIssueTypeSessionReplay withMode:sessionReplayMode]); } @@ -488,30 +485,6 @@ - (void)testClearLogs { OCMVerify([mock clearAllLogs]); } -- (void)testAddExperiments { - id mock = OCMClassMock([Instabug class]); - NSArray *experiments = @[@"exp1", @"exp2"]; - - OCMStub([mock addExperiments:experiments]); - [self.instabugBridge addExperiments:experiments]; - OCMVerify([mock addExperiments:experiments]); -} - -- (void)testRemoveExperiments { - id mock = OCMClassMock([Instabug class]); - NSArray *experiments = @[@"exp1", @"exp2"]; - - OCMStub([mock removeExperiments:experiments]); - [self.instabugBridge removeExperiments:experiments]; - OCMVerify([mock removeExperiments:experiments]); -} - -- (void)testClearAllExperiments { - id mock = OCMClassMock([Instabug class]); - OCMStub([mock clearAllExperiments]); - [self.instabugBridge clearAllExperiments]; - OCMVerify([mock clearAllExperiments]); -} - (void)testAddFeatureFlags { id mock = OCMClassMock([Instabug class]); @@ -653,6 +626,73 @@ - (void)testGetNetworkBodyMaxSize { OCMVerify(ClassMethod([mock getNetworkBodyMaxSize])); } +- (void)testSetTheme { + id mock = OCMClassMock([Instabug class]); + id mockTheme = OCMClassMock([IBGTheme class]); + + // Create theme configuration dictionary + NSDictionary *themeConfig = @{ + @"primaryColor": @"#FF0000", + @"backgroundColor": @"#00FF00", + @"titleTextColor": @"#0000FF", + @"subtitleTextColor": @"#FFFF00", + @"primaryTextColor": @"#FF00FF", + @"secondaryTextColor": @"#00FFFF", + @"callToActionTextColor": @"#800080", + @"headerBackgroundColor": @"#808080", + @"footerBackgroundColor": @"#C0C0C0", + @"rowBackgroundColor": @"#FFFFFF", + @"selectedRowBackgroundColor": @"#E6E6FA", + @"rowSeparatorColor": @"#D3D3D3", + @"primaryFontPath": @"TestFont.ttf", + @"secondaryFontPath": @"fonts/AnotherFont.ttf", + @"ctaFontPath": @"./assets/fonts/CTAFont.ttf" + }; + + // Mock IBGTheme creation and configuration + OCMStub([mockTheme primaryColor]).andReturn([UIColor redColor]); + OCMStub([mockTheme backgroundColor]).andReturn([UIColor greenColor]); + OCMStub([mockTheme titleTextColor]).andReturn([UIColor blueColor]); + OCMStub([mockTheme subtitleTextColor]).andReturn([UIColor yellowColor]); + OCMStub([mockTheme primaryTextColor]).andReturn([UIColor magentaColor]); + OCMStub([mockTheme secondaryTextColor]).andReturn([UIColor cyanColor]); + OCMStub([mockTheme callToActionTextColor]).andReturn([UIColor purpleColor]); + OCMStub([mockTheme headerBackgroundColor]).andReturn([UIColor grayColor]); + OCMStub([mockTheme footerBackgroundColor]).andReturn([UIColor lightGrayColor]); + OCMStub([mockTheme rowBackgroundColor]).andReturn([UIColor whiteColor]); + OCMStub([mockTheme selectedRowBackgroundColor]).andReturn([UIColor redColor]); + OCMStub([mockTheme rowSeparatorColor]).andReturn([UIColor lightGrayColor]); + OCMStub([mockTheme primaryTextFont]).andReturn([UIFont systemFontOfSize:17.0]); + OCMStub([mockTheme secondaryTextFont]).andReturn([UIFont systemFontOfSize:17.0]); + OCMStub([mockTheme callToActionTextFont]).andReturn([UIFont systemFontOfSize:17.0]); + + // Mock theme property setting + OCMStub([mockTheme setPrimaryColor:[OCMArg any]]).andReturn(mockTheme); + OCMStub([mockTheme setBackgroundColor:[OCMArg any]]).andReturn(mockTheme); + OCMStub([mockTheme setTitleTextColor:[OCMArg any]]).andReturn(mockTheme); + OCMStub([mockTheme setSubtitleTextColor:[OCMArg any]]).andReturn(mockTheme); + OCMStub([mockTheme setPrimaryTextColor:[OCMArg any]]).andReturn(mockTheme); + OCMStub([mockTheme setSecondaryTextColor:[OCMArg any]]).andReturn(mockTheme); + OCMStub([mockTheme setCallToActionTextColor:[OCMArg any]]).andReturn(mockTheme); + OCMStub([mockTheme setHeaderBackgroundColor:[OCMArg any]]).andReturn(mockTheme); + OCMStub([mockTheme setFooterBackgroundColor:[OCMArg any]]).andReturn(mockTheme); + OCMStub([mockTheme setRowBackgroundColor:[OCMArg any]]).andReturn(mockTheme); + OCMStub([mockTheme setSelectedRowBackgroundColor:[OCMArg any]]).andReturn(mockTheme); + OCMStub([mockTheme setRowSeparatorColor:[OCMArg any]]).andReturn(mockTheme); + OCMStub([mockTheme setPrimaryTextFont:[OCMArg any]]).andReturn(mockTheme); + OCMStub([mockTheme setSecondaryTextFont:[OCMArg any]]).andReturn(mockTheme); + OCMStub([mockTheme setCallToActionTextFont:[OCMArg any]]).andReturn(mockTheme); + + // Mock Instabug.theme property + OCMStub([mock theme]).andReturn(mockTheme); + OCMStub([mock setTheme:[OCMArg any]]); + + // Call the method + [self.instabugBridge setTheme:themeConfig]; + + // Verify that setTheme was called + OCMVerify([mock setTheme:[OCMArg any]]); +} @end diff --git a/examples/default/ios/Podfile.lock b/examples/default/ios/Podfile.lock index 274dbfc09..01837ec1a 100644 --- a/examples/default/ios/Podfile.lock +++ b/examples/default/ios/Podfile.lock @@ -31,7 +31,7 @@ PODS: - hermes-engine (0.75.4): - hermes-engine/Pre-built (= 0.75.4) - hermes-engine/Pre-built (0.75.4) - - Instabug (15.1.1) + - Instabug (16.0.1) - instabug-reactnative-ndk (0.1.0): - DoubleConversion - glog @@ -1625,8 +1625,8 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - RNInstabug (15.0.2): - - Instabug (= 15.1.1) + - RNInstabug (16.0.0): + - Instabug (= 16.0.1) - React-Core - RNReanimated (3.16.1): - DoubleConversion @@ -2022,7 +2022,7 @@ SPEC CHECKSUMS: Google-Maps-iOS-Utils: f77eab4c4326d7e6a277f8e23a0232402731913a GoogleMaps: 032f676450ba0779bd8ce16840690915f84e57ac hermes-engine: ea92f60f37dba025e293cbe4b4a548fd26b610a0 - Instabug: 3e7af445c14d7823fcdecba223f09b5f7c0c6ce1 + Instabug: f31a27e70cb6580aef656d1abf8c57a5bb5633d0 instabug-reactnative-ndk: d765ac289d56e8896398d02760d9abf2562fc641 OCMock: 589f2c84dacb1f5aaf6e4cec1f292551fe748e74 RCT-Folly: 4464f4d875961fce86008d45f4ecf6cef6de0740 @@ -2090,7 +2090,7 @@ SPEC CHECKSUMS: ReactCommon: 6a952e50c2a4b694731d7682aaa6c79bc156e4ad RNCClipboard: 2821ac938ef46f736a8de0c8814845dde2dcbdfb RNGestureHandler: 511250b190a284388f9dd0d2e56c1df76f14cfb8 - RNInstabug: c4d26c830b40c474422012d1a216d8ea37c88151 + RNInstabug: 6cf5149b0b50ad7d6806d761fc2014ce27bae779 RNReanimated: f42a5044d121d68e91680caacb0293f4274228eb RNScreens: c7ceced6a8384cb9be5e7a5e88e9e714401fd958 RNSVG: 8b1a777d54096b8c2a0fd38fc9d5a454332bbb4d diff --git a/examples/default/react-native.config.js b/examples/default/react-native.config.js new file mode 100644 index 000000000..cbdf34c94 --- /dev/null +++ b/examples/default/react-native.config.js @@ -0,0 +1,3 @@ +module.exports = { + assets: ['./assets/fonts/'], +}; diff --git a/examples/default/src/App.tsx b/examples/default/src/App.tsx index ad1c32579..82be996c5 100644 --- a/examples/default/src/App.tsx +++ b/examples/default/src/App.tsx @@ -49,6 +49,7 @@ export const App: React.FC = () => { invocationEvents: [InvocationEvent.floatingButton], debugLogsLevel: LogLevel.verbose, networkInterceptionMode: NetworkInterceptionMode.javascript, + appVariant: 'App variant', }); CrashReporting.setNDKCrashesEnabled(true); diff --git a/examples/default/src/navigation/HomeStack.tsx b/examples/default/src/navigation/HomeStack.tsx index 090aa6587..f3ebbf79b 100644 --- a/examples/default/src/navigation/HomeStack.tsx +++ b/examples/default/src/navigation/HomeStack.tsx @@ -22,7 +22,6 @@ import { import { GoogleMapsScreen } from '../screens/user-steps/GoogleMapsScreen'; import { LargeImageListScreen } from '../screens/user-steps/LargeImageListScreen'; import { APMScreen } from '../screens/apm/APMScreen'; -import { TracesScreen } from '../screens/apm/TracesScreen'; import { NetworkScreen } from '../screens/apm/NetworkScreen'; import { FlowsScreen } from '../screens/apm/FlowsScreen'; import { SessionReplayScreen } from '../screens/SessionReplayScreen'; @@ -140,7 +139,6 @@ export const HomeStackNavigator: React.FC = () => { - APM.endAppLaunch()} /> navigation.navigate('NetworkTraces')} /> - navigation.navigate('ExecutionTraces')} /> navigation.navigate('AppFlows')} /> navigation.navigate('WebViews')} /> navigation.navigate('ComplexViews')} /> diff --git a/examples/default/src/screens/apm/TracesScreen.tsx b/examples/default/src/screens/apm/TracesScreen.tsx deleted file mode 100644 index bd3e41838..000000000 --- a/examples/default/src/screens/apm/TracesScreen.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import React, { useState } from 'react'; -import { APM, Trace } from 'instabug-reactnative'; -import { ScrollView } from 'react-native'; -import { Section } from '../../components/Section'; -import { Screen } from '../../components/Screen'; -import { VStack } from 'native-base'; -import { InputField } from '../../components/InputField'; -import { CustomButton } from '../../components/CustomButton'; -import BackgroundTimer from 'react-native-background-timer'; - -export const TracesScreen: React.FC = () => { - const [traceName, setTraceName] = useState(''); - const [traceAttributeKey, setTraceAttributeKey] = useState(''); - const [traceAttributeValue, setTraceAttributeValue] = useState(''); - let executionTrace: Trace; - - async function startTrace() { - executionTrace = await APM.startExecutionTrace(traceName ?? ''); - } - - async function startDelayedTrace() { - return BackgroundTimer.setTimeout(async () => { - executionTrace = await APM.startExecutionTrace(traceName ?? ''); - }, 5000); - } - - function setTraceAttribute() { - if (!executionTrace) { - console.log('Please, start a trace before setting attributes.'); - } - return executionTrace.setAttribute(traceAttributeKey ?? '', traceAttributeValue ?? ''); - } - - function endExecutionTrace() { - if (!executionTrace) { - console.log('Please, start a trace before ending it.'); - } - return executionTrace.end(); - } - - return ( - - -
- - setTraceName(text)} - value={traceName} - /> - - - setTraceAttributeKey(text)} - value={traceAttributeKey} - /> - setTraceAttributeValue(text)} - value={traceAttributeValue} - /> - - - -
-
-
- ); -}; diff --git a/ios/RNInstabug/InstabugAPMBridge.h b/ios/RNInstabug/InstabugAPMBridge.h index 0a0ea397c..6b09dba6b 100644 --- a/ios/RNInstabug/InstabugAPMBridge.h +++ b/ios/RNInstabug/InstabugAPMBridge.h @@ -15,12 +15,6 @@ - (void)setAppLaunchEnabled:(BOOL)isEnabled; - (void)endAppLaunch; - (void)setAutoUITraceEnabled:(BOOL)isEnabled; -- (void)startExecutionTrace:(NSString *)name :(NSString *)id - :(RCTPromiseResolveBlock)resolve - :(RCTPromiseRejectBlock)reject DEPRECATED_MSG_ATTRIBUTE("Please use APM.startFlow instead."); -- (void)setExecutionTraceAttribute:(NSString *)id:(NSString *)key - :(NSString *)value DEPRECATED_MSG_ATTRIBUTE("Please use APM.setTraceAttribute instead."); -- (void)endExecutionTrace:(NSString *)id DEPRECATED_MSG_ATTRIBUTE("Please use APM.endFlow instead."); - (void)startFlow:(NSString *)name; - (void)endFlow:(NSString *)name; - (void)setFlowAttribute:(NSString *)name :(NSString *)key :(NSString *_Nullable)value; diff --git a/ios/RNInstabug/InstabugAPMBridge.m b/ios/RNInstabug/InstabugAPMBridge.m index c28c7f425..ab49af6fb 100644 --- a/ios/RNInstabug/InstabugAPMBridge.m +++ b/ios/RNInstabug/InstabugAPMBridge.m @@ -2,7 +2,6 @@ #import "InstabugAPMBridge.h" #import -#import #import #import #import @@ -27,12 +26,9 @@ + (BOOL)requiresMainQueueSetup RCT_EXPORT_MODULE(IBGAPM) -NSMutableDictionary *traces; - - (id) init { self = [super init]; - traces = [[NSMutableDictionary alloc] init]; return self; } @@ -61,41 +57,6 @@ - (id) init IBGAPM.autoUITraceEnabled = isEnabled; } -// Starts new execution trace with the specified `name`. -// -// Deprecated see [startFlow: (NSString *)name] -RCT_EXPORT_METHOD(startExecutionTrace:(NSString *)name :(NSString *)id - :(RCTPromiseResolveBlock)resolve - :(RCTPromiseRejectBlock)reject) { - IBGExecutionTrace *trace = [IBGAPM startExecutionTraceWithName:name]; - if (trace != nil) { - [traces setObject: trace forKey: id]; - resolve(id); - } else { - resolve([NSNull null]); - } -} - -// Sets a user defined attribute for the execution trace. -// -// Deprecated see [setFlowAttribute:(NSString *)name :(NSString *)key :(NSString *_Nullable)value] -RCT_EXPORT_METHOD(setExecutionTraceAttribute:(NSString *)id :(NSString *)key :(NSString *)value) { - IBGExecutionTrace *trace = [traces objectForKey:id]; - if (trace != nil) { - [trace setAttributeWithKey:key value:value]; - } -} - -// Ends execution trace with the specified `name`. -// -// Deprecated see [endFlow: (NSString *)name] -RCT_EXPORT_METHOD(endExecutionTrace:(NSString *)id) { - IBGExecutionTrace *trace = [traces objectForKey:id]; - if (trace != nil) { - [trace end]; - } -} - // Starts a flow trace with the specified `name`, // allowing the SDK to capture and analyze the flow of execution within the application. RCT_EXPORT_METHOD(startFlow: (NSString *)name) { diff --git a/ios/RNInstabug/InstabugBugReportingBridge.m b/ios/RNInstabug/InstabugBugReportingBridge.m index 75e058eb7..70efaa129 100644 --- a/ios/RNInstabug/InstabugBugReportingBridge.m +++ b/ios/RNInstabug/InstabugBugReportingBridge.m @@ -205,18 +205,16 @@ - (void) showBugReportingWithReportTypeAndOptionsHelper:(NSArray*)args { } RCT_EXPORT_METHOD(setCommentMinimumCharacterCount:(nonnull NSNumber *)limit reportTypes:(NSArray *)reportTypes) { - IBGBugReportingReportType parsedReportTypes = 0; - + IBGBugReportingType parsedReportTypes = 0; if (![reportTypes count]) { - parsedReportTypes = @(IBGBugReportingReportTypeBug).integerValue | @(IBGBugReportingReportTypeFeedback).integerValue | @(IBGBugReportingReportTypeQuestion).integerValue; + parsedReportTypes = @(IBGBugReportingTypeBug).integerValue | @(IBGBugReportingTypeFeedback).integerValue | @(IBGBugReportingTypeQuestion).integerValue; } else { for (NSNumber *reportType in reportTypes) { parsedReportTypes |= [reportType intValue]; } } - - [IBGBugReporting setCommentMinimumCharacterCountForReportTypes:parsedReportTypes withLimit:limit.intValue]; + [IBGBugReporting setCommentMinimumCharacterCount:[limit integerValue] forBugReportType:parsedReportTypes]; } RCT_EXPORT_METHOD(addUserConsent:(NSString *)key diff --git a/ios/RNInstabug/InstabugReactBridge.h b/ios/RNInstabug/InstabugReactBridge.h index 45c075098..187caa6c6 100644 --- a/ios/RNInstabug/InstabugReactBridge.h +++ b/ios/RNInstabug/InstabugReactBridge.h @@ -26,13 +26,14 @@ - (void)setEnabled:(BOOL)isEnabled; -- (void)init:(NSString *)token invocationEvents:(NSArray *)invocationEventsArray debugLogsLevel:(IBGSDKDebugLogsLevel)sdkDebugLogsLevel useNativeNetworkInterception:(BOOL)useNativeNetworkInterception codePushVersion:(NSString *)codePushVersion -options:(nullable NSDictionary *)options; +- (void)init:(NSString *)token invocationEvents:(NSArray *)invocationEventsArray debugLogsLevel:(IBGSDKDebugLogsLevel)sdkDebugLogsLevel useNativeNetworkInterception:(BOOL)useNativeNetworkInterception codePushVersion:(NSString *)codePushVersion appVariant:(NSString *)appVariant options:(nullable NSDictionary *)options; - (void)setCodePushVersion:(NSString *)version; - (void)setUserData:(NSString *)userData; +- (void)setAppVariant:(NSString *)appVariant; + - (void)setTrackUserSteps:(BOOL)isEnabled; - (void)setSessionProfilerEnabled:(BOOL)sessionProfilerEnabled; @@ -43,6 +44,8 @@ options:(nullable NSDictionary *)options; - (void)setPrimaryColor:(UIColor *)color; +- (void)setTheme:(NSDictionary *)themeConfig; + - (void)appendTags:(NSArray *)tags; - (void)resetTags; diff --git a/ios/RNInstabug/InstabugReactBridge.m b/ios/RNInstabug/InstabugReactBridge.m index 682896515..fe9e11e8a 100644 --- a/ios/RNInstabug/InstabugReactBridge.m +++ b/ios/RNInstabug/InstabugReactBridge.m @@ -42,8 +42,15 @@ - (dispatch_queue_t)methodQueue { debugLogsLevel:(IBGSDKDebugLogsLevel)sdkDebugLogsLevel useNativeNetworkInterception:(BOOL)useNativeNetworkInterception codePushVersion:(NSString *)codePushVersion - options:(nullable NSDictionary *)options - ) { + appVariant:(NSString *)appVariant + options:(nullable NSDictionary *)options + + ) { + + if(appVariant != nil){ + Instabug.appVariant = appVariant; + } + IBGInvocationEvent invocationEvents = 0; for (NSNumber *boxedValue in invocationEventsArray) { @@ -62,10 +69,14 @@ - (dispatch_queue_t)methodQueue { [Instabug setCodePushVersion:version]; } +RCT_EXPORT_METHOD(setAppVariant:(NSString *)appVariant) { + Instabug.appVariant = appVariant; +} + RCT_EXPORT_METHOD(setReproStepsConfig:(IBGUserStepsMode)bugMode :(IBGUserStepsMode)crashMode:(IBGUserStepsMode)sessionReplayMode) { [Instabug setReproStepsFor:IBGIssueTypeBug withMode:bugMode]; - [Instabug setReproStepsFor:IBGIssueTypeCrash withMode:crashMode]; - [Instabug setReproStepsFor:IBGIssueTypeSessionReplay withMode:sessionReplayMode]; + [Instabug setReproStepsFor:IBGIssueTypeAllCrashes withMode:crashMode]; + [Instabug setReproStepsFor:IBGIssueTypeSessionReplay withMode:sessionReplayMode]; } RCT_EXPORT_METHOD(setFileAttachment:(NSString *)fileLocation) { @@ -171,10 +182,88 @@ - (dispatch_queue_t)methodQueue { [Instabug setColorTheme:colorTheme]; } -RCT_EXPORT_METHOD(setPrimaryColor:(UIColor *)color) { - Instabug.tintColor = color; + +RCT_EXPORT_METHOD(setTheme:(NSDictionary *)themeConfig) { + IBGTheme *theme = [[IBGTheme alloc] init]; + + NSDictionary *colorMapping = @{ + @"primaryColor": ^(UIColor *color) { theme.primaryColor = color; }, + @"backgroundColor": ^(UIColor *color) { theme.backgroundColor = color; }, + @"titleTextColor": ^(UIColor *color) { theme.titleTextColor = color; }, + @"subtitleTextColor": ^(UIColor *color) { theme.subtitleTextColor = color; }, + @"primaryTextColor": ^(UIColor *color) { theme.primaryTextColor = color; }, + @"secondaryTextColor": ^(UIColor *color) { theme.secondaryTextColor = color; }, + @"callToActionTextColor": ^(UIColor *color) { theme.callToActionTextColor = color; }, + @"headerBackgroundColor": ^(UIColor *color) { theme.headerBackgroundColor = color; }, + @"footerBackgroundColor": ^(UIColor *color) { theme.footerBackgroundColor = color; }, + @"rowBackgroundColor": ^(UIColor *color) { theme.rowBackgroundColor = color; }, + @"selectedRowBackgroundColor": ^(UIColor *color) { theme.selectedRowBackgroundColor = color; }, + @"rowSeparatorColor": ^(UIColor *color) { theme.rowSeparatorColor = color; } + }; + + for (NSString *key in colorMapping) { + if (themeConfig[key]) { + NSString *colorString = themeConfig[key]; + UIColor *color = [self colorFromHexString:colorString]; + if (color) { + void (^setter)(UIColor *) = colorMapping[key]; + setter(color); + } + } + } + + [self setFontIfPresent:themeConfig[@"primaryFontPath"] forTheme:theme type:@"primary"]; + [self setFontIfPresent:themeConfig[@"secondaryFontPath"] forTheme:theme type:@"secondary"]; + [self setFontIfPresent:themeConfig[@"ctaFontPath"] forTheme:theme type:@"cta"]; + + Instabug.theme = theme; +} + +- (void)setFontIfPresent:(NSString *)fontPath forTheme:(IBGTheme *)theme type:(NSString *)type { + if (fontPath) { + NSString *fileName = [fontPath lastPathComponent]; + NSString *nameWithoutExtension = [fileName stringByDeletingPathExtension]; + UIFont *font = [UIFont fontWithName:nameWithoutExtension size:17.0]; + if (font) { + if ([type isEqualToString:@"primary"]) { + theme.primaryTextFont = font; + } else if ([type isEqualToString:@"secondary"]) { + theme.secondaryTextFont = font; + } else if ([type isEqualToString:@"cta"]) { + theme.callToActionTextFont = font; + } + } + } +} + +- (UIColor *)colorFromHexString:(NSString *)hexString { + NSString *cleanString = [hexString stringByReplacingOccurrencesOfString:@"#" withString:@""]; + + if (cleanString.length == 6) { + unsigned int rgbValue = 0; + NSScanner *scanner = [NSScanner scannerWithString:cleanString]; + [scanner scanHexInt:&rgbValue]; + + return [UIColor colorWithRed:((rgbValue & 0xFF0000) >> 16) / 255.0 + green:((rgbValue & 0xFF00) >> 8) / 255.0 + blue:(rgbValue & 0xFF) / 255.0 + alpha:1.0]; + } else if (cleanString.length == 8) { + unsigned int rgbaValue = 0; + NSScanner *scanner = [NSScanner scannerWithString:cleanString]; + [scanner scanHexInt:&rgbaValue]; + + return [UIColor colorWithRed:((rgbaValue & 0xFF000000) >> 24) / 255.0 + green:((rgbaValue & 0xFF0000) >> 16) / 255.0 + blue:((rgbaValue & 0xFF00) >> 8) / 255.0 + alpha:(rgbaValue & 0xFF) / 255.0]; + } + + return [UIColor blackColor]; } + + RCT_EXPORT_METHOD(appendTags:(NSArray *)tags) { [Instabug appendTags:tags]; } @@ -357,18 +446,6 @@ - (dispatch_queue_t)methodQueue { } } -RCT_EXPORT_METHOD(addExperiments:(NSArray *)experiments) { - [Instabug addExperiments:experiments]; -} - -RCT_EXPORT_METHOD(removeExperiments:(NSArray *)experiments) { - [Instabug removeExperiments:experiments]; -} - -RCT_EXPORT_METHOD(clearAllExperiments) { - [Instabug clearAllExperiments]; -} - RCT_EXPORT_METHOD(addFeatureFlags:(NSDictionary *)featureFlagsMap) { NSMutableArray *featureFlags = [NSMutableArray array]; for(id key in featureFlagsMap){ diff --git a/ios/native.rb b/ios/native.rb index 41f497687..6c1848522 100644 --- a/ios/native.rb +++ b/ios/native.rb @@ -1,4 +1,4 @@ -$instabug = { :version => '15.1.1' } +$instabug = { :version => '16.0.1' } def use_instabug! (spec = nil) version = $instabug[:version] diff --git a/ios/sourcemaps.sh b/ios/sourcemaps.sh index 455247c4a..6b9eee7df 100644 --- a/ios/sourcemaps.sh +++ b/ios/sourcemaps.sh @@ -1,5 +1,8 @@ #!/bin/sh + +export SOURCEMAP_FILE="$DERIVED_FILE_DIR/main.jsbundle.map" + main() { # Read environment variables from ios/.xcode.env if it exists env_path="$PODS_ROOT/../.xcode.env" @@ -28,7 +31,14 @@ main() { exit 0 fi - local source_map_file=$(generate_sourcemaps | tail -n 1) + +local sourcemap_file="" + # Use existing sourcemap if available + if [[ -f "$SOURCEMAP_FILE" ]]; then + sourcemap_file="$SOURCEMAP_FILE" + else + sourcemap_file=$(generate_sourcemaps | tail -n 1) +fi local js_project_dir="$PROJECT_DIR/.." local instabug_dir=$(dirname $(node -p "require.resolve('instabug-reactnative/package.json')")) @@ -41,12 +51,14 @@ main() { local inferred_code=$(/usr/libexec/PlistBuddy -c 'print CFBundleVersion' "$PROJECT_DIR/$INFOPLIST_FILE") local version_code=$(resolve_var "Version Code" "INSTABUG_APP_VERSION_CODE" "$inferred_code" | tail -n 1) +if [ -n "$sourcemap_file" ]; then node $instabug_dir/bin/index.js upload-sourcemaps \ --platform ios \ - --file $source_map_file \ + --file $sourcemap_file \ --token $app_token \ --name $version_name \ --code $version_code + fi } generate_sourcemaps() { diff --git a/package.json b/package.json index 61af51056..ed64a12c7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "instabug-reactnative", "description": "React Native plugin for integrating the Instabug SDK", - "version": "15.0.2", + "version": "16.0.0", "author": "Instabug (https://instabug.com)", "repository": "github:Instabug/Instabug-React-Native", "homepage": "https://www.instabug.com/platforms/react-native", diff --git a/src/index.ts b/src/index.ts index 6e7de0284..0dcb8cafa 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,7 @@ // Models import type { InstabugConfig } from './models/InstabugConfig'; import Report from './models/Report'; -import Trace from './models/Trace'; +import type { ThemeConfig } from './models/ThemeConfig'; // Modules import * as APM from './modules/APM'; import * as BugReporting from './modules/BugReporting'; @@ -19,7 +19,6 @@ import type { SessionMetadata } from './models/SessionMetadata'; export * from './utils/Enums'; export { Report, - Trace, APM, BugReporting, CrashReporting, @@ -29,6 +28,13 @@ export { Replies, Surveys, }; -export type { InstabugConfig, Survey, NetworkData, NetworkDataObfuscationHandler, SessionMetadata }; +export type { + InstabugConfig, + Survey, + NetworkData, + NetworkDataObfuscationHandler, + SessionMetadata, + ThemeConfig, +}; export default Instabug; diff --git a/src/models/InstabugConfig.ts b/src/models/InstabugConfig.ts index af1d6e841..bc3d5c747 100644 --- a/src/models/InstabugConfig.ts +++ b/src/models/InstabugConfig.ts @@ -24,6 +24,11 @@ export interface InstabugConfig { */ ignoreAndroidSecureFlag?: boolean; + /** + * An optional current App variant to be used for filtering data. + */ + appVariant?: string; + /** * An optional network interception mode, this determines whether network interception * is done in the JavaScript side or in the native Android and iOS SDK side. diff --git a/src/models/ThemeConfig.ts b/src/models/ThemeConfig.ts new file mode 100644 index 000000000..fb90347c9 --- /dev/null +++ b/src/models/ThemeConfig.ts @@ -0,0 +1,34 @@ +export type ThemeConfig = { + // Colors + primaryColor?: string; + backgroundColor?: string; + titleTextColor?: string; + subtitleTextColor?: string; + primaryTextColor?: string; + secondaryTextColor?: string; + callToActionTextColor?: string; + headerBackgroundColor?: string; + footerBackgroundColor?: string; + rowBackgroundColor?: string; + selectedRowBackgroundColor?: string; + rowSeparatorColor?: string; + + // Text Styles (Android only) + primaryTextStyle?: 'bold' | 'italic' | 'normal'; + secondaryTextStyle?: 'bold' | 'italic' | 'normal'; + titleTextStyle?: 'bold' | 'italic' | 'normal'; + ctaTextStyle?: 'bold' | 'italic' | 'normal'; + + // Fonts + primaryFontPath?: string; + primaryFontAsset?: string; + secondaryFontPath?: string; + secondaryFontAsset?: string; + ctaFontPath?: string; + ctaFontAsset?: string; + + // Legacy properties (deprecated) + primaryTextType?: string; + secondaryTextType?: string; + ctaTextType?: string; +}; diff --git a/src/models/Trace.ts b/src/models/Trace.ts deleted file mode 100644 index 19cd26d58..000000000 --- a/src/models/Trace.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { NativeAPM } from '../native/NativeAPM'; -import type * as APM from '../modules/APM'; - -export default class Trace { - constructor( - public readonly id: string, - public readonly name: string = '', - public readonly attributes: Record = {}, - ) {} - - /** - * Adds an attribute with a specified key and value to the Trace to be sent. - * - * @param key - The key of the attribute. - * @param value - The value of the attribute. - * - * @deprecated Please migrate to the App Flows APIs: {@link APM.startFlow}, {@link APM.endFlow}, and {@link APM.setFlowAttribute}. - */ - setAttribute(key: string, value: string) { - NativeAPM.setExecutionTraceAttribute(this.id, key, value); - this.attributes[key] = value; - } - - /** - * Ends the execution trace. - * - * @deprecated Please migrate to the App Flows APIs: {@link APM.startFlow}, {@link APM.endFlow}, and {@link APM.setFlowAttribute}. - */ - - end() { - NativeAPM.endExecutionTrace(this.id); - } -} diff --git a/src/modules/APM.ts b/src/modules/APM.ts index 92d401389..9f2dcbe01 100644 --- a/src/modules/APM.ts +++ b/src/modules/APM.ts @@ -1,6 +1,5 @@ import { Platform } from 'react-native'; -import Trace from '../models/Trace'; import { NativeAPM } from '../native/NativeAPM'; import { NativeInstabug } from '../native/NativeInstabug'; @@ -48,29 +47,6 @@ export const setAutoUITraceEnabled = (isEnabled: boolean) => { NativeAPM.setAutoUITraceEnabled(isEnabled); }; -/** - * Starts a custom execution trace. - * - * Returns a promise which resolves with the trace reference if APM is enabled; otherwise, the promise is rejected. - * - * @param name - The name of the trace to start. - * @returns A promise that resolves with a Trace object. - * - * @deprecated Please migrate to the App Flows APIs: {@link startFlow}, {@link endFlow}, and {@link setFlowAttribute}. - */ -export const startExecutionTrace = async (name: string): Promise => { - const TRACE_NOT_STARTED_APM_NOT_ENABLED = `Execution trace "${name}" wasn't created. Please make sure to enable APM first by following the instructions at this link: https://docs.instabug.com/reference#enable-or-disable-apm`; - const timestamp = Date.now() + ''; - - const id = await NativeAPM.startExecutionTrace(name, timestamp); - - if (!id) { - throw new Error(TRACE_NOT_STARTED_APM_NOT_ENABLED); - } - - return new Trace(id, name); -}; - /** * Starts an AppFlow with the specified name. * diff --git a/src/modules/BugReporting.ts b/src/modules/BugReporting.ts index 486169ecc..c514e1ba9 100644 --- a/src/modules/BugReporting.ts +++ b/src/modules/BugReporting.ts @@ -245,7 +245,10 @@ export const setDisclaimerText = (text: string) => { * Sets a minimum number of characters as a requirement for the comments field in the different report types. * @param limit int number of characters. * @param reportTypes (Optional) Array of reportType. If it's not passed, the limit will apply to all report types. + * @platform iOS */ export const setCommentMinimumCharacterCount = (limit: number, reportTypes?: ReportType[]) => { - NativeBugReporting.setCommentMinimumCharacterCount(limit, reportTypes ?? []); + if (Platform.OS === 'ios') { + NativeBugReporting.setCommentMinimumCharacterCount(limit, reportTypes ?? []); + } }; diff --git a/src/modules/Instabug.ts b/src/modules/Instabug.ts index fd4f17600..96177cd92 100644 --- a/src/modules/Instabug.ts +++ b/src/modules/Instabug.ts @@ -1,10 +1,4 @@ -import { - AppState, - type AppStateStatus, - findNodeHandle, - Platform, - processColor, -} from 'react-native'; +import { AppState, type AppStateStatus, findNodeHandle, Platform } from 'react-native'; import type { NavigationContainerRefWithCurrent, @@ -42,6 +36,7 @@ import { NativeNetworkLogger } from '../native/NativeNetworkLogger'; import InstabugConstants from '../utils/InstabugConstants'; import { InstabugRNConfig } from '../utils/config'; import { Logger } from '../utils/logger'; +import type { ThemeConfig } from '../models/ThemeConfig'; let _currentScreen: string | null = null; let _lastScreen: string | null = null; @@ -126,6 +121,14 @@ export const init = (config: InstabugConfig) => { }, 1000); }; +/** + * Set Current App Variant. + * @param appVariant the current App variant name + */ +export const setAppVariant = (appVariant: string) => { + NativeInstabug.setAppVariant(appVariant); +}; + /** * Handles app state changes and updates APM network flags if necessary. */ @@ -273,6 +276,7 @@ const initializeNativeInstabug = (config: InstabugConfig) => { shouldEnableNativeInterception && config.networkInterceptionMode === NetworkInterceptionMode.native, config.codePushVersion, + config.appVariant, config.ignoreAndroidSecureFlag != null ? { ignoreAndroidSecureFlag: config.ignoreAndroidSecureFlag, @@ -392,9 +396,10 @@ export const setColorTheme = (sdkTheme: ColorTheme) => { * To use, import processColor and pass to it with argument the color hex * as argument. * @param color A color to set the UI elements of the SDK to. + * @deprecated Please migrate to the new UI customization API: {@link setTheme} */ export const setPrimaryColor = (color: string) => { - NativeInstabug.setPrimaryColor(processColor(color)); + NativeInstabug.setTheme({ primaryColor: color }); }; /** @@ -778,35 +783,6 @@ export const reportScreenChange = (screenName: string) => { NativeInstabug.reportScreenChange(screenName); }; -/** - * Add experiments to next report. - * @param experiments An array of experiments to add to the next report. - * - * @deprecated Please migrate to the new Feature Flags APIs: {@link addFeatureFlags}. - */ -export const addExperiments = (experiments: string[]) => { - NativeInstabug.addExperiments(experiments); -}; - -/** - * Remove experiments from next report. - * @param experiments An array of experiments to remove from the next report. - * - * @deprecated Please migrate to the new Feature Flags APIs: {@link removeFeatureFlags}. - */ -export const removeExperiments = (experiments: string[]) => { - NativeInstabug.removeExperiments(experiments); -}; - -/** - * Clear all experiments - * - * @deprecated Please migrate to the new Feature Flags APIs: {@link removeAllFeatureFlags}. - */ -export const clearAllExperiments = () => { - NativeInstabug.clearAllExperiments(); -}; - /** * Add feature flags to the next report. * @param featureFlags An array of feature flags to add to the next report. @@ -898,3 +874,50 @@ export const _registerFeatureFlagsChangeListener = ( export const enableAutoMasking = (autoMaskingTypes: AutoMaskingType[]) => { NativeInstabug.enableAutoMasking(autoMaskingTypes); }; + +/** + * Sets a custom theme for Instabug UI elements. + * + * This method provides comprehensive theming support. It will automatically use IBGTheme + * if available in the SDK version, otherwise falls back to individual theming methods. + * + * @param theme - Configuration object containing theme properties + * + * @example + * ```typescript + * // Basic usage with primary color (always supported) + * Instabug.setTheme({ + * primaryColor: '#FF6B6B' + * }); + * + * // Comprehensive theming (uses IBGTheme when available) + * Instabug.setTheme({ + * primaryColor: '#FF6B6B', + * secondaryTextColor: '#666666', + * primaryTextColor: '#333333', + * titleTextColor: '#000000', + * backgroundColor: '#FFFFFF', + * primaryTextStyle: 'bold', + * secondaryTextStyle: 'normal', + * titleTextStyle: 'bold', + * ctaTextStyle: 'bold', + * primaryFontPath: '/data/user/0/com.yourapp/files/fonts/YourFont.ttf', + * secondaryFontPath: '/data/user/0/com.yourapp/files/fonts/YourFont.ttf', + * ctaTextType: '/data/user/0/com.yourapp/files/fonts/YourFont.ttf', + * primaryFontAsset: 'fonts/YourFont.ttf', + * secondaryFontAsset: 'fonts/YourFont.ttf' + * }); + * ``` + */ +export const setTheme = (theme: ThemeConfig) => { + NativeInstabug.setTheme(theme); +}; +/** + * Enables or disables displaying in full-screen mode, hiding the status and navigation bars. + * @param isEnabled A boolean to enable/disable setFullscreen. + */ +export const setFullscreen = (isEnabled: boolean) => { + if (Platform.OS === 'android') { + NativeInstabug.setFullscreen(isEnabled); + } +}; diff --git a/src/native/NativeAPM.ts b/src/native/NativeAPM.ts index 9fa30b702..86d017167 100644 --- a/src/native/NativeAPM.ts +++ b/src/native/NativeAPM.ts @@ -34,10 +34,6 @@ export interface ApmNativeModule extends NativeModule { endAppLaunch(): void; // Execution Traces APIs // - startExecutionTrace(name: string, timestamp: string): Promise; - setExecutionTraceAttribute(id: string, key: string, value: string): void; - endExecutionTrace(id: string): void; - // App Flows APIs // startFlow(name: string): void; endFlow(name: string): void; diff --git a/src/native/NativeInstabug.ts b/src/native/NativeInstabug.ts index c9c078f37..c350d705f 100644 --- a/src/native/NativeInstabug.ts +++ b/src/native/NativeInstabug.ts @@ -14,6 +14,7 @@ import type { import type { NativeConstants } from './NativeConstants'; import type { W3cExternalTraceAttributes } from '../models/W3cExternalTraceAttributes'; import { NativeModules } from './NativePackage'; +import type { ThemeConfig } from '../models/ThemeConfig'; export interface InstabugNativeModule extends NativeModule { getConstants(): NativeConstants; @@ -26,6 +27,7 @@ export interface InstabugNativeModule extends NativeModule { debugLogsLevel: LogLevel, useNativeNetworkInterception: boolean, codePushVersion?: string, + appVariant?: string, options?: { ignoreAndroidSecureFlag?: boolean; }, @@ -34,6 +36,7 @@ export interface InstabugNativeModule extends NativeModule { // Misc APIs // setCodePushVersion(version: string): void; + setAppVariant(appVariant: string): void; setIBGLogPrintsToConsole(printsToConsole: boolean): void; setSessionProfilerEnabled(isEnabled: boolean): void; @@ -121,10 +124,6 @@ export interface InstabugNativeModule extends NativeModule { getTags(): Promise; // Experiments APIs // - addExperiments(experiments: string[]): void; - removeExperiments(experiments: string[]): void; - clearAllExperiments(): void; - addFeatureFlags(featureFlags: Record): void; removeFeatureFlags(featureFlags: string[]): void; @@ -161,9 +160,12 @@ export interface InstabugNativeModule extends NativeModule { setOnFeaturesUpdatedListener(handler?: (params: any) => void): void; // android only enableAutoMasking(autoMaskingTypes: AutoMaskingType[]): void; getNetworkBodyMaxSize(): Promise; + + setTheme(theme: ThemeConfig): void; + setFullscreen(isEnabled: boolean): void; } -export const NativeInstabug = NativeModules.Instabug; +export const NativeInstabug = NativeModules.Instabug as InstabugNativeModule; export enum NativeEvents { PRESENDING_HANDLER = 'IBGpreSendingHandler', diff --git a/test/mocks/mockAPM.ts b/test/mocks/mockAPM.ts index 27644c694..7a9c5bac9 100644 --- a/test/mocks/mockAPM.ts +++ b/test/mocks/mockAPM.ts @@ -6,9 +6,6 @@ const mockAPM: ApmNativeModule = { setEnabled: jest.fn(), setAppLaunchEnabled: jest.fn(), setAutoUITraceEnabled: jest.fn(), - startExecutionTrace: jest.fn(), - setExecutionTraceAttribute: jest.fn(), - endExecutionTrace: jest.fn(), startFlow: jest.fn(), setFlowAttribute: jest.fn(), endFlow: jest.fn(), diff --git a/test/mocks/mockInstabug.ts b/test/mocks/mockInstabug.ts index 391a00a38..e6f41fc73 100644 --- a/test/mocks/mockInstabug.ts +++ b/test/mocks/mockInstabug.ts @@ -42,6 +42,7 @@ const mockInstabug: InstabugNativeModule = { clearAllUserAttributes: jest.fn(), showWelcomeMessageWithMode: jest.fn(), setWelcomeMessageMode: jest.fn(), + setAppVariant: jest.fn(), setFileAttachment: jest.fn(), addPrivateView: jest.fn(), removePrivateView: jest.fn(), @@ -49,9 +50,6 @@ const mockInstabug: InstabugNativeModule = { setPreSendingHandler: jest.fn(), reportScreenChange: jest.fn(), reportCurrentViewChange: jest.fn(), - addExperiments: jest.fn(), - removeExperiments: jest.fn(), - clearAllExperiments: jest.fn(), networkLogIOS: jest.fn(), networkLogAndroid: jest.fn(), addFeatureFlags: jest.fn(), @@ -77,6 +75,8 @@ const mockInstabug: InstabugNativeModule = { setOnFeaturesUpdatedListener: jest.fn(), enableAutoMasking: jest.fn(), getNetworkBodyMaxSize: jest.fn().mockResolvedValue(10240), // 10 KB + setTheme: jest.fn(), + setFullscreen: jest.fn(), }; export default mockInstabug; diff --git a/test/models/Trace.spec.ts b/test/models/Trace.spec.ts deleted file mode 100644 index 8421b419a..000000000 --- a/test/models/Trace.spec.ts +++ /dev/null @@ -1,39 +0,0 @@ -import Trace from '../../src/models/Trace'; -import { NativeAPM } from '../../src/native/NativeAPM'; - -describe('Trace Model', () => { - it('should set the id, name and attributes if passed', () => { - const id = 'trace-id'; - const name = 'my-trace'; - const attributes = { screen: 'login' }; - const trace = new Trace(id, name, attributes); - - expect(trace.id).toBe(id); - expect(trace.name).toBe(name); - expect(trace.attributes).toBe(attributes); - }); - - it('should set execution trace attributes', () => { - const attribute = { key: 'isAuthenticated', value: 'yes' }; - - const trace = new Trace('trace-id'); - trace.setAttribute(attribute.key, attribute.value); - - expect(trace.attributes[attribute.key]).toBe(attribute.value); - expect(NativeAPM.setExecutionTraceAttribute).toBeCalledTimes(1); - expect(NativeAPM.setExecutionTraceAttribute).toBeCalledWith( - trace.id, - attribute.key, - attribute.value, - ); - }); - - it('should end execution trace', () => { - const trace = new Trace('trace-id'); - - trace.end(); - - expect(NativeAPM.endExecutionTrace).toBeCalledTimes(1); - expect(NativeAPM.endExecutionTrace).toBeCalledWith(trace.id); - }); -}); diff --git a/test/modules/APM.spec.ts b/test/modules/APM.spec.ts index cf932d25c..ea703d3ea 100644 --- a/test/modules/APM.spec.ts +++ b/test/modules/APM.spec.ts @@ -1,11 +1,8 @@ import { Platform } from 'react-native'; -import { mocked } from 'jest-mock'; - -import Trace from '../../src/models/Trace'; -import * as APM from '../../src/modules/APM'; import { NativeAPM } from '../../src/native/NativeAPM'; import { NativeInstabug } from '../../src/native/NativeInstabug'; +import * as APM from '../../src/modules/APM'; describe('APM Module', () => { it('should call the native method setEnabled', () => { @@ -51,57 +48,6 @@ describe('APM Module', () => { expect(NativeAPM.setAutoUITraceEnabled).toBeCalledWith(true); }); - it('should call the native method startExecutionTrace', () => { - mocked(NativeAPM).startExecutionTrace.mockResolvedValueOnce('trace-id'); - - APM.startExecutionTrace('trace'); - - expect(NativeAPM.startExecutionTrace).toBeCalledTimes(1); - expect(NativeAPM.startExecutionTrace).toBeCalledWith('trace', expect.any(String)); - }); - - it("should throw an error if native startExecutionTrace didn't return an ID", async () => { - mocked(NativeAPM).startExecutionTrace.mockResolvedValueOnce(null); - const promise = APM.startExecutionTrace('trace'); - - await expect(promise).rejects.toThrowError(/trace "trace" wasn't created/i); - }); - - it('should resolve with an Trace object if native startExecutionTrace returned an ID', async () => { - mocked(NativeAPM).startExecutionTrace.mockResolvedValueOnce('trace-id'); - - const promise = APM.startExecutionTrace('trace'); - - await expect(promise).resolves.toBeInstanceOf(Trace); - await expect(promise).resolves.toHaveProperty('name', 'trace'); - }); - - it('should call the native method setExecutionTraceAttribute', () => { - mocked(NativeAPM).startExecutionTrace.mockResolvedValueOnce('trace-id'); - - APM.startExecutionTrace('trace').then((trace) => { - trace.setAttribute('key', 'value'); - - expect(NativeAPM.setExecutionTraceAttribute).toBeCalledTimes(1); - expect(NativeAPM.setExecutionTraceAttribute).toBeCalledWith( - expect.any(String), - 'key', - 'value', - ); - }); - }); - - it('should call the native method endExecutionTrace', () => { - mocked(NativeAPM).startExecutionTrace.mockResolvedValueOnce('trace-id'); - - APM.startExecutionTrace('trace').then((trace) => { - trace.end(); - - expect(NativeAPM.endExecutionTrace).toBeCalledTimes(1); - expect(NativeAPM.endExecutionTrace).toBeCalledWith(expect.any(String)); - }); - }); - it('should call the native method startFlow', () => { const appFlowName = 'flowName'; diff --git a/test/modules/Instabug.spec.ts b/test/modules/Instabug.spec.ts index eeec8e8e4..bd21b3efc 100644 --- a/test/modules/Instabug.spec.ts +++ b/test/modules/Instabug.spec.ts @@ -1,7 +1,7 @@ import '../mocks/mockInstabugUtils'; import '../mocks/mockNetworkLogger'; -import { findNodeHandle, Platform, processColor } from 'react-native'; +import { findNodeHandle, Platform } from 'react-native'; import type { NavigationContainerRefWithCurrent } from '@react-navigation/native'; // Import the hook import { mocked } from 'jest-mock'; import waitForExpect from 'wait-for-expect'; @@ -308,6 +308,7 @@ describe('Instabug Module', () => { instabugConfig.debugLogsLevel, usesNativeNetworkInterception, instabugConfig.codePushVersion, + undefined, { ignoreAndroidSecureFlag: instabugConfig.ignoreAndroidSecureFlag }, ); }); @@ -359,6 +360,7 @@ describe('Instabug Module', () => { // usesNativeNetworkInterception should be true when using native interception mode with iOS true, instabugConfig.codePushVersion, + undefined, { ignoreAndroidSecureFlag: instabugConfig.ignoreAndroidSecureFlag }, ); } @@ -462,12 +464,13 @@ describe('Instabug Module', () => { expect(NativeInstabug.setColorTheme).toBeCalledWith(theme); }); - it('should call the native method setPrimaryColor', () => { + it('should call the native method setPrimaryColor on iOS', () => { + Platform.OS = 'ios'; const color = '#fff'; - Instabug.setPrimaryColor(color); + Instabug.setTheme({ primaryColor: color }); - expect(NativeInstabug.setPrimaryColor).toBeCalledTimes(1); - expect(NativeInstabug.setPrimaryColor).toBeCalledWith(processColor(color)); + expect(NativeInstabug.setTheme).toBeCalledTimes(1); + expect(NativeInstabug.setTheme).toBeCalledWith({ primaryColor: color }); }); it('should call the native method appendTags', () => { @@ -832,25 +835,6 @@ describe('Instabug Module', () => { expect(emitter.listenerCount(NativeEvents.PRESENDING_HANDLER)).toBe(1); }); - it('should call native addExperiments method', () => { - const experiments = ['exp1', 'exp2']; - Instabug.addExperiments(experiments); - expect(NativeInstabug.addExperiments).toBeCalledTimes(1); - expect(NativeInstabug.addExperiments).toBeCalledWith(experiments); - }); - - it('should call native removeExperiments method', () => { - const experiments = ['exp1', 'exp2']; - Instabug.removeExperiments(experiments); - expect(NativeInstabug.removeExperiments).toBeCalledTimes(1); - expect(NativeInstabug.removeExperiments).toBeCalledWith(experiments); - }); - - it('should call native clearAllExperiments method', () => { - Instabug.clearAllExperiments(); - expect(NativeInstabug.clearAllExperiments).toBeCalledTimes(1); - }); - it('should call native addFeatureFlags method', () => { const featureFlags: Array = [ { @@ -960,6 +944,7 @@ describe('Instabug iOS initialization tests', () => { false, // Disable native interception config.codePushVersion, config.ignoreAndroidSecureFlag, + undefined, ); }); @@ -977,6 +962,7 @@ describe('Instabug iOS initialization tests', () => { true, // Enable native interception config.codePushVersion, config.ignoreAndroidSecureFlag, + undefined, ); }); @@ -994,6 +980,7 @@ describe('Instabug iOS initialization tests', () => { false, // Disable native interception config.codePushVersion, config.ignoreAndroidSecureFlag, + undefined, ); }); @@ -1036,6 +1023,7 @@ describe('Instabug Android initialization tests', () => { config.debugLogsLevel, false, // always disable native interception to insure sending network logs to core (Bugs & Crashes). config.codePushVersion, + undefined, { ignoreAndroidSecureFlag: config.ignoreAndroidSecureFlag }, ); }); @@ -1104,4 +1092,21 @@ describe('Instabug Android initialization tests', () => { ); }); }); + + it('should initialize correctly with App variant', async () => { + config.appVariant = 'App Variant'; + await Instabug.init(config); + fakeTimer(() => { + expect(NativeInstabug.setOnFeaturesUpdatedListener).toHaveBeenCalled(); + expect(NativeInstabug.init).toHaveBeenCalledWith( + config.token, + config.invocationEvents, + config.debugLogsLevel, + true, + config.codePushVersion, + config.appVariant, + undefined, + ); + }); + }); });