Skip to content

Commit 21dec51

Browse files
Merge branch 'feat/support-advanced-UI-customization' into chore/-Remove-deprecated-APIS
2 parents 3a28199 + cd807c4 commit 21dec51

File tree

12 files changed

+612
-1
lines changed

12 files changed

+612
-1
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Changelog
22

3+
## [Unreleased](https://github.com/Instabug/Instabug-Flutter/compare/v15.0.2...dev)
4+
5+
### Added
6+
7+
- Add support for Advanced UI customization with comprehensive theming capabilities ([#599](https://github.com/Instabug/Instabug-Flutter/pull/599))
8+
9+
310
## [15.0.2](https://github.com/Instabug/Instabug-Flutter/compare/v14.3.0...15.0.2) (Jul 7, 2025)
411

512
### Added

android/src/main/java/com/instabug/flutter/modules/InstabugApi.java

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import android.graphics.Bitmap;
66
import android.graphics.BitmapFactory;
77
import android.net.Uri;
8+
import android.graphics.Typeface;
89
import android.util.Log;
910
import androidx.annotation.NonNull;
1011
import androidx.annotation.Nullable;
@@ -495,4 +496,157 @@ public void setNetworkLogBodyEnabled(@NonNull Boolean isEnabled) {
495496
e.printStackTrace();
496497
}
497498
}
499+
500+
@Override
501+
public void setTheme(@NonNull Map<String, Object> themeConfig) {
502+
try {
503+
Log.d(TAG, "setTheme called with config: " + themeConfig.toString());
504+
505+
com.instabug.library.model.IBGTheme.Builder builder = new com.instabug.library.model.IBGTheme.Builder();
506+
507+
if (themeConfig.containsKey("primaryColor")) {
508+
builder.setPrimaryColor(getColor(themeConfig, "primaryColor"));
509+
}
510+
if (themeConfig.containsKey("secondaryTextColor")) {
511+
builder.setSecondaryTextColor(getColor(themeConfig, "secondaryTextColor"));
512+
}
513+
if (themeConfig.containsKey("primaryTextColor")) {
514+
builder.setPrimaryTextColor(getColor(themeConfig, "primaryTextColor"));
515+
}
516+
if (themeConfig.containsKey("titleTextColor")) {
517+
builder.setTitleTextColor(getColor(themeConfig, "titleTextColor"));
518+
}
519+
if (themeConfig.containsKey("backgroundColor")) {
520+
builder.setBackgroundColor(getColor(themeConfig, "backgroundColor"));
521+
}
522+
523+
if (themeConfig.containsKey("primaryTextStyle")) {
524+
builder.setPrimaryTextStyle(getTextStyle(themeConfig, "primaryTextStyle"));
525+
}
526+
if (themeConfig.containsKey("secondaryTextStyle")) {
527+
builder.setSecondaryTextStyle(getTextStyle(themeConfig, "secondaryTextStyle"));
528+
}
529+
if (themeConfig.containsKey("ctaTextStyle")) {
530+
builder.setCtaTextStyle(getTextStyle(themeConfig, "ctaTextStyle"));
531+
}
532+
533+
setFontIfPresent(themeConfig, builder, "primaryFontPath", "primaryFontAsset", "primary");
534+
setFontIfPresent(themeConfig, builder, "secondaryFontPath", "secondaryFontAsset", "secondary");
535+
setFontIfPresent(themeConfig, builder, "ctaFontPath", "ctaFontAsset", "CTA");
536+
537+
com.instabug.library.model.IBGTheme theme = builder.build();
538+
Instabug.setTheme(theme);
539+
Log.d(TAG, "Theme applied successfully");
540+
541+
} catch (Exception e) {
542+
Log.e(TAG, "Error in setTheme: " + e.getMessage());
543+
e.printStackTrace();
544+
}
545+
}
546+
547+
548+
549+
/**
550+
* Retrieves a color value from the Map.
551+
*
552+
* @param map The Map object.
553+
* @param key The key to look for.
554+
* @return The parsed color as an integer, or black if missing or invalid.
555+
*/
556+
private int getColor(Map<String, Object> map, String key) {
557+
try {
558+
if (map != null && map.containsKey(key) && map.get(key) != null) {
559+
String colorString = (String) map.get(key);
560+
return android.graphics.Color.parseColor(colorString);
561+
}
562+
} catch (Exception e) {
563+
e.printStackTrace();
564+
}
565+
return android.graphics.Color.BLACK;
566+
}
567+
568+
/**
569+
* Retrieves a text style from the Map.
570+
*
571+
* @param map The Map object.
572+
* @param key The key to look for.
573+
* @return The corresponding Typeface style, or Typeface.NORMAL if missing or invalid.
574+
*/
575+
private int getTextStyle(Map<String, Object> map, String key) {
576+
try {
577+
if (map != null && map.containsKey(key) && map.get(key) != null) {
578+
String style = (String) map.get(key);
579+
switch (style.toLowerCase()) {
580+
case "bold":
581+
return Typeface.BOLD;
582+
case "italic":
583+
return Typeface.ITALIC;
584+
case "bold_italic":
585+
return Typeface.BOLD_ITALIC;
586+
case "normal":
587+
default:
588+
return Typeface.NORMAL;
589+
}
590+
}
591+
} catch (Exception e) {
592+
e.printStackTrace();
593+
}
594+
return Typeface.NORMAL;
595+
}
596+
597+
/**
598+
* Sets a font on the theme builder if the font configuration is present in the theme config.
599+
*
600+
* @param themeConfig The theme configuration map
601+
* @param builder The theme builder
602+
* @param fileKey The key for font file path
603+
* @param assetKey The key for font asset path
604+
* @param fontType The type of font (for logging purposes)
605+
*/
606+
private void setFontIfPresent(Map<String, Object> themeConfig, com.instabug.library.model.IBGTheme.Builder builder,
607+
String fileKey, String assetKey, String fontType) {
608+
if (themeConfig.containsKey(fileKey) || themeConfig.containsKey(assetKey)) {
609+
Typeface typeface = getTypeface(themeConfig, fileKey, assetKey);
610+
if (typeface != null) {
611+
switch (fontType) {
612+
case "primary":
613+
builder.setPrimaryTextFont(typeface);
614+
break;
615+
case "secondary":
616+
builder.setSecondaryTextFont(typeface);
617+
break;
618+
case "CTA":
619+
builder.setCtaTextFont(typeface);
620+
break;
621+
}
622+
}
623+
}
624+
}
625+
626+
private Typeface getTypeface(Map<String, Object> map, String fileKey, String assetKey) {
627+
String fontName = null;
628+
629+
if (assetKey != null && map.containsKey(assetKey) && map.get(assetKey) != null) {
630+
fontName = (String) map.get(assetKey);
631+
} else if (fileKey != null && map.containsKey(fileKey) && map.get(fileKey) != null) {
632+
fontName = (String) map.get(fileKey);
633+
}
634+
635+
if (fontName == null) {
636+
return Typeface.DEFAULT;
637+
}
638+
639+
try {
640+
String assetPath = "fonts/" + fontName;
641+
return Typeface.createFromAsset(context.getAssets(), assetPath);
642+
} catch (Exception e) {
643+
try {
644+
return Typeface.create(fontName, Typeface.NORMAL);
645+
} catch (Exception e2) {
646+
return Typeface.DEFAULT;
647+
}
648+
}
649+
}
650+
651+
498652
}

