Skip to content

feat/screen-render-tolerance #616

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 27 commits into
base: feat/screen-render-to-dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
3ea6484
feat: support advanced UI customization
AyaMahmoud148 Jul 10, 2025
4081f5b
chore: add change log
AyaMahmoud148 Jul 10, 2025
8798555
fix: delete setFullScreen
AyaMahmoud148 Jul 10, 2025
d46e04d
fix: linting
AyaMahmoud148 Jul 10, 2025
98a2f8c
fix: unit test
AyaMahmoud148 Jul 10, 2025
20add37
fix: linting
AyaMahmoud148 Jul 10, 2025
cd807c4
fix: resolve comments
AyaMahmoud148 Jul 14, 2025
3a28199
chore: remove deprecated apis
AyaMahmoud148 Jul 31, 2025
cb19383
chore: add changelog
AyaMahmoud148 Jul 31, 2025
400df41
Merge branch 'feat/support-advanced-UI-customization' into chore/-Rem…
AyaMahmoud148 Jul 31, 2025
4257f33
fix: setTheme calling
AyaMahmoud148 Jul 31, 2025
5c59d8c
fix: formatte
AyaMahmoud148 Jul 31, 2025
47d148f
add: tolerance value to screen render capturing
AndrewAminInstabug Jul 31, 2025
61da816
fix: formate
AyaMahmoud148 Jul 31, 2025
924f1d9
fix: formatting
AyaMahmoud148 Jul 31, 2025
270940c
fix: ios tests
AyaMahmoud148 Jul 31, 2025
3aa618b
fix: ios tests
AyaMahmoud148 Aug 3, 2025
6e88be4
fix: e2e tests
AyaMahmoud148 Aug 3, 2025
985709a
fix: formate analyze
AyaMahmoud148 Aug 3, 2025
d82c1d4
fix: e2e ios testing
AyaMahmoud148 Aug 3, 2025
61522cf
chore: update iOS pods & implement tolerance in iOS
AndrewAminInstabug Aug 4, 2025
d882ac9
Merge remote-tracking branch 'origin/feat/screen-render-to-dev' into …
AndrewAminInstabug Aug 5, 2025
f2c14a9
Merge remote-tracking branch 'origin/chore/-Remove-deprecated-APIS' i…
AndrewAminInstabug Aug 5, 2025
7324592
chore: resolve conflict with remove deprecated apis branch, update CH…
AndrewAminInstabug Aug 5, 2025
ba40474
chore: run mockito of flutter 2.10.5
AndrewAminInstabug Aug 5, 2025
bfac8cd
fix: format
ahmedAlaaInstabug Aug 6, 2025
cfc0c01
chore: update android dependency
AndrewAminInstabug Aug 10, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# Changelog

## [Unreleased](https://github.com/Instabug/Instabug-Flutter/compare/v15.0.2...dev)

### Changed

