diff --git a/.github/workflows/require-label.yml b/.github/workflows/require-label.yml index 7c70ba44745..7dfc51bdad7 100644 --- a/.github/workflows/require-label.yml +++ b/.github/workflows/require-label.yml @@ -8,6 +8,6 @@ jobs: enforce-label: runs-on: ubuntu-latest steps: - - uses: yogevbd/enforce-label-action@2.0.0 + - uses: yogevbd/enforce-label-action@2.2.1 with: REQUIRED_LABELS_ANY: "type: accepted/bug,type: accepted/enhancement,Infrastructure,type: documentation" diff --git a/.vscode/settings.json b/.vscode/settings.json index 101ab9b5f87..e6273c1ba74 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,5 +3,4 @@ "eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact"], "eslint.enable": true, "typescript.tsdk": "node_modules/typescript/lib", - "editor.formatOnSaveMode": "modifications" } diff --git a/e2e/Buttons.test.js b/e2e/Buttons.test.js index 1004835aab3..b9ac9e9023b 100644 --- a/e2e/Buttons.test.js +++ b/e2e/Buttons.test.js @@ -32,10 +32,10 @@ describe('Buttons', () => { it('custom button is clickable', async () => { await elementByLabel('Two').tap(); - await expect(elementByLabel('Thanks for that :)')).toExist(); + await expect(elementByLabel('Times created: 1')).toExist(); }); - it(':ios: Reseting buttons should unmount button react view', async () => { + it(':ios: Resetting buttons should unmount button react view', async () => { await elementById(TestIDs.SHOW_LIFECYCLE_BTN).tap(); await elementById(TestIDs.RESET_BUTTONS).tap(); await expect(elementByLabel('Button component unmounted')).toBeVisible(); @@ -56,4 +56,10 @@ describe('Buttons', () => { await elementById(TestIDs.ADD_BUTTON).tap(); await elementById(TestIDs.BUTTON_THREE).tap(); }); + + it('Button component is not recreated if it has a predefined componentId', async () => { + await elementById(TestIDs.ADD_BUTTON).tap(); + await elementById(TestIDs.ROUND_BUTTON).tap(); + await expect(elementByLabel('Times created: 1')).toBeVisible(); + }); }); diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/options/FadeAnimation.java b/lib/android/app/src/main/java/com/reactnativenavigation/options/FadeAnimation.java index c70bc682b8b..0d2f52c02a0 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/options/FadeAnimation.java +++ b/lib/android/app/src/main/java/com/reactnativenavigation/options/FadeAnimation.java @@ -4,11 +4,11 @@ import org.json.JSONObject; public class FadeAnimation extends NestedAnimationsOptions { - public FadeAnimation() { + public FadeAnimation(boolean reversed) { try { JSONObject alpha = new JSONObject(); - alpha.put("from", 0); - alpha.put("to", 1); + alpha.put("from", reversed ? 1 : 0); + alpha.put("to", reversed ? 0 : 1); alpha.put("duration", 300); JSONObject content = new JSONObject(); @@ -21,4 +21,8 @@ public FadeAnimation() { e.printStackTrace(); } } + + public FadeAnimation() { + this(false); + } } diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/StackAnimator.kt b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/StackAnimator.kt index 7f08a0a162e..51e522e64b4 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/StackAnimator.kt +++ b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/StackAnimator.kt @@ -73,7 +73,7 @@ open class StackAnimator @JvmOverloads constructor( } private suspend fun popWithElementTransitions(appearing: ViewController<*>, disappearing: ViewController<*>, pop: NestedAnimationsOptions, set: AnimatorSet) { - val fade = if (pop.content.isFadeAnimation()) pop else FadeAnimation() + val fade = if (pop.content.isFadeAnimation()) pop else FadeAnimation(true) val transitionAnimators = transitionAnimatorCreator.create(pop, fade.content, disappearing, appearing) set.playTogether(fade.content.getAnimation(disappearing.view), transitionAnimators) transitionAnimators.listeners.forEach { listener: Animator.AnimatorListener -> set.addListener(listener) } diff --git a/lib/ios/RNNLayoutManager.m b/lib/ios/RNNLayoutManager.m index a6c08de792f..16736f0b617 100644 --- a/lib/ios/RNNLayoutManager.m +++ b/lib/ios/RNNLayoutManager.m @@ -1,4 +1,3 @@ - #import "RNNLayoutManager.h" #import "RNNLayoutProtocol.h" #import "UIViewController+LayoutProtocol.h" @@ -6,8 +5,8 @@ @implementation RNNLayoutManager + (UIViewController *)findComponentForId:(NSString *)componentId { - for (UIWindow* window in UIApplication.sharedApplication.windows) { - UIViewController* result = [self findChildComponentForParent:window.rootViewController ForId:componentId]; + for (UIWindow *window in UIApplication.sharedApplication.windows) { + UIViewController *result = [self findChildComponentForParent:window.rootViewController forId:componentId]; if (result) { return result; } @@ -16,25 +15,20 @@ + (UIViewController *)findComponentForId:(NSString *)componentId { return nil; } -+ (UIViewController *)findChildComponentForParent:(UIViewController *)parentViewController ForId:(NSString *)componentId { ++ (UIViewController *)findChildComponentForParent:(UIViewController *)parentViewController forId:(NSString *)componentId { if ([parentViewController.layoutInfo.componentId isEqualToString:componentId]) { return parentViewController; } if (parentViewController.presentedViewController) { - if ([parentViewController.presentedViewController.layoutInfo.componentId isEqualToString:componentId]) { - return parentViewController.presentedViewController; - } - - UIViewController* modalResult = [self findChildComponentForParent:parentViewController.presentedViewController ForId:componentId]; + UIViewController *modalResult = [self findChildComponentForParent:parentViewController.presentedViewController forId:componentId]; if (modalResult) { return modalResult; } - } - for (UIViewController* childVC in parentViewController.childViewControllers) { - UIViewController* result = [self findChildComponentForParent:childVC ForId:componentId]; + for (UIViewController *childVC in parentViewController.childViewControllers) { + UIViewController *result = [self findChildComponentForParent:childVC forId:componentId]; if (result) { return result; } @@ -43,5 +37,4 @@ + (UIViewController *)findChildComponentForParent:(UIViewController *)parentView return nil; } - @end diff --git a/lib/src/commands/OptionsProcessor.test.ts b/lib/src/commands/OptionsProcessor.test.ts index 0e85221cefc..2cad86097ac 100644 --- a/lib/src/commands/OptionsProcessor.test.ts +++ b/lib/src/commands/OptionsProcessor.test.ts @@ -3,7 +3,7 @@ import { UniqueIdProvider } from '../adapters/UniqueIdProvider'; import { Store } from '../components/Store'; import { OptionProcessorsStore } from '../processors/OptionProcessorsStore'; import { Options, OptionsModalPresentationStyle } from '../interfaces/Options'; -import { mock, when, anyString, instance, anyNumber, verify } from 'ts-mockito'; +import { mock, when, instance, anyNumber, verify } from 'ts-mockito'; import { ColorService } from '../adapters/ColorService'; import { AssetService } from '../adapters/AssetResolver'; import { Deprecations } from './Deprecations'; @@ -25,7 +25,9 @@ describe('navigation options', () => { const assetService = instance(mockedAssetService); const mockedColorService = mock(ColorService) as ColorService; - when(mockedColorService.toNativeColor(anyString())).thenReturn(666); + when(mockedColorService.toNativeColor('red')).thenReturn(0xffff0000); + when(mockedColorService.toNativeColor('green')).thenReturn(0xff00ff00); + when(mockedColorService.toNativeColor('blue')).thenReturn(0xff0000ff); const colorService = instance(mockedColorService); optionProcessorsRegistry = new OptionProcessorsStore(); uut = new OptionsProcessor( @@ -172,8 +174,8 @@ describe('navigation options', () => { }; uut.processOptions(options, CommandName.SetRoot); expect(options).toEqual({ - statusBar: { backgroundColor: 666 }, - topBar: { background: { color: 666 } }, + statusBar: { backgroundColor: 0xffff0000 }, + topBar: { background: { color: 0xff0000ff } }, }); }); @@ -279,4 +281,41 @@ describe('navigation options', () => { } ); }); + + it('transform searchBar bool to object', () => { + const options = { topBar: { searchBar: true as any } }; + uut.processOptions(options, CommandName.SetRoot); + expect(options.topBar.searchBar).toStrictEqual({ + visible: true, + hideOnScroll: false, + hideTopBarOnFocus: false, + obscuresBackgroundDuringPresentation: false, + backgroundColor: null, + tintColor: null, + placeholder: '', + }); + }); + + it('transform searchBar bool to object and merges in deprecated values', () => { + const options = { + topBar: { + searchBar: true as any, + searchBarHiddenWhenScrolling: true, + hideNavBarOnFocusSearchBar: true, + searchBarBackgroundColor: 'red', + searchBarTintColor: 'green', + searchBarPlaceholder: 'foo', + }, + }; + uut.processOptions(options, CommandName.SetRoot); + expect(options.topBar.searchBar).toStrictEqual({ + visible: true, + hideOnScroll: true, + hideTopBarOnFocus: true, + obscuresBackgroundDuringPresentation: false, + backgroundColor: 0xffff0000, + tintColor: 0xff00ff00, + placeholder: 'foo', + }); + }); }); diff --git a/lib/src/commands/OptionsProcessor.ts b/lib/src/commands/OptionsProcessor.ts index 9b0799b6d32..1b5ad122fdd 100644 --- a/lib/src/commands/OptionsProcessor.ts +++ b/lib/src/commands/OptionsProcessor.ts @@ -10,7 +10,7 @@ import { Store } from '../components/Store'; import { UniqueIdProvider } from '../adapters/UniqueIdProvider'; import { ColorService } from '../adapters/ColorService'; import { AssetService } from '../adapters/AssetResolver'; -import { Options } from '../interfaces/Options'; +import { Options, OptionsSearchBar, OptionsTopBar } from '../interfaces/Options'; import { Deprecations } from './Deprecations'; import { OptionProcessorsStore } from '../processors/OptionProcessorsStore'; import { CommandName } from '../interfaces/CommandName'; @@ -49,7 +49,7 @@ export class OptionsProcessor { } private processObject( - objectToProcess: object, + objectToProcess: Record, parentOptions: object, onProcess: (key: string, parentOptions: object) => void, commandName: CommandName, @@ -72,8 +72,9 @@ export class OptionsProcessor { onProcess(key, parentOptions); - if (!isEqual(key, 'passProps') && (isObject(value) || isArray(value))) { - this.processObject(value, parentOptions, onProcess, commandName, objectPath); + const processedValue = objectToProcess[key]; + if (!isEqual(key, 'passProps') && (isObject(processedValue) || isArray(processedValue))) { + this.processObject(processedValue, parentOptions, onProcess, commandName, objectPath); } }); } @@ -138,30 +139,34 @@ export class OptionsProcessor { } } - private processSearchBar(key: string, value: any, options: Record) { - if (isEqual(key, 'searchBar')) { - typeof value === 'boolean' && this.deprecations.onProcessOptions(key, options, ''); + private processSearchBar(key: string, value: OptionsSearchBar | boolean, options: OptionsTopBar) { + if (key !== 'searchBar') { + return; + } + + const deprecatedSearchBarOptions: OptionsSearchBar = { + visible: false, + hideOnScroll: options.searchBarHiddenWhenScrolling ?? false, + hideTopBarOnFocus: options.hideNavBarOnFocusSearchBar ?? false, + obscuresBackgroundDuringPresentation: false, + backgroundColor: options.searchBarBackgroundColor, + tintColor: options.searchBarTintColor, + placeholder: options.searchBarPlaceholder ?? '', + }; + + if (typeof value === 'boolean') { + // Deprecated + this.deprecations.onProcessOptions(key, options, ''); + + options[key] = { + ...deprecatedSearchBarOptions, + visible: value, + }; + } else { options[key] = { - ...options[key], - visible: options[key].visible ?? value, - hiddenWhenScrolling: - options[key].hiddenWhenScrolling ?? options.searchBarHiddenWhenScrolling ?? false, - hideTopBarOnFocus: - options[key].hideTopBarOnFocus ?? options.hideNavBarOnFocusSearchBar ?? false, - obscuresBackgroundDuringPresentation: - options[key].obscuresBackgroundDuringPresentation ?? false, - placeholder: options[key].placeholder ?? options.searchBarPlaceholder ?? '', + ...deprecatedSearchBarOptions, + ...value, }; - this.processColor( - 'backgroundColor', - options[key].backgroundColor ?? options.searchBarBackgroundColor, - options[key] - ); - this.processColor( - 'tintColor', - options[key].tintColor ?? options.searchBarTintColor, - options[key] - ); } } diff --git a/lib/src/interfaces/Options.ts b/lib/src/interfaces/Options.ts index 6d2292291e3..141cf55142b 100644 --- a/lib/src/interfaces/Options.ts +++ b/lib/src/interfaces/Options.ts @@ -1105,6 +1105,17 @@ export interface ViewAnimationOptions extends ScreenAnimationOptions { id?: string; } +export interface ModalAnimationOptions extends ViewAnimationOptions { + /** + * Animations to be applied on elements which are shared between the appearing and disappearing screens + */ + sharedElementTransitions?: SharedElementTransition[]; + /** + * Animations to be applied on views in the appearing or disappearing screens + */ + elementTransitions?: ElementTransition[]; +} + /** * Used for describing stack commands animations. */ @@ -1163,11 +1174,11 @@ export interface AnimationOptions { /** * Configure what animates when modal is shown */ - showModal?: ViewAnimationOptions; + showModal?: ModalAnimationOptions; /** * Configure what animates when modal is dismissed */ - dismissModal?: ViewAnimationOptions; + dismissModal?: ModalAnimationOptions; } /** diff --git a/package.json b/package.json index 08033db9dbe..47789cb8412 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-navigation", - "version": "7.1.0", + "version": "7.2.0", "description": "React Native Navigation - truly native navigation for iOS and Android", "license": "MIT", "nativePackage": true, @@ -184,4 +184,4 @@ } } } -} +} \ No newline at end of file diff --git a/playground/src/screens/ButtonsScreen.tsx b/playground/src/screens/ButtonsScreen.tsx index ea2a357806e..1242a70663e 100644 --- a/playground/src/screens/ButtonsScreen.tsx +++ b/playground/src/screens/ButtonsScreen.tsx @@ -55,6 +55,7 @@ export default class ButtonOptions extends NavigationComponent { name: Screens.RoundButton, passProps: { title: 'Two', + timesCreated: 1, }, }, }, diff --git a/playground/src/screens/RoundedButton.tsx b/playground/src/screens/RoundedButton.tsx index b6af74764d7..905bb5d4fde 100644 --- a/playground/src/screens/RoundedButton.tsx +++ b/playground/src/screens/RoundedButton.tsx @@ -5,19 +5,24 @@ import Colors from '../commons/Colors'; interface Props extends NavigationComponentProps { title: string; + timesCreated?: number; } +let timesCreated = 0; export default class RoundedButton extends React.Component { constructor(props: Props) { super(props); Navigation.events().bindComponent(this); + timesCreated = props.timesCreated ?? timesCreated + 1; } render() { return ( - Alert.alert(this.props.title, 'Thanks for that :)')}> + Alert.alert(this.props.title, `Times created: ${timesCreated}`)} + > {this.props.title} diff --git a/scripts/test-e2e.js b/scripts/test-e2e.js index e4adef80728..fd72ccf6570 100644 --- a/scripts/test-e2e.js +++ b/scripts/test-e2e.js @@ -27,5 +27,6 @@ function run() { } exec.execSync( `detox test --configuration ${configuration} ${headless$} -w ${workers} ${loglevel}` - ); //-f "ScreenStyle.test.js" --loglevel trace + // "Buttons.test.js" --loglevel trace` + ); }