android/src/test/java/com/instabug/flutter/InstabugApiTest.java

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@
8080
import org.mockito.verification.VerificationMode;
8181
import org.mockito.verification.VerificationMode;
8282

83+
import android.graphics.Typeface;
84+
8385
public class InstabugApiTest {
8486
private final Callable<Bitmap> screenshotProvider = () -> mock(Bitmap.class);
8587
private final Application mContext = mock(Application.class);
@@ -629,4 +631,53 @@ public void testSetNetworkLogBodyDisabled() {
629631

630632
mInstabug.verify(() -> Instabug.setNetworkLogBodyEnabled(false));
631633
}
634+
635+
@Test
636+
public void testSetThemeWithAllProperties() {
637+
Map<String, Object> themeConfig = new HashMap<>();
638+
themeConfig.put("primaryColor", "#FF6B6B");
639+
themeConfig.put("backgroundColor", "#FFFFFF");
640+
themeConfig.put("titleTextColor", "#000000");
641+
themeConfig.put("primaryTextColor", "#333333");
642+
themeConfig.put("secondaryTextColor", "#666666");
643+
themeConfig.put("primaryTextStyle", "bold");
644+
themeConfig.put("secondaryTextStyle", "italic");
645+
themeConfig.put("ctaTextStyle", "bold_italic");
646+
themeConfig.put("primaryFontAsset", "assets/fonts/CustomFont-Regular.ttf");
647+
themeConfig.put("secondaryFontAsset", "assets/fonts/CustomFont-Bold.ttf");
648+
themeConfig.put("ctaFontAsset", "assets/fonts/CustomFont-Italic.ttf");
649+
650+
MockedConstruction<com.instabug.library.model.IBGTheme.Builder> mThemeBuilder =
651+
mockConstruction(com.instabug.library.model.IBGTheme.Builder.class, (mock, context) -> {
652+
when(mock.setPrimaryColor(anyInt())).thenReturn(mock);
653+
when(mock.setBackgroundColor(anyInt())).thenReturn(mock);
654+
when(mock.setTitleTextColor(anyInt())).thenReturn(mock);
655+
when(mock.setPrimaryTextColor(anyInt())).thenReturn(mock);
656+
when(mock.setSecondaryTextColor(anyInt())).thenReturn(mock);
657+
when(mock.setPrimaryTextStyle(anyInt())).thenReturn(mock);
658+
when(mock.setSecondaryTextStyle(anyInt())).thenReturn(mock);
659+
when(mock.setCtaTextStyle(anyInt())).thenReturn(mock);
660+
when(mock.setPrimaryTextFont(any(Typeface.class))).thenReturn(mock);
661+
when(mock.setSecondaryTextFont(any(Typeface.class))).thenReturn(mock);
662+
when(mock.setCtaTextFont(any(Typeface.class))).thenReturn(mock);
663+
when(mock.build()).thenReturn(mock(com.instabug.library.model.IBGTheme.class));
664+
});
665+
666+
api.setTheme(themeConfig);
667+
668+
com.instabug.library.model.IBGTheme.Builder builder = mThemeBuilder.constructed().get(0);
669+
670+
verify(builder).setPrimaryColor(anyInt());
671+
verify(builder).setBackgroundColor(anyInt());
672+
verify(builder).setTitleTextColor(anyInt());
673+
verify(builder).setPrimaryTextColor(anyInt());
674+
verify(builder).setSecondaryTextColor(anyInt());
675+
verify(builder).setPrimaryTextStyle(Typeface.BOLD);
676+
verify(builder).setSecondaryTextStyle(Typeface.ITALIC);
677+
verify(builder).setCtaTextStyle(Typeface.BOLD_ITALIC);
678+
679+
mInstabug.verify(() -> Instabug.setTheme(any(com.instabug.library.model.IBGTheme.class)));
680+
}
681+
682+
632683
}

