Skip to content

Commit 94ccaec

Browse files
authored
feat: add iconBackground support to backButton on iOS and Android (#8160)
* feat: add iconBackground support to backButton on iOS and Android * merge master * test: add e2e test setup and BackButton screen updates for iconBackground testing * fix: update icon rendering behavior and change BackButton icon color to white * feat: add back button icon background detox test image for iOS * refactor: simplify color check in icon rendering and update detox config * test: regenerate back button iconBackground snapshot for correct device * test: add back button iconBackground snapshot for Android * test: update Android back button iconBackground snapshot for API 35 * chore: update detox emulator AVD name to Wix_API_35 * chore: update detox emulator AVD name to Pixel_3a_API_35 * test: update back button icon background image for Android
1 parent 28ba788 commit 94ccaec

14 files changed

Lines changed: 100 additions & 19 deletions

File tree

android/src/main/java/com/reactnativenavigation/options/BackButton.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public static BackButton parse(Context context, JSONObject json) {
2727
result.disableIconTint = BoolParser.parse(json, "disableIconTint");
2828
result.color = ThemeColour.parse(context, json.optJSONObject( "color"));
2929
result.disabledColor = ThemeColour.parse(context, json.optJSONObject( "disabledColor"));
30+
result.iconBackground = IconBackgroundOptions.parse(context, json.optJSONObject("iconBackground"));
3031
result.testId = TextParser.parse(json, "testID");
3132
result.popStackOnPress = BoolParser.parse(json, "popStackOnPress");
3233

@@ -54,6 +55,7 @@ public void mergeWith(BackButton other) {
5455
if (other.disabledColor.hasValue()) disabledColor = other.disabledColor;
5556
if (other.disableIconTint.hasValue()) disableIconTint = other.disableIconTint;
5657
if (other.enabled.hasValue()) enabled = other.enabled;
58+
if (other.iconBackground.hasValue()) iconBackground = other.iconBackground;
5759
if (other.testId.hasValue()) testId = other.testId;
5860
if (other.popStackOnPress.hasValue()) popStackOnPress = other.popStackOnPress;
5961
}
@@ -67,6 +69,7 @@ void mergeWithDefault(final BackButton defaultOptions) {
6769
if (!disabledColor.hasValue()) disabledColor = defaultOptions.disabledColor;
6870
if (!disableIconTint.hasValue()) disableIconTint = defaultOptions.disableIconTint;
6971
if (!enabled.hasValue()) enabled = defaultOptions.enabled;
72+
if (!iconBackground.hasValue()) iconBackground = defaultOptions.iconBackground;
7073
if (!testId.hasValue()) testId = defaultOptions.testId;
7174
if (!popStackOnPress.hasValue()) popStackOnPress = defaultOptions.popStackOnPress;
7275
}

android/src/main/java/com/reactnativenavigation/viewcontrollers/stack/topbar/button/ButtonPresenter.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,9 +163,10 @@ open class ButtonPresenter(private val context: Context, private val button: But
163163
fun applyNavigationIcon(toolbar: Toolbar, onPress: (ButtonOptions) -> Unit) {
164164
iconResolver.resolve(button) { icon: Drawable ->
165165
setIconColor(icon)
166+
val iconWithBackground = applyIconBackgroundDrawable(icon)
166167
toolbar.setNavigationOnClickListener { onPress(button) }
167168
toolbar.navigationIcon = null
168-
toolbar.navigationIcon = icon
169+
toolbar.navigationIcon = iconWithBackground
169170
setLeftButtonTestId(toolbar)
170171
if (button.accessibilityLabel.hasValue()) toolbar.navigationContentDescription = button.accessibilityLabel.get()
171172
}

ios/RNNBackButtonOptions.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#import "RNNOptions.h"
2+
#import "RNNIconBackgroundOptions.h"
23

34
@interface RNNBackButtonOptions : RNNOptions
45

@@ -16,6 +17,7 @@
1617
@property(nonatomic, strong) Text *displayMode;
1718
@property(nonatomic, strong) Text *identifier;
1819
@property(nonatomic, strong) Bool *popStackOnPress;
20+
@property(nonatomic, strong) RNNIconBackgroundOptions *iconBackground;
1921

2022
- (BOOL)hasValue;
2123

ios/RNNBackButtonOptions.mm

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,13 @@ - (instancetype)initWithDict:(NSDictionary *)dict {
1919
self.enableMenu = [BoolParser parse:dict key:@"enableMenu"];
2020
self.displayMode = [TextParser parse:dict key:@"displayMode"];
2121
self.popStackOnPress = [BoolParser parse:dict key:@"popStackOnPress"];
22+
self.iconBackground = [[RNNIconBackgroundOptions alloc] initWithDict:dict[@"iconBackground"] enabled:nil];
2223

2324
return self;
2425
}
2526

2627
- (void)mergeOptions:(RNNBackButtonOptions *)options {
28+
[self.iconBackground mergeOptions:options.iconBackground];
2729
if (options.identifier.hasValue)
2830
self.identifier = options.identifier;
2931
if (options.icon.hasValue)
@@ -57,7 +59,8 @@ - (void)mergeOptions:(RNNBackButtonOptions *)options {
5759
- (BOOL)hasValue {
5860
return self.icon.hasValue || self.showTitle.hasValue || self.color.hasValue ||
5961
self.fontFamily.hasValue || self.fontSize.hasValue || self.title.hasValue ||
60-
self.enableMenu.hasValue || self.displayMode.hasValue || self.sfSymbol.hasValue;
62+
self.enableMenu.hasValue || self.displayMode.hasValue || self.sfSymbol.hasValue ||
63+
self.iconBackground.hasValue;
6164
}
6265

6366
@end

ios/RNNIconDrawer.mm

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ - (UIImage *)draw:(UIImage *)image
2929
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
3030
UIGraphicsEndImageContext();
3131

32+
if (color) {
33+
return [newImage imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
34+
}
35+
3236
return newImage;
3337
}
3438

ios/TopBarPresenter.mm

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#import "TopBarPresenter.h"
22
#import "RNNFontAttributesCreator.h"
33
#import "RNNUIBarBackButtonItem.h"
4+
#import "RNNIconDrawer.h"
45
#import "UIColor+RNNUtils.h"
56
#import "UIImage+utils.h"
67
#import "UINavigationController+RNNOptions.h"
@@ -179,6 +180,21 @@ - (void)setBackButtonOptions:(RNNBackButtonOptions *)backButtonOptions {
179180
: icon;
180181
}
181182

183+
// Apply iconBackground if present
184+
if (backButtonOptions.iconBackground.hasValue && icon) {
185+
UIColor *backgroundColor = [backButtonOptions.iconBackground.color withDefault:UIColor.clearColor];
186+
CGFloat cornerRadius = [backButtonOptions.iconBackground.cornerRadius withDefault:@(0)].floatValue;
187+
CGFloat width = [backButtonOptions.iconBackground.width withDefault:@(icon.size.width)].floatValue;
188+
CGFloat height = [backButtonOptions.iconBackground.height withDefault:@(icon.size.height)].floatValue;
189+
190+
RNNIconDrawer *iconDrawer = [[RNNIconDrawer alloc] init];
191+
icon = [iconDrawer draw:icon
192+
imageColor:color
193+
backgroundColor:backgroundColor
194+
size:CGSizeMake(width, height)
195+
cornerRadius:cornerRadius];
196+
}
197+
182198
[self setBackIndicatorImage:icon withColor:color];
183199

184200
title = title ? title : (previousNavigationItem.title ? previousNavigationItem.title : @"");

playground/e2e/BackButton.test.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ import { default as TestIDs, default as testIDs } from '../src/testIDs';
22
import Android from './AndroidUtils';
33
import Utils from './Utils';
44

5-
const { elementByLabel, elementById } = Utils;
5+
const { elementByLabel, elementById, elementTopBar, expectImagesToBeEqual } = Utils;
66

77
describe('Back Button', () => {
88
beforeEach(async () => {
9+
// eslint-disable-next-line no-undef
910
await device.launchApp({ newInstance: true });
1011
await elementById(TestIDs.NAVIGATION_TAB).tap();
1112
await elementById(TestIDs.BACK_BUTTON_SCREEN_BTN).tap();
@@ -63,4 +64,12 @@ describe('Back Button', () => {
6364
await expect(elementByLabel('Modal')).toBeVisible();
6465
await expect(elementByLabel('navigationButtonPressed | RNN.hardwareBackButton')).toBeVisible();
6566
});
67+
68+
it.e2e('should render back button with iconBackground', async () => {
69+
await elementById(TestIDs.PUSH_BACK_BUTTON_ICON_BACKGROUND).tap();
70+
// eslint-disable-next-line no-undef
71+
const snapshottedImagePath = `./e2e/assets/back_button_icon_background.${device.getPlatform()}.png`;
72+
const actual = await elementTopBar().takeScreenshot('back_button_icon_background');
73+
expectImagesToBeEqual(actual, snapshottedImagePath);
74+
});
6675
});
13.9 KB
Loading
23.3 KB
Loading

playground/ios/playground.xcodeproj/project.pbxproj

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -681,11 +681,11 @@
681681
);
682682
inputPaths = (
683683
"${PODS_ROOT}/Target Support Files/Pods-playground/Pods-playground-frameworks.sh",
684-
"${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes",
684+
"${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermesvm.framework/hermesvm",
685685
);
686686
name = "[CP] Embed Pods Frameworks";
687687
outputPaths = (
688-
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework",
688+
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermesvm.framework",
689689
);
690690
runOnlyForDeploymentPostprocessing = 0;
691691
shellPath = /bin/sh;
@@ -769,11 +769,11 @@
769769
);
770770
inputPaths = (
771771
"${PODS_ROOT}/Target Support Files/Pods-NavigationIOS12Tests/Pods-NavigationIOS12Tests-frameworks.sh",
772-
"${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes",
772+
"${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermesvm.framework/hermesvm",
773773
);
774774
name = "[CP] Embed Pods Frameworks";
775775
outputPaths = (
776-
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework",
776+
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermesvm.framework",
777777
);
778778
runOnlyForDeploymentPostprocessing = 0;
779779
shellPath = /bin/sh;
@@ -813,11 +813,11 @@
813813
);
814814
inputPaths = (
815815
"${PODS_ROOT}/Target Support Files/Pods-NavigationTests/Pods-NavigationTests-frameworks.sh",
816-
"${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes",
816+
"${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermesvm.framework/hermesvm",
817817
);
818818
name = "[CP] Embed Pods Frameworks";
819819
outputPaths = (
820-
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework",
820+
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermesvm.framework",
821821
);
822822
runOnlyForDeploymentPostprocessing = 0;
823823
shellPath = /bin/sh;
@@ -879,11 +879,11 @@
879879
);
880880
inputPaths = (
881881
"${PODS_ROOT}/Target Support Files/Pods-SnapshotTests/Pods-SnapshotTests-frameworks.sh",
882-
"${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes",
882+
"${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermesvm.framework/hermesvm",
883883
);
884884
name = "[CP] Embed Pods Frameworks";
885885
outputPaths = (
886-
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework",
886+
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermesvm.framework",
887887
);
888888
runOnlyForDeploymentPostprocessing = 0;
889889
shellPath = /bin/sh;

0 commit comments

Comments
 (0)