Skip to content

Commit 3ea6484

Browse files
committed
feat: support advanced UI customization
1 parent dd69dcc commit 3ea6484

File tree

11 files changed

+602
-1
lines changed

11 files changed

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

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);
@@ -658,4 +660,75 @@ public void testSetNetworkLogBodyDisabled() {
658660

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

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

612612
}
613613

614+
- (void)testSetThemeWithAllProperties {
615+
NSDictionary *themeConfig = @{
616+
@"primaryColor": @"#FF6B6B",
617+
@"backgroundColor": @"#FFFFFF",
618+
@"titleTextColor": @"#000000",
619+
@"primaryTextColor": @"#333333",
620+
@"secondaryTextColor": @"#666666",
621+
@"callToActionTextColor": @"#FF6B6B",
622+
@"primaryFontPath": @"assets/fonts/CustomFont-Regular.ttf",
623+
@"secondaryFontPath": @"assets/fonts/CustomFont-Bold.ttf",
624+
@"ctaFontPath": @"assets/fonts/CustomFont-Italic.ttf"
625+
};
626+
627+
id mockTheme = OCMClassMock([IBGTheme class]);
628+
OCMStub([mockTheme primaryColor]).andReturn([UIColor redColor]);
629+
OCMStub([mockTheme backgroundColor]).andReturn([UIColor whiteColor]);
630+
OCMStub([mockTheme titleTextColor]).andReturn([UIColor blackColor]);
631+
OCMStub([mockTheme primaryTextColor]).andReturn([UIColor darkGrayColor]);
632+
OCMStub([mockTheme secondaryTextColor]).andReturn([UIColor grayColor]);
633+
OCMStub([mockTheme callToActionTextColor]).andReturn([UIColor redColor]);
634+
635+
FlutterError *error;
636+
637+
[self.api setThemeThemeConfig:themeConfig error:&error];
638+
639+
OCMVerify([self.mInstabug setTheme:OCMArg.any]);
640+
}
641+
614642
@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)