example/android/app/src/main/kotlin/com/example/InstabugSample/InstabugExampleMethodCallHandler.kt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@ class InstabugExampleMethodCallHandler : MethodChannel.MethodCallHandler {
3737
sendOOM()
3838
result.success(null)
3939
}
40+
SET_FULLSCREEN -> {
41+
val isEnabled = call.arguments as? Map<*, *>
42+
val enabled = isEnabled?.get("isEnabled") as? Boolean ?: false
43+
setFullscreen(enabled)
44+
result.success(null)
45+
}
4046
else -> {
4147
Log.e(TAG, "onMethodCall for ${call.method} is not implemented")
4248
result.notImplemented()
@@ -55,6 +61,7 @@ class InstabugExampleMethodCallHandler : MethodChannel.MethodCallHandler {
5561
const val SEND_NATIVE_FATAL_HANG = "sendNativeFatalHang"
5662
const val SEND_ANR = "sendAnr"
5763
const val SEND_OOM = "sendOom"
64+
const val SET_FULLSCREEN = "setFullscreen"
5865
}
5966

6067
private fun sendNativeNonFatal(exceptionObject: String?) {
@@ -125,4 +132,25 @@ class InstabugExampleMethodCallHandler : MethodChannel.MethodCallHandler {
125132
return randomString.toString()
126133
}
127134

135+
private fun setFullscreen(enabled: Boolean) {
136+
try {
137+
138+
try {
139+
val instabugClass = Class.forName("com.instabug.library.Instabug")
140+
val setFullscreenMethod = instabugClass.getMethod("setFullscreen", Boolean::class.java)
141+
setFullscreenMethod.invoke(null, enabled)
142+
} catch (e: ClassNotFoundException) {
143+
throw e
144+
} catch (e: NoSuchMethodException) {
145+
throw e
146+
} catch (e: Exception) {
147+
throw e
148+
}
149+
150+
} catch (e: Exception) {
151+
e.printStackTrace()
152+
153+
}
154+
}
155+
128156
}

example/ios/InstabugTests/InstabugApiTests.m

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -586,4 +586,32 @@ - (void)testisW3CFeatureFlagsEnabled {
586586

587587
}
588588

589+
- (void)testSetThemeWithAllProperties {
590+
NSDictionary *themeConfig = @{
591+
@"primaryColor": @"#FF6B6B",
592+
@"backgroundColor": @"#FFFFFF",
593+
@"titleTextColor": @"#000000",
594+
@"primaryTextColor": @"#333333",
595+
@"secondaryTextColor": @"#666666",
596+
@"callToActionTextColor": @"#FF6B6B",
597+
@"primaryFontPath": @"assets/fonts/CustomFont-Regular.ttf",
598+
@"secondaryFontPath": @"assets/fonts/CustomFont-Bold.ttf",
599+
@"ctaFontPath": @"assets/fonts/CustomFont-Italic.ttf"
600+
};
601+
602+
id mockTheme = OCMClassMock([IBGTheme class]);
603+
OCMStub([mockTheme primaryColor]).andReturn([UIColor redColor]);
604+
OCMStub([mockTheme backgroundColor]).andReturn([UIColor whiteColor]);
605+
OCMStub([mockTheme titleTextColor]).andReturn([UIColor blackColor]);
606+
OCMStub([mockTheme primaryTextColor]).andReturn([UIColor darkGrayColor]);
607+
OCMStub([mockTheme secondaryTextColor]).andReturn([UIColor grayColor]);
608+
OCMStub([mockTheme callToActionTextColor]).andReturn([UIColor redColor]);
609+
610+
FlutterError *error;
611+
612+
[self.api setThemeThemeConfig:themeConfig error:&error];
613+
614+
OCMVerify([self.mInstabug setTheme:[OCMArg isNotNil]]);
615+
}
616+
589617
@end

example/lib/src/native/instabug_flutter_example_method_channel.dart

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,22 @@ class InstabugFlutterExampleMethodChannel {
5454
log("Failed to send out of memory: '${e.message}'.", name: _tag);
5555
}
5656
}
57+
58+
static Future<void> setFullscreen(bool isEnabled) async {
59+
if (!Platform.isAndroid) {
60+
return;
61+
}
62+
63+
try {
64+
await _channel.invokeMethod(Constants.setFullscreenMethodName, {
65+
'isEnabled': isEnabled,
66+
});
67+
} on PlatformException catch (e) {
68+
log("Failed to set fullscreen: '${e.message}'.", name: _tag);
69+
} catch (e) {
70+
log("Unexpected error setting fullscreen: '$e'.", name: _tag);
71+
}
72+
}
5773
}
5874

5975
class Constants {
@@ -65,4 +81,5 @@ class Constants {
6581
static const sendNativeFatalHangMethodName = "sendNativeFatalHang";
6682
static const sendAnrMethodName = "sendAnr";
6783
static const sendOomMethodName = "sendOom";
84+
static const setFullscreenMethodName = "setFullscreen";
6885
}

example/pubspec.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ flutter:
5151

5252
# To add assets to your application, add an assets section, like this:
5353
# assets:
54+
# - assets/fonts/
5455
# - images/a_dot_burr.jpeg
5556
# - images/a_dot_ham.jpeg
5657

@@ -66,6 +67,9 @@ flutter:
6667
# list giving the asset and other descriptors for the font. For
6768
# example:
6869
# fonts:
70+
# - family: ManufacturingConsent
71+
# fonts:
72+
# - asset: assets/fonts/ManufacturingConsent-Regular.ttf
6973
# - family: Schyler
7074
# fonts:
7175
# - asset: fonts/Schyler-Regular.ttf

0 commit comments

Comments
 (0)