Skip to content

Commit f4fc1e6

Browse files
committed
feat: support advanced UI customization
1 parent cb85eda commit f4fc1e6

File tree

11 files changed

+600
-1
lines changed

11 files changed

+600
-1
lines changed

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

Lines changed: 162 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

1011
import androidx.annotation.NonNull;
@@ -529,4 +530,165 @@ public void setNetworkLogBodyEnabled(@NonNull Boolean isEnabled) {
529530
e.printStackTrace();
530531
}
531532
}
533+
534+
@Override
535+
public void setTheme(@NonNull Map<String, Object> themeConfig) {
536+
try {
537+
Log.d(TAG, "setTheme called with config: " + themeConfig.toString());
538+
539+
com.instabug.library.model.IBGTheme.Builder builder = new com.instabug.library.model.IBGTheme.Builder();
540+
541+
if (themeConfig.containsKey("primaryColor")) {
542+
builder.setPrimaryColor(getColor(themeConfig, "primaryColor"));
543+
}
544+
if (themeConfig.containsKey("secondaryTextColor")) {
545+
builder.setSecondaryTextColor(getColor(themeConfig, "secondaryTextColor"));
546+
}
547+
if (themeConfig.containsKey("primaryTextColor")) {
548+
builder.setPrimaryTextColor(getColor(themeConfig, "primaryTextColor"));
549+
}
550+
if (themeConfig.containsKey("titleTextColor")) {
551+
builder.setTitleTextColor(getColor(themeConfig, "titleTextColor"));
552+
}
553+
if (themeConfig.containsKey("backgroundColor")) {
554+
builder.setBackgroundColor(getColor(themeConfig, "backgroundColor"));
555+
}
556+
557+
if (themeConfig.containsKey("primaryTextStyle")) {
558+
builder.setPrimaryTextStyle(getTextStyle(themeConfig, "primaryTextStyle"));
559+
}
560+
if (themeConfig.containsKey("secondaryTextStyle")) {
561+
builder.setSecondaryTextStyle(getTextStyle(themeConfig, "secondaryTextStyle"));
562+
}
563+
if (themeConfig.containsKey("ctaTextStyle")) {
564+
builder.setCtaTextStyle(getTextStyle(themeConfig, "ctaTextStyle"));
565+
}
566+
567+
setFontIfPresent(themeConfig, builder, "primaryFontPath", "primaryFontAsset", "primary");
568+
setFontIfPresent(themeConfig, builder, "secondaryFontPath", "secondaryFontAsset", "secondary");
569+
setFontIfPresent(themeConfig, builder, "ctaFontPath", "ctaFontAsset", "CTA");
570+
571+
com.instabug.library.model.IBGTheme theme = builder.build();
572+
Instabug.setTheme(theme);
573+
Log.d(TAG, "Theme applied successfully");
574+
575+
} catch (Exception e) {
576+
Log.e(TAG, "Error in setTheme: " + e.getMessage());
577+
e.printStackTrace();
578+
}
579+
}
580+
581+
public void setFullscreen(@NonNull Boolean isEnabled) {
582+
try {
583+
Instabug.setFullscreen(isEnabled);
584+
} catch (Exception e) {
585+
e.printStackTrace();
586+
}
587+
}
588+
589+
/**
590+
* Retrieves a color value from the Map.
591+
*
592+
* @param map The Map object.
593+
* @param key The key to look for.
594+
* @return The parsed color as an integer, or black if missing or invalid.
595+
*/
596+
private int getColor(Map<String, Object> map, String key) {
597+
try {
598+
if (map != null && map.containsKey(key) && map.get(key) != null) {
599+
String colorString = (String) map.get(key);
600+
return android.graphics.Color.parseColor(colorString);
601+
}
602+
} catch (Exception e) {
603+
e.printStackTrace();
604+
}
605+
return android.graphics.Color.BLACK;
606+
}
607+
608+
/**
609+
* Retrieves a text style from the Map.
610+
*
611+
* @param map The Map object.
612+
* @param key The key to look for.
613+
* @return The corresponding Typeface style, or Typeface.NORMAL if missing or invalid.
614+
*/
615+
private int getTextStyle(Map<String, Object> map, String key) {
616+
try {
617+
if (map != null && map.containsKey(key) && map.get(key) != null) {
618+
String style = (String) map.get(key);
619+
switch (style.toLowerCase()) {
620+
case "bold":
621+
return Typeface.BOLD;
622+
case "italic":
623+
return Typeface.ITALIC;
624+
case "bold_italic":
625+
return Typeface.BOLD_ITALIC;
626+
case "normal":
627+
default:
628+
return Typeface.NORMAL;
629+
}
630+
}
631+
} catch (Exception e) {
632+
e.printStackTrace();
633+
}
634+
return Typeface.NORMAL;
635+
}
636+
637+
/**
638+
* Sets a font on the theme builder if the font configuration is present in the theme config.
639+
*
640+
* @param themeConfig The theme configuration map
641+
* @param builder The theme builder
642+
* @param fileKey The key for font file path
643+
* @param assetKey The key for font asset path
644+
* @param fontType The type of font (for logging purposes)
645+
*/
646+
private void setFontIfPresent(Map<String, Object> themeConfig, com.instabug.library.model.IBGTheme.Builder builder,
647+
String fileKey, String assetKey, String fontType) {
648+
if (themeConfig.containsKey(fileKey) || themeConfig.containsKey(assetKey)) {
649+
Typeface typeface = getTypeface(themeConfig, fileKey, assetKey);
650+
if (typeface != null) {
651+
switch (fontType) {
652+
case "primary":
653+
builder.setPrimaryTextFont(typeface);
654+
break;
655+
case "secondary":
656+
builder.setSecondaryTextFont(typeface);
657+
break;
658+
case "CTA":
659+
builder.setCtaTextFont(typeface);
660+
break;
661+
}
662+
}
663+
}
664+
}
665+
666+
private Typeface getTypeface(Map<String, Object> map, String fileKey, String assetKey) {
667+
try {
668+
String fontName = null;
669+
670+
if (assetKey != null && map.containsKey(assetKey) && map.get(assetKey) != null) {
671+
fontName = (String) map.get(assetKey);
672+
} else if (fileKey != null && map.containsKey(fileKey) && map.get(fileKey) != null) {
673+
fontName = (String) map.get(fileKey);
674+
}
675+
676+
if (fontName == null) {
677+
return Typeface.DEFAULT;
678+
}
679+
680+
681+
try {
682+
String assetPath = "fonts/" + fontName;
683+
return Typeface.createFromAsset(context.getAssets(), assetPath);
684+
} catch (Exception e) {
685+
return Typeface.create(fontName, Typeface.NORMAL);
686+
}
687+
688+
} catch (Exception e) {
689+
return Typeface.DEFAULT;
690+
}
691+
}
692+
693+
532694
}

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