- **BREAKING** Remove deprecated APIs ([#614](https://github.com/Instabug/Instabug-Flutter/pull/614)). See migration guide for more details.
### Added

- Add support for Advanced UI customization with comprehensive theming capabilities. ([#599](https://github.com/Instabug/Instabug-Flutter/pull/599))

- Add screen rendering monitoring functionality within the APM product. ([#605](https://github.com/Instabug/Instabug-Flutter/pull/605))


## [15.0.2](https://github.com/Instabug/Instabug-Flutter/compare/v14.3.0...15.0.2) (Jul 7, 2025)

### Added
Expand Down
2 changes: 1 addition & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ android {
}

dependencies {
api 'com.instabug.library:instabug:15.0.2.7020723-SNAPSHOT'
api 'com.instabug.library:instabug:15.0.2.7046628-SNAPSHOT'
testImplementation 'junit:junit:4.13.2'
testImplementation "org.mockito:mockito-inline:3.12.1"
testImplementation "io.mockk:mockk:1.13.13"
Expand Down
115 changes: 28 additions & 87 deletions android/src/main/java/com/instabug/flutter/modules/ApmApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import com.instabug.apm.InternalAPM;
import com.instabug.apm.configuration.cp.APMFeature;
import com.instabug.apm.configuration.cp.FeatureAvailabilityCallback;
import com.instabug.apm.model.ExecutionTrace;
import com.instabug.apm.configuration.cp.ToleranceValueCallback;
import com.instabug.apm.networking.APMNetworkLogger;
import com.instabug.apm.networkinterception.cp.APMCPNetworkLog;
import com.instabug.apm.screenrendering.models.cp.IBGFrameData;
Expand All @@ -31,11 +31,10 @@

public class ApmApi implements ApmPigeon.ApmHostApi {
private final String TAG = ApmApi.class.getName();
private final HashMap<String, ExecutionTrace> traces = new HashMap<>();
private final Callable<Float> refreshRate;
private final Callable<Float> refreshRateCallback;

public ApmApi(Callable<Float> refreshRate) {
this.refreshRate = refreshRate;
this.refreshRateCallback = refreshRate;
}

public static void init(BinaryMessenger messenger, Callable<Float> refreshRateProvider) {
Expand Down Expand Up @@ -94,57 +93,22 @@ public void setAutoUITraceEnabled(@NonNull Boolean isEnabled) {
}
}

/**
* Starts an execution trace and handles the result
* using callbacks.
*
* @param id The `id` parameter is a non-null String that represents the identifier of the execution
* trace.
* @param name The `name` parameter in the `startExecutionTrace` method represents the name of the
* execution trace that will be started. It is used as a reference to identify the trace during
* execution monitoring.
* @param result The `result` parameter in the `startExecutionTrace` method is an instance of
* `ApmPigeon.Result<String>`. This parameter is used to provide the result of the execution trace
* operation back to the caller. The `success` method of the `result` object is called with the
* @deprecated see {@link #startFlow}
*/
@Override
public void startExecutionTrace(@NonNull String id, @NonNull String name, ApmPigeon.Result<String> result) {
ThreadManager.runOnBackground(new Runnable() {
@Override
public void run() {
try {
ExecutionTrace trace = APM.startExecutionTrace(name);
if (trace != null) {
traces.put(id, trace);

ThreadManager.runOnMainThread(new Runnable() {
@Override
public void run() {
result.success(id);
}
});
} else {
ThreadManager.runOnMainThread(new Runnable() {
@Override
public void run() {
result.success(null);
}
});
}
} catch (Exception e) {
e.printStackTrace();

ThreadManager.runOnMainThread(new Runnable() {
@Override
public void run() {
result.success(null);
}
});
}
}
});
}
/**
* Starts an execution trace and handles the result
* using callbacks.
*
* @param id The `id` parameter is a non-null String that represents the identifier of the execution
* trace.
* @param name The `name` parameter in the `startExecutionTrace` method represents the name of the
* execution trace that will be started. It is used as a reference to identify the trace during
* execution monitoring.
* @param result The `result` parameter in the `startExecutionTrace` method is an instance of
* `ApmPigeon.Result<String>`. This parameter is used to provide the result of the execution trace
* operation back to the caller. The `success` method of the `result` object is called with the
*
* @deprecated see {@link #startFlow}
*/


/**
* Starts an AppFlow with the specified name.
Expand Down Expand Up @@ -209,37 +173,7 @@ public void endFlow(@NonNull String name) {
}
}

/**
* 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}
*/
@Override
public void setExecutionTraceAttribute(@NonNull String id, @NonNull String key, @NonNull String value) {
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}
*/
@Override
public void endExecutionTrace(@NonNull String id) {
try {
traces.get(id).end();
} catch (Exception e) {
e.printStackTrace();
}
}

/**
* Starts a UI trace.
Expand Down Expand Up @@ -504,9 +438,16 @@ public void invoke(boolean isEnabled) {
}

@Override
public void deviceRefreshRate(@NonNull ApmPigeon.Result<Double> result) {
public void getDeviceRefreshRateAndTolerance(@NonNull ApmPigeon.Result<List<Double>> result) {
try {
result.success(refreshRate.call().doubleValue());
final double refreshRate = refreshRateCallback.call().doubleValue();
InternalAPM._getToleranceValueForScreenRenderingCP(new ToleranceValueCallback() {
@Override
public void invoke(long tolerance) {
result.success(java.util.Arrays.asList(refreshRate, (double) tolerance));
}
});

} catch (Exception e) {
e.printStackTrace();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ public void setCommentMinimumCharacterCount(@NonNull Long limit, @Nullable List<
reportTypesArray[i] = ArgsRegistry.reportTypes.get(key);
}
}
BugReporting.setCommentMinimumCharacterCount(limit.intValue(), reportTypesArray);
BugReporting.setCommentMinimumCharacterCountForBugReportType(limit.intValue(), reportTypesArray);
}

@Override
Expand Down
168 changes: 154 additions & 14 deletions android/src/main/java/com/instabug/flutter/modules/InstabugApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.graphics.Typeface;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
Expand Down Expand Up @@ -176,7 +177,6 @@ public void setWelcomeMessageMode(@NonNull String mode) {

@Override
public void setPrimaryColor(@NonNull Long color) {
Instabug.setPrimaryColor(color.intValue());
}

@Override
Expand Down Expand Up @@ -228,20 +228,7 @@ public void run() {
);
}

@Override
public void addExperiments(@NonNull List<String> experiments) {
Instabug.addExperiments(experiments);
}

@Override
public void removeExperiments(@NonNull List<String> experiments) {
Instabug.removeExperiments(experiments);
}

@Override
public void clearAllExperiments() {
Instabug.clearAllExperiments();
}

@Override
public void addFeatureFlags(@NonNull Map<String, String> featureFlags) {
Expand Down Expand Up @@ -509,4 +496,157 @@ public void setNetworkLogBodyEnabled(@NonNull Boolean isEnabled) {
e.printStackTrace();
}
}

@Override
public void setTheme(@NonNull Map<String, Object> themeConfig) {
try {
Log.d(TAG, "setTheme called with config: " + themeConfig.toString());

com.instabug.library.model.IBGTheme.Builder builder = new com.instabug.library.model.IBGTheme.Builder();

if (themeConfig.containsKey("primaryColor")) {
builder.setPrimaryColor(getColor(themeConfig, "primaryColor"));
}
if (themeConfig.containsKey("secondaryTextColor")) {
builder.setSecondaryTextColor(getColor(themeConfig, "secondaryTextColor"));
}
if (themeConfig.containsKey("primaryTextColor")) {
builder.setPrimaryTextColor(getColor(themeConfig, "primaryTextColor"));
}
if (themeConfig.containsKey("titleTextColor")) {
builder.setTitleTextColor(getColor(themeConfig, "titleTextColor"));
}
if (themeConfig.containsKey("backgroundColor")) {
builder.setBackgroundColor(getColor(themeConfig, "backgroundColor"));
}

if (themeConfig.containsKey("primaryTextStyle")) {
builder.setPrimaryTextStyle(getTextStyle(themeConfig, "primaryTextStyle"));
}
if (themeConfig.containsKey("secondaryTextStyle")) {
builder.setSecondaryTextStyle(getTextStyle(themeConfig, "secondaryTextStyle"));
}
if (themeConfig.containsKey("ctaTextStyle")) {
builder.setCtaTextStyle(getTextStyle(themeConfig, "ctaTextStyle"));
}

setFontIfPresent(themeConfig, builder, "primaryFontPath", "primaryFontAsset", "primary");
setFontIfPresent(themeConfig, builder, "secondaryFontPath", "secondaryFontAsset", "secondary");
setFontIfPresent(themeConfig, builder, "ctaFontPath", "ctaFontAsset", "CTA");

com.instabug.library.model.IBGTheme theme = builder.build();
Instabug.setTheme(theme);
Log.d(TAG, "Theme applied successfully");

} catch (Exception e) {
Log.e(TAG, "Error in setTheme: " + e.getMessage());
e.printStackTrace();
}
}



/**
* Retrieves a color value from the Map.
*
* @param map The Map object.
* @param key The key to look for.
* @return The parsed color as an integer, or black if missing or invalid.
*/
private int getColor(Map<String, Object> map, String key) {
try {
if (map != null && map.containsKey(key) && map.get(key) != null) {
String colorString = (String) map.get(key);
return android.graphics.Color.parseColor(colorString);
}
} catch (Exception e) {
e.printStackTrace();
}
return android.graphics.Color.BLACK;
}

/**
* Retrieves a text style from the Map.
*
* @param map The Map object.
* @param key The key to look for.
* @return The corresponding Typeface style, or Typeface.NORMAL if missing or invalid.
*/
private int getTextStyle(Map<String, Object> map, String key) {
try {
if (map != null && map.containsKey(key) && map.get(key) != null) {
String style = (String) map.get(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;
}

/**
* 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(Map<String, Object> themeConfig, com.instabug.library.model.IBGTheme.Builder builder,
String fileKey, String assetKey, String fontType) {
if (themeConfig.containsKey(fileKey) || themeConfig.containsKey(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;
}
}
}
}

private Typeface getTypeface(Map<String, Object> map, String fileKey, String assetKey) {
String fontName = null;

if (assetKey != null && map.containsKey(assetKey) && map.get(assetKey) != null) {
fontName = (String) map.get(assetKey);
} else if (fileKey != null && map.containsKey(fileKey) && map.get(fileKey) != null) {
fontName = (String) map.get(fileKey);
}

if (fontName == null) {
return Typeface.DEFAULT;
}

try {
String assetPath = "fonts/" + fontName;
return Typeface.createFromAsset(context.getAssets(), assetPath);
} catch (Exception e) {
try {
return Typeface.create(fontName, Typeface.NORMAL);
} catch (Exception e2) {
return Typeface.DEFAULT;
}
}
}


}
Loading