();
@Nonnull
@Override
@@ -207,81 +202,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
*
@@ -318,73 +238,73 @@ public void run() {
});
}
- /**
- * The `networkLogAndroid` function logs network-related information using APMNetworkLogger in a React
- * Native module.
- *
- * @param requestStartTime The `requestStartTime` parameter in the `networkLogAndroid` method
- * represents the timestamp when the network request started. It is of type `double` and is passed as
- * a parameter to log network-related information.
- * @param requestDuration The `requestDuration` parameter in the `networkLogAndroid` method represents
- * the duration of the network request in milliseconds. It indicates the time taken for the request to
- * complete from the moment it was initiated until the response was received. This parameter helps in
- * measuring the performance of network requests and identifying any potential
- * @param requestHeaders requestHeaders is a string parameter that contains the headers of the network
- * request. It typically includes information such as the content type, authorization token, and any
- * other headers that were sent with the request.
- * @param requestBody The `requestBody` parameter in the `networkLogAndroid` method represents the
- * body of the HTTP request being logged. It contains the data that is sent as part of the request to
- * the server. This could include form data, JSON payload, XML data, or any other content that is
- * being transmitted
- * @param requestBodySize The `requestBodySize` parameter in the `networkLogAndroid` method represents
- * the size of the request body in bytes. It is a double value that indicates the size of the request
- * body being sent in the network request. This parameter is used to log information related to the
- * network request, including details
- * @param requestMethod The `requestMethod` parameter in the `networkLogAndroid` method represents the
- * HTTP method used in the network request, such as GET, POST, PUT, DELETE, etc. It indicates the type
- * of operation that the client is requesting from the server.
- * @param requestUrl The `requestUrl` parameter in the `networkLogAndroid` method represents the URL
- * of the network request being logged. It typically contains the address of the server to which the
- * request is being made, along with any additional path or query parameters required for the request.
- * This URL is essential for identifying the
- * @param requestContentType The `requestContentType` parameter in the `networkLogAndroid` method
- * represents the content type of the request being made. This could be values like
- * "application/json", "application/xml", "text/plain", etc., indicating the format of the data being
- * sent in the request body. It helps in specifying
- * @param responseHeaders The `responseHeaders` parameter in the `networkLogAndroid` method represents
- * the headers of the response received from a network request. These headers typically include
- * information such as content type, content length, server information, and any other metadata
- * related to the response. The `responseHeaders` parameter is expected to
- * @param responseBody The `responseBody` parameter in the `networkLogAndroid` method represents the
- * body of the response received from a network request. It contains the data or content sent back by
- * the server in response to the request made by the client. This could be in various formats such as
- * JSON, XML, HTML
- * @param responseBodySize The `responseBodySize` parameter in the `networkLogAndroid` method
- * represents the size of the response body in bytes. It is a double value that indicates the size of
- * the response body received from the network request. This parameter is used to log information
- * related to the network request and response, including
- * @param statusCode The `statusCode` parameter in the `networkLogAndroid` method represents the HTTP
- * status code of the network request/response. It indicates the status of the HTTP response, such as
- * success (200), redirection (3xx), client errors (4xx), or server errors (5xx). This parameter is
- * @param responseContentType The `responseContentType` parameter in the `networkLogAndroid` method
- * represents the content type of the response received from the network request. It indicates the
- * format of the data in the response, such as JSON, XML, HTML, etc. This information is useful for
- * understanding how to parse and handle the
- * @param errorDomain The `errorDomain` parameter in the `networkLogAndroid` method is used to specify
- * the domain of an error, if any occurred during the network request. If there was no error, this
- * parameter will be `null`.
- * @param w3cAttributes The `w3cAttributes` parameter in the `networkLogAndroid` method is a
- * ReadableMap object that contains additional attributes related to W3C external trace. It may
- * include the following key-value pairs:
- * @param gqlQueryName The `gqlQueryName` parameter in the `networkLogAndroid` method represents the
- * name of the GraphQL query being executed. It is a nullable parameter, meaning it can be null if no
- * GraphQL query name is provided. This parameter is used to log information related to GraphQL
- * queries in the network logging
- * @param serverErrorMessage The `serverErrorMessage` parameter in the `networkLogAndroid` method is
- * used to pass any error message received from the server during network communication. This message
- * can provide additional details about any errors that occurred on the server side, helping in
- * debugging and troubleshooting network-related issues.
- */
+ /**
+ * The `networkLogAndroid` function logs network-related information using APMNetworkLogger in a React
+ * Native module.
+ *
+ * @param requestStartTime The `requestStartTime` parameter in the `networkLogAndroid` method
+ * represents the timestamp when the network request started. It is of type `double` and is passed as
+ * a parameter to log network-related information.
+ * @param requestDuration The `requestDuration` parameter in the `networkLogAndroid` method represents
+ * the duration of the network request in milliseconds. It indicates the time taken for the request to
+ * complete from the moment it was initiated until the response was received. This parameter helps in
+ * measuring the performance of network requests and identifying any potential
+ * @param requestHeaders requestHeaders is a string parameter that contains the headers of the network
+ * request. It typically includes information such as the content type, authorization token, and any
+ * other headers that were sent with the request.
+ * @param requestBody The `requestBody` parameter in the `networkLogAndroid` method represents the
+ * body of the HTTP request being logged. It contains the data that is sent as part of the request to
+ * the server. This could include form data, JSON payload, XML data, or any other content that is
+ * being transmitted
+ * @param requestBodySize The `requestBodySize` parameter in the `networkLogAndroid` method represents
+ * the size of the request body in bytes. It is a double value that indicates the size of the request
+ * body being sent in the network request. This parameter is used to log information related to the
+ * network request, including details
+ * @param requestMethod The `requestMethod` parameter in the `networkLogAndroid` method represents the
+ * HTTP method used in the network request, such as GET, POST, PUT, DELETE, etc. It indicates the type
+ * of operation that the client is requesting from the server.
+ * @param requestUrl The `requestUrl` parameter in the `networkLogAndroid` method represents the URL
+ * of the network request being logged. It typically contains the address of the server to which the
+ * request is being made, along with any additional path or query parameters required for the request.
+ * This URL is essential for identifying the
+ * @param requestContentType The `requestContentType` parameter in the `networkLogAndroid` method
+ * represents the content type of the request being made. This could be values like
+ * "application/json", "application/xml", "text/plain", etc., indicating the format of the data being
+ * sent in the request body. It helps in specifying
+ * @param responseHeaders The `responseHeaders` parameter in the `networkLogAndroid` method represents
+ * the headers of the response received from a network request. These headers typically include
+ * information such as content type, content length, server information, and any other metadata
+ * related to the response. The `responseHeaders` parameter is expected to
+ * @param responseBody The `responseBody` parameter in the `networkLogAndroid` method represents the
+ * body of the response received from a network request. It contains the data or content sent back by
+ * the server in response to the request made by the client. This could be in various formats such as
+ * JSON, XML, HTML
+ * @param responseBodySize The `responseBodySize` parameter in the `networkLogAndroid` method
+ * represents the size of the response body in bytes. It is a double value that indicates the size of
+ * the response body received from the network request. This parameter is used to log information
+ * related to the network request and response, including
+ * @param statusCode The `statusCode` parameter in the `networkLogAndroid` method represents the HTTP
+ * status code of the network request/response. It indicates the status of the HTTP response, such as
+ * success (200), redirection (3xx), client errors (4xx), or server errors (5xx). This parameter is
+ * @param responseContentType The `responseContentType` parameter in the `networkLogAndroid` method
+ * represents the content type of the response received from the network request. It indicates the
+ * format of the data in the response, such as JSON, XML, HTML, etc. This information is useful for
+ * understanding how to parse and handle the
+ * @param errorDomain The `errorDomain` parameter in the `networkLogAndroid` method is used to specify
+ * the domain of an error, if any occurred during the network request. If there was no error, this
+ * parameter will be `null`.
+ * @param w3cAttributes The `w3cAttributes` parameter in the `networkLogAndroid` method is a
+ * ReadableMap object that contains additional attributes related to W3C external trace. It may
+ * include the following key-value pairs:
+ * @param gqlQueryName The `gqlQueryName` parameter in the `networkLogAndroid` method represents the
+ * name of the GraphQL query being executed. It is a nullable parameter, meaning it can be null if no
+ * GraphQL query name is provided. This parameter is used to log information related to GraphQL
+ * queries in the network logging
+ * @param serverErrorMessage The `serverErrorMessage` parameter in the `networkLogAndroid` method is
+ * used to pass any error message received from the server during network communication. This message
+ * can provide additional details about any errors that occurred on the server side, helping in
+ * debugging and troubleshooting network-related issues.
+ */
@ReactMethod
private void networkLogAndroid(final double requestStartTime,
final double requestDuration,
@@ -403,15 +323,15 @@ private void networkLogAndroid(final double requestStartTime,
@Nullable final ReadableMap w3cAttributes,
@Nullable final String gqlQueryName,
@Nullable final String serverErrorMessage
- ) {
+ ) {
try {
APMNetworkLogger networkLogger = new APMNetworkLogger();
final boolean hasError = errorDomain != null && !errorDomain.isEmpty();
final String errorMessage = hasError ? errorDomain : null;
- Boolean isW3cHeaderFound=false;
- Long partialId=null;
- Long networkStartTimeInSeconds=null;
+ Boolean isW3cHeaderFound = false;
+ Long partialId = null;
+ Long networkStartTimeInSeconds = null;
try {
@@ -420,7 +340,7 @@ private void networkLogAndroid(final double requestStartTime,
}
if (!w3cAttributes.isNull("partialId")) {
- partialId =(long) w3cAttributes.getDouble("partialId");
+ partialId = (long) w3cAttributes.getDouble("partialId");
networkStartTimeInSeconds = (long) w3cAttributes.getDouble("networkStartTimeInSeconds");
}
@@ -438,34 +358,54 @@ private void networkLogAndroid(final double requestStartTime,
try {
Method method = getMethod(Class.forName("com.instabug.apm.networking.APMNetworkLogger"), "log", long.class, long.class, String.class, String.class, long.class, String.class, String.class, String.class, String.class, String.class, long.class, int.class, String.class, String.class, String.class, String.class, APMCPNetworkLog.W3CExternalTraceAttributes.class);
if (method != null) {
- method.invoke(
- networkLogger,
- (long) requestStartTime * 1000,
- (long) requestDuration,
- requestHeaders,
- requestBody,
- (long) requestBodySize,
- requestMethod,
- requestUrl,
- requestContentType,
- responseHeaders,
- responseBody,
- (long)responseBodySize,
- (int) statusCode,
- responseContentType,
- errorMessage,
- gqlQueryName,
- serverErrorMessage,
- w3cExternalTraceAttributes
- );
+ method.invoke(
+ networkLogger,
+ (long) requestStartTime * 1000,
+ (long) requestDuration,
+ requestHeaders,
+ requestBody,
+ (long) requestBodySize,
+ requestMethod,
+ requestUrl,
+ requestContentType,
+ responseHeaders,
+ responseBody,
+ (long) responseBodySize,
+ (int) statusCode,
+ responseContentType,
+ errorMessage,
+ gqlQueryName,
+ serverErrorMessage,
+ w3cExternalTraceAttributes
+ );
} else {
Log.e("IB-CP-Bridge", "APMNetworkLogger.log was not found by reflection");
}
} catch (Throwable e) {
e.printStackTrace();
}
- } catch(Throwable e) {
+ } catch (Throwable e) {
e.printStackTrace();
}
}
+
+
+ /**
+ * Enables or disables screen rendering
+ *
+ * @param isEnabled boolean indicating enabled or disabled.
+ */
+ @ReactMethod
+ public void setScreenRenderEnabled(boolean isEnabled) {
+ MainThreadHandler.runOnMainThread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ APM.setScreenRenderingEnabled(isEnabled);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ });
+ }
}
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 17f48656f..ab97d4c81 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;
@@ -26,7 +30,6 @@
import com.facebook.react.bridge.WritableNativeMap;
import com.facebook.react.uimanager.UIManagerModule;
import com.instabug.apm.InternalAPM;
-import com.instabug.apm.configuration.cp.APMFeature;
import com.instabug.library.Feature;
import com.instabug.library.Instabug;
import com.instabug.library.InstabugColorTheme;
@@ -35,24 +38,22 @@
import com.instabug.library.LogLevel;
import com.instabug.library.ReproConfigurations;
import com.instabug.library.core.InstabugCore;
+import com.instabug.library.featuresflags.model.IBGFeatureFlag;
import com.instabug.library.internal.crossplatform.CoreFeature;
import com.instabug.library.internal.crossplatform.CoreFeaturesState;
import com.instabug.library.internal.crossplatform.FeaturesStateListener;
import com.instabug.library.internal.crossplatform.InternalCore;
-import com.instabug.library.featuresflags.model.IBGFeatureFlag;
-import com.instabug.library.internal.crossplatform.InternalCore;
import com.instabug.library.internal.crossplatform.OnFeaturesUpdatedListener;
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;
-import com.instabug.library.util.InstabugSDKLogger;
import com.instabug.reactlibrary.utils.ArrayUtil;
import com.instabug.reactlibrary.utils.EventEmitterModule;
import com.instabug.reactlibrary.utils.MainThreadHandler;
-
import com.instabug.reactlibrary.utils.RNTouchedViewExtractor;
import org.json.JSONException;
@@ -115,6 +116,7 @@ public void removeListeners(Integer count) {
/**
* Enables or disables Instabug functionality.
+ *
* @param isEnabled A boolean to enable/disable Instabug.
*/
@ReactMethod
@@ -281,26 +283,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.
@@ -1036,60 +1018,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) {
@@ -1175,7 +1104,7 @@ public void invoke(@NonNull CoreFeaturesState featuresState) {
params.putBoolean("isW3ExternalTraceIDEnabled", featuresState.isW3CExternalTraceIdEnabled());
params.putBoolean("isW3ExternalGeneratedHeaderEnabled", featuresState.isAttachingGeneratedHeaderEnabled());
params.putBoolean("isW3CaughtHeaderEnabled", featuresState.isAttachingCapturedHeaderEnabled());
- params.putInt("networkBodyLimit",featuresState.getNetworkLogCharLimit());
+ params.putInt("networkBodyLimit", featuresState.getNetworkLogCharLimit());
sendEvent(Constants.IBG_ON_FEATURE_FLAGS_UPDATE_RECEIVED_CALLBACK, params);
}
@@ -1259,7 +1188,7 @@ public void run() {
* Map between the exported JS constant and the arg key in {@link ArgsRegistry}.
* The constant name and the arg key should match to be able to resolve the
* constant with its actual value from the {@link ArgsRegistry} maps.
- *
+ *
* This is a workaround, because RN cannot resolve enums in the constants map.
*/
@Override
@@ -1290,23 +1219,25 @@ public void invoke() {
}
});
}
+
/**
- * Enables or disables capturing network body.
- * @param isEnabled A boolean to enable/disable capturing network body.
- */
- @ReactMethod
- public void setNetworkLogBodyEnabled(final boolean isEnabled) {
- MainThreadHandler.runOnMainThread(new Runnable() {
- @Override
- public void run() {
- try {
- Instabug.setNetworkLogBodyEnabled(isEnabled);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- });
- }
+ * Enables or disables capturing network body.
+ *
+ * @param isEnabled A boolean to enable/disable capturing network body.
+ */
+ @ReactMethod
+ public void setNetworkLogBodyEnabled(final boolean isEnabled) {
+ MainThreadHandler.runOnMainThread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ Instabug.setNetworkLogBodyEnabled(isEnabled);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ });
+ }
/**
* Sets the auto mask screenshots types.
@@ -1351,4 +1282,255 @@ public void run() {
}
});
}
+ /**
+ * 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..c4e6860c6 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() {
@@ -205,5 +166,13 @@ public void testSetFlowAttribute() {
APM.endUITrace();
}
+ @Test
+ public void given$setScreenRenderEnabled_whenQuery_thenShouldCallNativeApiWithEnabled() {
+ apmModule.setScreenRenderEnabled(true);
+ // then
+ verify(APM.class, times(1));
+ APM.setScreenRenderingEnabled(true);
+ }
+
}
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..c68d7778f 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() {
@@ -704,4 +647,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/examples/default/ios/InstabugExample.xcodeproj/project.pbxproj b/examples/default/ios/InstabugExample.xcodeproj/project.pbxproj
index fb1253b31..9424acb7e 100644
--- a/examples/default/ios/InstabugExample.xcodeproj/project.pbxproj
+++ b/examples/default/ios/InstabugExample.xcodeproj/project.pbxproj
@@ -22,7 +22,6 @@
CC3DF8932A1DFC9A003E9914 /* InstabugSurveysTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC3DF88B2A1DFC99003E9914 /* InstabugSurveysTests.m */; };
CC3DF8942A1DFC9A003E9914 /* InstabugAPMTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC3DF88C2A1DFC99003E9914 /* InstabugAPMTests.m */; };
CC3DF8952A1DFC9A003E9914 /* IBGConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = CC3DF88D2A1DFC9A003E9914 /* IBGConstants.m */; };
- CC487A9C2C71FCFC0021F680 /* Instabug.plist in Resources */ = {isa = PBXBuildFile; fileRef = CC487A9B2C71FCFC0021F680 /* Instabug.plist */; };
CCF1E4092B022CF20024802D /* RNInstabugTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CCF1E4082B022CF20024802D /* RNInstabugTests.m */; };
CD36F4707EA1F435D2CC7A15 /* libPods-InstabugExample-InstabugTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AF7A6E02D40E0CEEA833CC4 /* libPods-InstabugExample-InstabugTests.a */; };
F7BF47401EF3A435254C97BB /* libPods-InstabugExample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BAED0D0441A708AE2390E153 /* libPods-InstabugExample.a */; };
@@ -57,7 +56,7 @@
BE3328762BDACE030078249A /* IBGCrashReporting+CP.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "IBGCrashReporting+CP.h"; sourceTree = ""; };
C3C8C24386310A3120006604 /* CrashReportingExampleModule.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CrashReportingExampleModule.m; sourceTree = ""; };
C3C8C784EADC037C5A752B94 /* CrashReportingExampleModule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CrashReportingExampleModule.h; sourceTree = ""; };
- C4E7796400B3B360DD0B6E73 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = InstabugExample/PrivacyInfo.xcprivacy; sourceTree = ""; };
+ C4E7796400B3B360DD0B6E73 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = InstabugExample/PrivacyInfo.xcprivacy; sourceTree = ""; };
CC3DF8852A1DFC99003E9914 /* InstabugCrashReportingTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InstabugCrashReportingTests.m; sourceTree = ""; };
CC3DF8862A1DFC99003E9914 /* InstabugBugReportingTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InstabugBugReportingTests.m; sourceTree = ""; };
CC3DF8872A1DFC99003E9914 /* InstabugSampleTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InstabugSampleTests.m; sourceTree = ""; };
@@ -67,7 +66,6 @@
CC3DF88B2A1DFC99003E9914 /* InstabugSurveysTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InstabugSurveysTests.m; sourceTree = ""; };
CC3DF88C2A1DFC99003E9914 /* InstabugAPMTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InstabugAPMTests.m; sourceTree = ""; };
CC3DF88D2A1DFC9A003E9914 /* IBGConstants.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IBGConstants.m; sourceTree = ""; };
- CC487A9B2C71FCFC0021F680 /* Instabug.plist */ = {isa = PBXFileReference; lastKnownFileType = file.bplist; name = Instabug.plist; path = InstabugExample/Instabug.plist; sourceTree = ""; };
CCF1E4082B022CF20024802D /* RNInstabugTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNInstabugTests.m; sourceTree = ""; };
DBCB1B1D023646D84146C91E /* Pods-InstabugExample-InstabugTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InstabugExample-InstabugTests.release.xcconfig"; path = "Target Support Files/Pods-InstabugExample-InstabugTests/Pods-InstabugExample-InstabugTests.release.xcconfig"; sourceTree = ""; };
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
@@ -124,7 +122,6 @@
13B07FAE1A68108700A75B9A /* InstabugExample */ = {
isa = PBXGroup;
children = (
- CC487A9B2C71FCFC0021F680 /* Instabug.plist */,
13B07FAF1A68108700A75B9A /* AppDelegate.h */,
13B07FB01A68108700A75B9A /* AppDelegate.mm */,
13B07FB51A68108700A75B9A /* Images.xcassets */,
@@ -302,7 +299,6 @@
buildActionMask = 2147483647;
files = (
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */,
- CC487A9C2C71FCFC0021F680 /* Instabug.plist in Resources */,
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
AF8A18C6FA80B2BFE6006C9E /* PrivacyInfo.xcprivacy in Resources */,
);
diff --git a/examples/default/ios/InstabugExample/Instabug.plist b/examples/default/ios/InstabugExample/Instabug.plist
deleted file mode 100644
index 24d035f42..000000000
Binary files a/examples/default/ios/InstabugExample/Instabug.plist and /dev/null differ
diff --git a/examples/default/ios/InstabugTests/InstabugAPMTests.m b/examples/default/ios/InstabugTests/InstabugAPMTests.m
index 949393adb..127ff50ce 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]);
@@ -177,6 +130,22 @@ - (void) testEndUITrace {
OCMVerify([mock endUITrace]);
}
+- (void) testSetScreenRenderEnabled {
+ id mock = OCMClassMock([IBGAPM class]);
+ BOOL isEnabled = YES;
+
+ [self.instabugBridge setScreenRenderEnabled:isEnabled];
+
+ OCMVerify([mock setScreenRenderingEnabled:YES]);
+}
+- (void) testSetScreenRenderDisabled {
+ id mock = OCMClassMock([IBGAPM class]);
+ BOOL isEnabled = NO;
+
+ [self.instabugBridge setScreenRenderEnabled:isEnabled];
+
+ OCMVerify([mock setScreenRenderingEnabled:NO]);
+}
@end
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 ded37c3af..b91213a01 100644
--- a/examples/default/ios/InstabugTests/InstabugSampleTests.m
+++ b/examples/default/ios/InstabugTests/InstabugSampleTests.m
@@ -141,19 +141,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"];
@@ -239,7 +226,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]);
}
@@ -486,30 +473,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]);
@@ -651,6 +614,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 b/examples/default/ios/Podfile
index e1dda08e3..0d3e454c6 100644
--- a/examples/default/ios/Podfile
+++ b/examples/default/ios/Podfile
@@ -15,7 +15,7 @@ target 'InstabugExample' do
config = use_native_modules!
rn_maps_path = '../node_modules/react-native-maps'
pod 'react-native-google-maps', :path => rn_maps_path
-
+ pod 'Instabug', :podspec => 'https://ios-releases.instabug.com/custom/faeture-screen_rendering-release/15.1.26/Instabug.podspec'
# Flags change depending on the env values.
flags = get_default_flags()
diff --git a/examples/default/ios/Podfile.lock b/examples/default/ios/Podfile.lock
index 0d7d7a1ff..67becbc47 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 (15.1.26)
- instabug-reactnative-ndk (0.1.0):
- DoubleConversion
- glog
@@ -1626,7 +1626,7 @@ PODS:
- ReactCommon/turbomodule/core
- Yoga
- RNInstabug (15.0.1):
- - Instabug (= 15.1.1)
+ - Instabug (= 15.1.26)
- React-Core
- RNReanimated (3.16.1):
- DoubleConversion
@@ -1770,6 +1770,7 @@ DEPENDENCIES:
- fmt (from `../node_modules/react-native/third-party-podspecs/fmt.podspec`)
- glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`)
- hermes-engine (from `../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`)
+ - Instabug (from `https://ios-releases.instabug.com/custom/faeture-screen_rendering-release/15.1.26/Instabug.podspec`)
- instabug-reactnative-ndk (from `../node_modules/instabug-reactnative-ndk`)
- OCMock
- RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`)
@@ -1850,7 +1851,6 @@ SPEC REPOS:
trunk:
- Google-Maps-iOS-Utils
- GoogleMaps
- - Instabug
- OCMock
- SocketRocket
@@ -1868,6 +1868,8 @@ EXTERNAL SOURCES:
hermes-engine:
:podspec: "../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec"
:tag: hermes-2024-08-15-RNv0.75.1-4b3bf912cc0f705b51b71ce1a5b8bd79b93a451b
+ Instabug:
+ :podspec: https://ios-releases.instabug.com/custom/faeture-screen_rendering-release/15.1.26/Instabug.podspec
instabug-reactnative-ndk:
:path: "../node_modules/instabug-reactnative-ndk"
RCT-Folly:
@@ -2022,7 +2024,7 @@ SPEC CHECKSUMS:
Google-Maps-iOS-Utils: f77eab4c4326d7e6a277f8e23a0232402731913a
GoogleMaps: 032f676450ba0779bd8ce16840690915f84e57ac
hermes-engine: ea92f60f37dba025e293cbe4b4a548fd26b610a0
- Instabug: 3e7af445c14d7823fcdecba223f09b5f7c0c6ce1
+ Instabug: c47bd604b5212496da79b19b368eb5de73833d69
instabug-reactnative-ndk: d765ac289d56e8896398d02760d9abf2562fc641
OCMock: 589f2c84dacb1f5aaf6e4cec1f292551fe748e74
RCT-Folly: 4464f4d875961fce86008d45f4ecf6cef6de0740
@@ -2090,7 +2092,7 @@ SPEC CHECKSUMS:
ReactCommon: 6a952e50c2a4b694731d7682aaa6c79bc156e4ad
RNCClipboard: 2821ac938ef46f736a8de0c8814845dde2dcbdfb
RNGestureHandler: 511250b190a284388f9dd0d2e56c1df76f14cfb8
- RNInstabug: f17d4e6c679fbc921f2692c223a1c21395589cc5
+ RNInstabug: 35bf420d77731598fae13c33ceecf0343fd8dd99
RNReanimated: f42a5044d121d68e91680caacb0293f4274228eb
RNScreens: c7ceced6a8384cb9be5e7a5e88e9e714401fd958
RNSVG: 8b1a777d54096b8c2a0fd38fc9d5a454332bbb4d
@@ -2098,6 +2100,6 @@ SPEC CHECKSUMS:
SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d
Yoga: 055f92ad73f8c8600a93f0e25ac0b2344c3b07e6
-PODFILE CHECKSUM: 837b933596e1616ff02cc206bb17dee4f611fdbc
+PODFILE CHECKSUM: 4e2ae668f4fb59c72dfd359d3d9c86ec6d4967e5
COCOAPODS: 1.14.0
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 ceef8bc19..00d054764 100644
--- a/examples/default/src/App.tsx
+++ b/examples/default/src/App.tsx
@@ -3,8 +3,8 @@ import { ActivityIndicator, StyleSheet } from 'react-native';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { NavigationContainer, useNavigationContainerRef } from '@react-navigation/native';
-import type { SessionMetadata } from 'instabug-reactnative';
import Instabug, {
+ APM,
CrashReporting,
InvocationEvent,
LaunchType,
@@ -12,6 +12,7 @@ import Instabug, {
NetworkInterceptionMode,
NetworkLogger,
ReproStepsMode,
+ type SessionMetadata,
SessionReplay,
} from 'instabug-reactnative';
import { NativeBaseProvider } from 'native-base';
@@ -42,35 +43,41 @@ export const App: React.FC = () => {
const [isInstabugInitialized, setIsInstabugInitialized] = useState(false);
- const initializeInstabug = async () => {
- try {
- SessionReplay.setSyncCallback((data) => shouldSyncSession(data));
-
- await Instabug.init({
- token: 'deb1910a7342814af4e4c9210c786f35',
- invocationEvents: [InvocationEvent.floatingButton],
- debugLogsLevel: LogLevel.verbose,
- networkInterceptionMode: NetworkInterceptionMode.javascript,
+ const initializeInstabug = () => {
+ // Synchronous setup
+ SessionReplay.setSyncCallback((data) => shouldSyncSession(data));
+
+ // Start async initialization but don't block rendering
+ Instabug.init({
+ token: 'deb1910a7342814af4e4c9210c786f35',
+ invocationEvents: [InvocationEvent.floatingButton],
+ debugLogsLevel: LogLevel.verbose,
+ networkInterceptionMode: NetworkInterceptionMode.javascript,
+ })
+ .then(() => {
+ // Post-initialization setup
+ NetworkLogger.setNetworkDataObfuscationHandler(async (networkData) => {
+ networkData.url = `${networkData.url}/JS/Obfuscated`;
+ return networkData;
+ });
+ APM.setScreenRenderingEnabled(true);
+ setIsInstabugInitialized(true);
+ })
+ .catch((error) => {
+ console.error('Instabug initialization failed:', error);
+ setIsInstabugInitialized(true); // Proceed even if initialization fails
});
- CrashReporting.setNDKCrashesEnabled(true);
- Instabug.setReproStepsConfig({ all: ReproStepsMode.enabled });
+ // Synchronous configuration that doesn't depend on init completion
+ CrashReporting.setNDKCrashesEnabled(true);
+ Instabug.setReproStepsConfig({ all: ReproStepsMode.enabled });
- setIsInstabugInitialized(true); // Set to true after initialization
- } catch (error) {
- console.error('Instabug initialization failed:', error);
- setIsInstabugInitialized(true); // Proceed even if initialization fails
- }
+ // Set initialized immediately to show UI - initialization continues in background
+ setIsInstabugInitialized(true);
};
useEffect(() => {
- initializeInstabug().then(() => {
- NetworkLogger.setNetworkDataObfuscationHandler(async (networkData) => {
- networkData.url = `${networkData.url}/JS/Obfuscated`;
- return networkData;
- });
- // NetworkLogger.setRequestFilterExpression('false');
- });
+ initializeInstabug();
});
useEffect(() => {
diff --git a/examples/default/src/navigation/HomeStack.tsx b/examples/default/src/navigation/HomeStack.tsx
index 090aa6587..94e461ed5 100644
--- a/examples/default/src/navigation/HomeStack.tsx
+++ b/examples/default/src/navigation/HomeStack.tsx
@@ -22,7 +22,7 @@ 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 { CustomUITraceScreen } from '../screens/apm/CustomUITraceScreen';
import { NetworkScreen } from '../screens/apm/NetworkScreen';
import { FlowsScreen } from '../screens/apm/FlowsScreen';
import { SessionReplayScreen } from '../screens/SessionReplayScreen';
@@ -31,6 +31,7 @@ import { HttpScreen } from '../screens/apm/HttpScreen';
import { WebViewsScreen } from '../screens/apm/webViews/WebViewsScreen';
import { FullWebViewsScreen } from '../screens/apm/webViews/FullWebViewsScreen';
import { PartialWebViewsScreen } from '../screens/apm/webViews/PartialWebViewsScreen';
+import ScreenRender from '../screens/apm/ScreenRender';
export type HomeStackParamList = {
Home: undefined;
@@ -43,7 +44,7 @@ export type HomeStackParamList = {
BasicComponents: undefined;
ScrollView: undefined;
FlatList: undefined;
- ComplexViews: undefined;
+ ComplexViews: { initialDepth?: number; initialBreadth?: number } | undefined;
SectionList: undefined;
Gestures: undefined;
GoogleMapsScreen: undefined;
@@ -56,11 +57,12 @@ export type HomeStackParamList = {
// APM //
APM: undefined;
NetworkTraces: undefined;
- ExecutionTraces: undefined;
+ CustomUITraces: undefined;
AppFlows: undefined;
WebViews: undefined;
FullWebViews: undefined;
PartialWebViews: undefined;
+ ScreenRender: undefined;
};
const HomeStack = createNativeStackNavigator();
@@ -140,7 +142,7 @@ export const HomeStackNavigator: React.FC = () => {
-
+
{
component={PartialWebViewsScreen}
options={{ title: 'PartialWebViews' }}
/>
+
);
};
diff --git a/examples/default/src/screens/apm/APMScreen.tsx b/examples/default/src/screens/apm/APMScreen.tsx
index 3652a95c5..6c4708e0c 100644
--- a/examples/default/src/screens/apm/APMScreen.tsx
+++ b/examples/default/src/screens/apm/APMScreen.tsx
@@ -34,10 +34,11 @@ export const APMScreen: React.FC APM.endAppLaunch()} />
navigation.navigate('NetworkTraces')} />
- navigation.navigate('ExecutionTraces')} />
+ navigation.navigate('CustomUITraces')} />
navigation.navigate('AppFlows')} />
navigation.navigate('WebViews')} />
navigation.navigate('ComplexViews')} />
+ navigation.navigate('ScreenRender')} />
);
};
diff --git a/examples/default/src/screens/apm/CustomUITraceScreen.tsx b/examples/default/src/screens/apm/CustomUITraceScreen.tsx
new file mode 100644
index 000000000..f03b13334
--- /dev/null
+++ b/examples/default/src/screens/apm/CustomUITraceScreen.tsx
@@ -0,0 +1,58 @@
+import React, { useState } from 'react';
+import { APM } 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 CustomUITraceScreen: React.FC = () => {
+ const [traceName, setTraceName] = useState('');
+
+ function startUITrace() {
+ if (!traceName.trim()) {
+ console.log('Please enter a trace name before starting.');
+ return;
+ }
+ APM.startUITrace(traceName);
+
+ console.log(`UI trace "${traceName}" started.`);
+ }
+
+ function startDelayedUITrace() {
+ if (!traceName.trim()) {
+ console.log('Please enter a trace name before starting.');
+ return;
+ }
+ return BackgroundTimer.setTimeout(() => {
+ APM.startUITrace(traceName);
+ console.log(`Delayed UI trace "${traceName}" started.`);
+ }, 5000);
+ }
+
+ function endUITrace() {
+ APM.endUITrace();
+ console.log('UI trace ended.');
+ }
+
+ return (
+
+
+
+
+ setTraceName(text)}
+ value={traceName}
+ />
+
+
+
+
+
+
+
+ );
+};
diff --git a/examples/default/src/screens/apm/ExecutionTraceScreen.tsx b/examples/default/src/screens/apm/ExecutionTraceScreen.tsx
new file mode 100644
index 000000000..e69de29bb
diff --git a/examples/default/src/screens/apm/ScreenRender.tsx b/examples/default/src/screens/apm/ScreenRender.tsx
new file mode 100644
index 000000000..d42aea04a
--- /dev/null
+++ b/examples/default/src/screens/apm/ScreenRender.tsx
@@ -0,0 +1,445 @@
+/* eslint-disable react-native/no-inline-styles */
+import type { NativeStackScreenProps } from '@react-navigation/native-stack';
+import React, { useEffect, useRef, useState } from 'react';
+import {
+ Animated,
+ SafeAreaView,
+ ScrollView,
+ StyleSheet,
+ Text,
+ TouchableOpacity,
+ View,
+} from 'react-native';
+import type { HomeStackParamList } from '../../navigation/HomeStack';
+import { APM } from 'instabug-reactnative';
+
+// Custom Components
+const ScreenRenderSwitch: React.FC = () => {
+ const [isEnabled, setIsEnabled] = useState(false);
+
+ return (
+
+ Screen Render Monitoring
+ {
+ setIsEnabled(!isEnabled);
+ APM.setScreenRenderingEnabled(isEnabled);
+ }}>
+
+
+
+ );
+};
+
+const AnimatedBox: React.FC<{ isBlocking: boolean; blockingIntensity: number }> = ({
+ isBlocking,
+ blockingIntensity,
+}) => {
+ const [counter, setCounter] = useState(0);
+ const [layoutThrasher, setLayoutThrasher] = useState(0);
+ const animatedValue = useRef(new Animated.Value(0)).current;
+ const intervalRef = useRef(null);
+
+ // Continuous animation - Use native driver for native thread work
+ useEffect(() => {
+ const animation = Animated.loop(
+ Animated.sequence([
+ Animated.timing(animatedValue, {
+ toValue: 1,
+ duration: 1000,
+ useNativeDriver: true, // Native driver for native thread
+ }),
+ Animated.timing(animatedValue, {
+ toValue: 0,
+ duration: 1000,
+ useNativeDriver: true, // Native driver for native thread
+ }),
+ ]),
+ );
+ animation.start();
+
+ return () => animation.stop();
+ }, [animatedValue]);
+
+ // High frequency counter updates
+ useEffect(() => {
+ intervalRef.current = setInterval(() => {
+ setCounter((prev) => prev + 1);
+
+ // Layout thrashing to block native thread
+ if (isBlocking) {
+ setLayoutThrasher((prev) => prev + 1);
+ }
+ }, 16); // ~60fps updates
+
+ return () => {
+ if (intervalRef.current) {
+ clearInterval(intervalRef.current);
+ }
+ };
+ }, [isBlocking]);
+
+ const translateX = animatedValue.interpolate({
+ inputRange: [0, 1],
+ outputRange: [0, 100],
+ });
+
+ const getStatusText = () => {
+ if (!isBlocking) {
+ return 'Running Smoothly';
+ }
+ if (blockingIntensity === 1) {
+ return 'SLOW NATIVE RENDERING!';
+ }
+ if (blockingIntensity === 2) {
+ return 'FROZEN NATIVE THREAD!';
+ }
+ return 'BLOCKING NATIVE THREAD!';
+ };
+
+ const getBoxColor = () => {
+ if (!isBlocking) {
+ return '#4ECDC4';
+ }
+ if (blockingIntensity === 1) {
+ return '#FFB347';
+ } // Orange for slow
+ if (blockingIntensity === 2) {
+ return '#FF6B6B';
+ } // Red for frozen
+ return '#FF6B6B';
+ };
+
+ // Generate many layout-heavy elements to stress native thread
+ const generateHeavyNativeElements = () => {
+ if (!isBlocking) {
+ return null;
+ }
+
+ const elementCount = blockingIntensity === 1 ? 50 : 200; // More elements = more native work
+
+ return Array.from({ length: elementCount }, (_, i) => (
+
+ ));
+ };
+
+ return (
+
+ Frame Counter: {counter}
+
+ Status: {getStatusText()}
+
+
+ {/* Native thread heavy work area */}
+
+
+
+ {blockingIntensity === 1 ? 'Slow!' : blockingIntensity === 2 ? 'Frozen!' : 'Smooth'}
+
+
+
+ {/* Heavy native rendering elements */}
+ {generateHeavyNativeElements()}
+
+
+ {/* Additional native-heavy components */}
+ {isBlocking && (
+
+ {/* Multiple ScrollViews to stress native scrolling */}
+
+ {Array.from({ length: 100 }, (_, i) => (
+
+ ))}
+
+
+ {/* Text that forces layout recalculation */}
+
+ Layout Thrashing Text: {layoutThrasher}
+
+
+ )}
+
+ );
+};
+
+interface InstabugButtonProps {
+ text: string;
+ onPress: () => void;
+ disabled?: boolean;
+}
+
+const InstabugButton: React.FC = ({ text, onPress, disabled }) => {
+ return (
+
+ {text}
+
+ );
+};
+
+// Main Component
+const ScreenRenderPage: React.FC> = ({
+ navigation,
+}) => {
+ const [isBlocking, setIsBlocking] = useState(false);
+ const [blockingIntensity, setBlockingIntensity] = useState(0); // 0 = none, 1 = slow, 2 = frozen
+ const blockingTimeoutRef = useRef(null);
+
+ const triggerFrozenFrames = (): void => {
+ setIsBlocking(true);
+ setBlockingIntensity(2); // Frozen frames mode
+
+ // Clear any existing timeout
+ if (blockingTimeoutRef.current) {
+ clearTimeout(blockingTimeoutRef.current);
+ }
+
+ // Stop blocking after 5 seconds
+ blockingTimeoutRef.current = setTimeout(() => {
+ setIsBlocking(false);
+ setBlockingIntensity(0);
+ }, 5000);
+ };
+
+ const navigateToComplexPage = (): void => {
+ navigation.navigate('ComplexViews', { initialDepth: 10, initialBreadth: 2 });
+ };
+
+ // Cleanup timeout on unmount
+ useEffect(() => {
+ return () => {
+ if (blockingTimeoutRef.current) {
+ clearTimeout(blockingTimeoutRef.current);
+ }
+ };
+ }, []);
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: '#f5f5f5',
+ },
+ scrollContent: {
+ padding: 20,
+ },
+ title: {
+ fontSize: 24,
+ fontWeight: 'bold',
+ textAlign: 'center',
+ marginBottom: 20,
+ color: '#333',
+ },
+ spacer: {
+ height: 16,
+ },
+ largeSpacer: {
+ height: 50,
+ },
+ buttonContainer: {
+ gap: 12,
+ },
+ button: {
+ backgroundColor: '#007AFF',
+ paddingVertical: 12,
+ paddingHorizontal: 20,
+ borderRadius: 8,
+ alignItems: 'center',
+ marginVertical: 4,
+ },
+ buttonText: {
+ color: 'white',
+ fontSize: 16,
+ fontWeight: '600',
+ },
+ buttonDisabled: {
+ backgroundColor: '#ccc',
+ opacity: 0.7,
+ },
+ buttonTextDisabled: {
+ color: '#888',
+ },
+ switchContainer: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ paddingVertical: 12,
+ paddingHorizontal: 16,
+ backgroundColor: 'white',
+ borderRadius: 8,
+ marginVertical: 8,
+ },
+ switchLabel: {
+ fontSize: 16,
+ color: '#333',
+ },
+ switch: {
+ width: 50,
+ height: 30,
+ borderRadius: 15,
+ backgroundColor: '#ccc',
+ justifyContent: 'center',
+ padding: 2,
+ },
+ switchEnabled: {
+ backgroundColor: '#007AFF',
+ },
+ switchThumb: {
+ width: 26,
+ height: 26,
+ borderRadius: 13,
+ backgroundColor: 'white',
+ alignSelf: 'flex-start',
+ },
+ switchThumbEnabled: {
+ alignSelf: 'flex-end',
+ },
+ animatedContainer: {
+ alignItems: 'center',
+ paddingVertical: 20,
+ backgroundColor: 'white',
+ borderRadius: 8,
+ marginVertical: 8,
+ },
+ counterText: {
+ fontSize: 18,
+ fontWeight: 'bold',
+ color: '#333',
+ marginBottom: 15,
+ },
+ animatedBox: {
+ width: 100,
+ height: 60,
+ backgroundColor: '#FF6B6B',
+ borderRadius: 8,
+ justifyContent: 'center',
+ alignItems: 'center',
+ transform: [{ scale: 1 }],
+ },
+ animatedBoxActive: {
+ backgroundColor: '#4ECDC4',
+ transform: [{ scale: 1.1 }],
+ },
+ animatedBoxText: {
+ color: 'white',
+ fontSize: 14,
+ fontWeight: '600',
+ textAlign: 'center',
+ },
+ statusText: {
+ fontSize: 16,
+ color: '#666',
+ marginBottom: 10,
+ },
+ statusTextAlert: {
+ color: '#FF6B6B', // Red for alert
+ fontWeight: 'bold',
+ },
+ additionalElements: {
+ flexDirection: 'row',
+ flexWrap: 'wrap',
+ justifyContent: 'center',
+ marginTop: 20,
+ },
+ smallBox: {
+ width: 30,
+ height: 30,
+ margin: 5,
+ borderRadius: 15,
+ },
+ nativeWorkArea: {
+ position: 'relative',
+ width: 200,
+ height: 200,
+ backgroundColor: '#f0f0f0',
+ borderRadius: 10,
+ justifyContent: 'center',
+ alignItems: 'center',
+ marginVertical: 10,
+ borderWidth: 1,
+ borderColor: '#ccc',
+ },
+ heavyNativeSection: {
+ marginTop: 20,
+ alignItems: 'center',
+ },
+ miniScrollView: {
+ width: '100%',
+ height: 100,
+ backgroundColor: '#e0e0e0',
+ borderRadius: 8,
+ marginBottom: 10,
+ },
+});
+
+export default ScreenRenderPage;
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/examples/default/src/screens/user-steps/ComplexViewsScreen.tsx b/examples/default/src/screens/user-steps/ComplexViewsScreen.tsx
index 0035d5775..b6fc337a6 100644
--- a/examples/default/src/screens/user-steps/ComplexViewsScreen.tsx
+++ b/examples/default/src/screens/user-steps/ComplexViewsScreen.tsx
@@ -1,4 +1,6 @@
import React, { useRef, useState } from 'react';
+import type { NativeStackScreenProps } from '@react-navigation/native-stack';
+import type { HomeStackParamList } from '../../navigation/HomeStack';
import { Screen } from '../../components/Screen';
import { Section } from '../../components/Section';
@@ -7,10 +9,11 @@ import { Button } from 'react-native';
import { ScrollView, VStack } from 'native-base';
import { InputField } from '../../components/InputField';
-export const ComplexViewsScreen: React.FC = () => {
- const initialDepth = 10;
- const initialBreadth = 2;
-
+export const ComplexViewsScreen: React.FC<
+ NativeStackScreenProps
+> = ({ route }) => {
+ const initialDepth = route.params?.initialDepth ?? 10;
+ const initialBreadth = route.params?.initialBreadth ?? 2;
const depthRef = useRef(initialDepth);
const breadthRef = useRef(initialBreadth);
diff --git a/ios/RNInstabug/InstabugAPMBridge.h b/ios/RNInstabug/InstabugAPMBridge.h
index 0a0ea397c..894fc5ed1 100644
--- a/ios/RNInstabug/InstabugAPMBridge.h
+++ b/ios/RNInstabug/InstabugAPMBridge.h
@@ -15,18 +15,14 @@
- (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;
- (void)startUITrace:(NSString *)name;
- (void)endUITrace;
+- (void)setScreenRenderEnabled:(BOOL)isEnabled;
+
extern NSMutableDictionary *traces;
@end
diff --git a/ios/RNInstabug/InstabugAPMBridge.m b/ios/RNInstabug/InstabugAPMBridge.m
index c28c7f425..fec8f150f 100644
--- a/ios/RNInstabug/InstabugAPMBridge.m
+++ b/ios/RNInstabug/InstabugAPMBridge.m
@@ -61,41 +61,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) {
@@ -124,6 +89,10 @@ - (id) init
[IBGAPM endUITrace];
}
+// Enables or disables screen render.
+RCT_EXPORT_METHOD(setScreenRenderEnabled:(BOOL)isEnabled) {
+ IBGAPM.screenRenderingEnabled = isEnabled;
+}
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 1fe5505d3..c12b59b1e 100644
--- a/ios/RNInstabug/InstabugReactBridge.h
+++ b/ios/RNInstabug/InstabugReactBridge.h
@@ -42,6 +42,8 @@
- (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 a48851ba8..3194d8777 100644
--- a/ios/RNInstabug/InstabugReactBridge.m
+++ b/ios/RNInstabug/InstabugReactBridge.m
@@ -62,8 +62,8 @@ - (dispatch_queue_t)methodQueue {
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) {
@@ -173,6 +173,87 @@ - (dispatch_queue_t)methodQueue {
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];
}
@@ -355,18 +436,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..8296dbcdb 100644
--- a/ios/native.rb
+++ b/ios/native.rb
@@ -1,4 +1,4 @@
-$instabug = { :version => '15.1.1' }
+$instabug = { :version => '15.1.26' }
def use_instabug! (spec = nil)
version = $instabug[:version]
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/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..87f682770 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.
*
@@ -139,3 +115,11 @@ export const endUITrace = () => {
export const _ibgSleep = () => {
NativeAPM.ibgSleep();
};
+
+/**
+ * Enables or disables Screen Render feature
+ * @param isEnabled
+ */
+export const setScreenRenderingEnabled = (isEnabled: boolean) => {
+ NativeAPM.setScreenRenderEnabled(isEnabled);
+};
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 f7d582e70..c6ec8ad39 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;
@@ -389,9 +384,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 });
};
/**
@@ -775,35 +771,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.
@@ -895,3 +862,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..2e7594fd5 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;
@@ -48,6 +44,9 @@ export interface ApmNativeModule extends NativeModule {
startUITrace(name: string): void;
endUITrace(): void;
ibgSleep(): void;
+
+ // Screen Rendering //
+ setScreenRenderEnabled(isEnabled: boolean): void;
}
export const NativeAPM = NativeModules.IBGAPM;
diff --git a/src/native/NativeInstabug.ts b/src/native/NativeInstabug.ts
index 7032bbc07..a3c9102d6 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;
@@ -118,10 +119,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;
@@ -158,9 +155,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..8c75b4807 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(),
@@ -17,6 +14,7 @@ const mockAPM: ApmNativeModule = {
endAppLaunch: jest.fn(),
ibgSleep: jest.fn(),
networkLogAndroid: jest.fn(),
+ setScreenRenderEnabled: jest.fn(),
};
export default mockAPM;
diff --git a/test/mocks/mockInstabug.ts b/test/mocks/mockInstabug.ts
index 391a00a38..96b052d20 100644
--- a/test/mocks/mockInstabug.ts
+++ b/test/mocks/mockInstabug.ts
@@ -49,9 +49,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 +74,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..d8e37d429 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';
@@ -155,4 +101,11 @@ describe('APM Module', () => {
expect(NativeAPM.ibgSleep).toBeCalledTimes(1);
expect(NativeAPM.ibgSleep).toBeCalledWith();
});
+
+ it('should call the native method setScreenRenderEnabled', () => {
+ APM.setScreenRenderingEnabled(true);
+
+ expect(NativeAPM.setScreenRenderEnabled).toBeCalledTimes(1);
+ expect(NativeAPM.setScreenRenderEnabled).toBeCalledWith(true);
+ });
});
diff --git a/test/modules/Instabug.spec.ts b/test/modules/Instabug.spec.ts
index f6e36d8e4..695d7310e 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';
@@ -455,12 +455,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', () => {
@@ -825,25 +826,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 = [
{