Lines changed: 73 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);
@@ -670,4 +672,75 @@ public void testSetNetworkLogBodyDisabled() {
670672

671673
mInstabug.verify(() -> Instabug.setNetworkLogBodyEnabled(false));
672674
}
675+
676+
@Test
677+
public void testSetThemeWithAllProperties() {
678+
Map<String, Object> themeConfig = new HashMap<>();
679+
themeConfig.put("primaryColor", "#FF6B6B");
680+
themeConfig.put("backgroundColor", "#FFFFFF");
681+
themeConfig.put("titleTextColor", "#000000");
682+
themeConfig.put("primaryTextColor", "#333333");
683+
themeConfig.put("secondaryTextColor", "#666666");
684+
themeConfig.put("primaryTextStyle", "bold");
685+
themeConfig.put("secondaryTextStyle", "italic");
686+
themeConfig.put("ctaTextStyle", "bold_italic");
687+
themeConfig.put("primaryFontAsset", "assets/fonts/CustomFont-Regular.ttf");
688+
themeConfig.put("secondaryFontAsset", "assets/fonts/CustomFont-Bold.ttf");
689+
themeConfig.put("ctaFontAsset", "assets/fonts/CustomFont-Italic.ttf");
690+
691+
MockedConstruction<com.instabug.library.model.IBGTheme.Builder> mThemeBuilder =
692+
mockConstruction(com.instabug.library.model.IBGTheme.Builder.class, (mock, context) -> {
693+
when(mock.setPrimaryColor(anyInt())).thenReturn(mock);
694+
when(mock.setBackgroundColor(anyInt())).thenReturn(mock);
695+
when(mock.setTitleTextColor(anyInt())).thenReturn(mock);
696+
when(mock.setPrimaryTextColor(anyInt())).thenReturn(mock);
697+
when(mock.setSecondaryTextColor(anyInt())).thenReturn(mock);
698+
when(mock.setPrimaryTextStyle(anyInt())).thenReturn(mock);
699+
when(mock.setSecondaryTextStyle(anyInt())).thenReturn(mock);
700+
when(mock.setCtaTextStyle(anyInt())).thenReturn(mock);
701+
when(mock.setPrimaryTextFont(any(Typeface.class))).thenReturn(mock);
702+
when(mock.setSecondaryTextFont(any(Typeface.class))).thenReturn(mock);
703+
when(mock.setCtaTextFont(any(Typeface.class))).thenReturn(mock);
704+
when(mock.build()).thenReturn(mock(com.instabug.library.model.IBGTheme.class));
705+
});
706+
707+
api.setTheme(themeConfig);
708+
709+
com.instabug.library.model.IBGTheme.Builder builder = mThemeBuilder.constructed().get(0);
710+
711+
// Verify color setters were called
712+
verify(builder).setPrimaryColor(android.graphics.Color.parseColor("#FF6B6B"));
713+
verify(builder).setBackgroundColor(android.graphics.Color.parseColor("#FFFFFF"));
714+
verify(builder).setTitleTextColor(android.graphics.Color.parseColor("#000000"));
715+
verify(builder).setPrimaryTextColor(android.graphics.Color.parseColor("#333333"));
716+
verify(builder).setSecondaryTextColor(android.graphics.Color.parseColor("#666666"));
717+
718+
// Verify text style setters were called
719+
verify(builder).setPrimaryTextStyle(Typeface.BOLD);
720+
verify(builder).setSecondaryTextStyle(Typeface.ITALIC);
721+
verify(builder).setCtaTextStyle(Typeface.BOLD_ITALIC);
722+
723+
// Verify theme was set on Instabug
724+
mInstabug.verify(() -> Instabug.setTheme(any(com.instabug.library.model.IBGTheme.class)));
725+
726+
}
727+
728+
729+
@Test
730+
public void testSetFullscreenEnabled() {
731+
boolean isEnabled = true;
732+
733+
api.setFullscreen(isEnabled);
734+
735+
mInstabug.verify(() -> Instabug.setFullscreen(true));
736+
}
737+
738+
@Test
739+
public void testSetFullscreenDisabled() {
740+
boolean isEnabled = false;
741+
742+
api.setFullscreen(isEnabled);
743+
744+
mInstabug.verify(() -> Instabug.setFullscreen(false));
745+
}
673746
}

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
@@ -618,4 +618,32 @@ - (void)testisW3CFeatureFlagsEnabled {
618618

619619
}
620620

621+
- (void)testSetThemeWithAllProperties {
622+
NSDictionary *themeConfig = @{
623+
@"primaryColor": @"#FF6B6B",
624+
@"backgroundColor": @"#FFFFFF",
625+
@"titleTextColor": @"#000000",
626+
@"primaryTextColor": @"#333333",
627+
@"secondaryTextColor": @"#666666",
628+
@"callToActionTextColor": @"#FF6B6B",
629+
@"primaryFontPath": @"assets/fonts/CustomFont-Regular.ttf",
630+
@"secondaryFontPath": @"assets/fonts/CustomFont-Bold.ttf",
631+
@"ctaFontPath": @"assets/fonts/CustomFont-Italic.ttf"
632+
};
633+
634+
id mockTheme = OCMClassMock([IBGTheme class]);
635+
OCMStub([mockTheme primaryColor]).andReturn([UIColor redColor]);
636+
OCMStub([mockTheme backgroundColor]).andReturn([UIColor whiteColor]);
637+
OCMStub([mockTheme titleTextColor]).andReturn([UIColor blackColor]);
638+
OCMStub([mockTheme primaryTextColor]).andReturn([UIColor darkGrayColor]);
639+
OCMStub([mockTheme secondaryTextColor]).andReturn([UIColor grayColor]);
640+
OCMStub([mockTheme callToActionTextColor]).andReturn([UIColor redColor]);
641+
642+
FlutterError *error;
643+
644+
[self.api setThemeThemeConfig:themeConfig error:&error];
645+
646+
OCMVerify([self.mInstabug setTheme:OCMArg.any]);
647+
}
648+
621649
@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: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ flutter:
5151

5252
# To add assets to your application, add an assets section, like this:
5353
# assets:
54+
# - assets/fonts/
55+
# assets:
5456
# - images/a_dot_burr.jpeg
5557
# - images/a_dot_ham.jpeg
5658

@@ -66,6 +68,9 @@ flutter:
6668
# list giving the asset and other descriptors for the font. For
6769
# example:
6870
# fonts:
71+
# - family: ManufacturingConsent
72+
# fonts:
73+
# - asset: assets/fonts/ManufacturingConsent-Regular.ttf
6974
# - family: Schyler
7075
# fonts:
7176
# - asset: fonts/Schyler-Regular.ttf

0 commit comments

Comments
 (0)