UNPKG

react-native-navigation

Version:

React Native Navigation - truly native navigation for iOS and Android

427 lines (395 loc) • 13.5 kB
import clone from 'lodash/clone'; import isEqual from 'lodash/isEqual'; import isObject from 'lodash/isObject'; import isArray from 'lodash/isArray'; import isString from 'lodash/isString'; import endsWith from 'lodash/endsWith'; import forEach from 'lodash/forEach'; import has from 'lodash/has'; import { Store } from '../components/Store'; import { UniqueIdProvider } from '../adapters/UniqueIdProvider'; import { ColorService } from '../adapters/ColorService'; import { AssetService } from '../adapters/AssetResolver'; import { AnimationOptions, EnterExitAnimationOptions, ModalAnimationOptions, OldModalAnimationOptions, Options, OptionsSearchBar, OptionsTopBar, StackAnimationOptions, StatusBarAnimationOptions, TopBarAnimationOptions, ViewAnimationOptions, } from '../interfaces/Options'; import { Deprecations } from './Deprecations'; import { OptionProcessorsStore } from '../processors/OptionProcessorsStore'; import { CommandName } from '../interfaces/CommandName'; import { Platform, DynamicColorIOS, ColorValue } from 'react-native'; export class OptionsProcessor { constructor( private store: Store, private uniqueIdProvider: UniqueIdProvider, private optionProcessorsRegistry: OptionProcessorsStore, private colorService: ColorService, private assetService: AssetService, private deprecations: Deprecations ) {} public processOptions(commandName: CommandName, options?: Options, props?: any) { if (options) { this.processObject( options, clone(options), (key, parentOptions) => { this.deprecations.onProcessOptions(key, parentOptions, commandName); this.deprecations.checkForDeprecatedOptions(parentOptions); }, commandName, props ); } } public processDefaultOptions(options: Options, commandName: CommandName) { this.processObject( options, clone(options), (key, parentOptions) => { this.deprecations.onProcessDefaultOptions(key, parentOptions); }, commandName ); } private processObject( objectToProcess: Record<string, any>, parentOptions: object, onProcess: (key: string, parentOptions: object) => void, commandName: CommandName, props?: any, parentPath?: string ) { forEach(objectToProcess, (value, key) => { const objectPath = this.resolveObjectPath(key, parentPath); this.processWithRegisteredProcessor( key, value, objectToProcess, objectPath, commandName, props ); this.processColor(key, value, objectToProcess); if (!value) { return; } this.processComponent(key, value, objectToProcess); this.processImage(key, value, objectToProcess); this.processButtonsPassProps(key, value); this.processSearchBar(key, value, objectToProcess); this.processInterpolation(key, value, objectToProcess); this.processAnimation(key, value, objectToProcess); onProcess(key, parentOptions); const processedValue = objectToProcess[key]; if (!isEqual(key, 'passProps') && (isObject(processedValue) || isArray(processedValue))) { this.processObject( processedValue, parentOptions, onProcess, commandName, props, objectPath ); } }); } private resolveObjectPath(key: string, path?: string): string { if (!path) path = key; else path += `.${key}`; return path; } private processColor(key: string, value: any, options: Record<string, any>) { if ((isEqual(key, 'color') || endsWith(key, 'Color')) && !isEqual(key, 'bkgColor')) { if (Platform.OS === 'ios') this.processColorIOS(key, value, options); else this.processColorAndroid(key, value, options); } } private processColorIOS(key: string, value: any, options: Record<string, any>) { if (value !== undefined) { if (value === null) { options[key] = 'NoColor'; } else if (value instanceof Object) { if ('semantic' in value) { options[key] = value; } else if ('dynamic' in value) { options[key] = DynamicColorIOS({ light: this.colorService.toNativeColor(value.dynamic.light) as ColorValue, dark: this.colorService.toNativeColor(value.dynamic.dark) as ColorValue, }); } else { options[key] = DynamicColorIOS({ light: this.colorService.toNativeColor(value.light) as ColorValue, dark: this.colorService.toNativeColor(value.dark) as ColorValue, }); } } else { options[key] = this.colorService.toNativeColor(value); } } } private processColorAndroid(key: string, value: any, options: Record<string, any>) { if (value !== undefined) { const newColorObj: Record<string, any> = { dark: 'NoColor', light: 'NoColor' }; if (value === null) { options[key] = newColorObj; } else if (value instanceof Object) { if ('semantic' in value || 'resource_paths' in value) { options[key] = value; return; } else { for (let keyColor in value) { newColorObj[keyColor] = this.colorService.toNativeColor(value[keyColor]); } options[key] = newColorObj; } } else { let parsedColor = this.colorService.toNativeColor(value); newColorObj.light = parsedColor; newColorObj.dark = parsedColor; options[key] = newColorObj; } } } private processWithRegisteredProcessor( key: string, value: string, options: Record<string, any>, path: string, commandName: CommandName, passProps: any ) { const registeredProcessors = this.optionProcessorsRegistry.getProcessors(path); if (registeredProcessors) { registeredProcessors.forEach((processor) => { options[key] = processor(value, commandName, passProps); }); } } private processImage(key: string, value: any, options: Record<string, any>) { if ( isEqual(key, 'icon') || isEqual(key, 'image') || endsWith(key, 'Icon') || endsWith(key, 'Image') ) { options[key] = isString(value) ? value : this.assetService.resolveFromRequire(value); } } private processButtonsPassProps(key: string, value: any) { if (endsWith(key, 'Buttons')) { forEach(value, (button) => { if (button.passProps && button.id) { this.store.setPendingProps(button.id, button.passProps); button.passProps = undefined; } }); } } private processComponent(key: string, value: any, options: Record<string, any>) { if (isEqual(key, 'component')) { value.componentId = value.id ? value.id : this.uniqueIdProvider.generate('CustomComponent'); this.store.ensureClassForName(value.name); if (value.passProps) { this.store.setPendingProps(value.componentId, value.passProps); } options[key].passProps = undefined; } } 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] = { ...deprecatedSearchBarOptions, ...value, }; } } private processInterpolation(key: string, value: any, options: Record<string, any>) { if (isEqual(key, 'interpolation')) { if (typeof value === 'string') { this.deprecations.onProcessOptions(key, options, ''); options[key] = { type: options[key], }; } } } private processAnimation(key: string, value: any, options: Record<string, any>) { this.processSetRootAnimation(key, value, options); this.processPush(key, value, options); this.processPop(key, value, options); this.processSetStackRoot(key, value, options); this.processShowModal(key, value, options); this.processDismissModal(key, value, options); } private processSetStackRoot( key: string, animation: ViewAnimationOptions | StackAnimationOptions, parentOptions: AnimationOptions ) { if (key !== 'setStackRoot') return; if (this.isNewStackAnimationApi(animation)) return; this.convertDeprecatedViewAnimationApiToNewStackAnimationApi(animation, parentOptions); } private isNewStackAnimationApi(animation: ViewAnimationOptions | StackAnimationOptions) { return has(animation, 'content') || has(animation, 'topBar') || has(animation, 'bottomTabs'); } private convertDeprecatedViewAnimationApiToNewStackAnimationApi( animation: ViewAnimationOptions | StackAnimationOptions, parentOptions: AnimationOptions ) { if (!has(animation, 'content.enter') && !has(animation, 'content.exit')) { parentOptions.setStackRoot = { content: { enter: animation, }, }; if (has(animation, 'enabled')) { parentOptions.setStackRoot!!.enabled = animation.enabled; } if (has(animation, 'waitForRender')) { parentOptions.setStackRoot!!.waitForRender = animation.waitForRender; } } } private processPop( key: string, animation: StackAnimationOptions, parentOptions: AnimationOptions ) { if (key !== 'pop') return; if (animation.content && !has(animation, 'content.enter') && !has(animation, 'content.exit')) { parentOptions.pop!!.content = { exit: animation.content as ViewAnimationOptions, }; } if (animation.topBar && !has(animation, 'topBar.enter') && !has(animation, 'topBar.exit')) { parentOptions.pop!!.topBar = { exit: animation.topBar as ViewAnimationOptions, }; } if ( animation.bottomTabs && !has(animation, 'bottomTabs.enter') && !has(animation, 'bottomTabs.exit') ) { parentOptions.pop!!.bottomTabs = { exit: animation.bottomTabs as ViewAnimationOptions, }; } } private processSetRootAnimation( key: string, animation: ViewAnimationOptions | EnterExitAnimationOptions, parentOptions: AnimationOptions ) { if (key !== 'setRoot') return; if (Platform.OS === 'android' && !('enter' in animation)) { parentOptions.setRoot = { enter: animation, } as EnterExitAnimationOptions; } else if (Platform.OS === 'ios' && 'enter' in animation) { parentOptions.setRoot = animation; } } private processShowModal( key: string, animation: OldModalAnimationOptions | ModalAnimationOptions, parentOptions: AnimationOptions ) { if (key !== 'showModal') return; if (!('enter' in animation)) { const elementTransitions = animation.elementTransitions; const sharedElementTransitions = animation.sharedElementTransitions; const enter = { ...(animation as OldModalAnimationOptions) }; delete enter.sharedElementTransitions; delete enter.elementTransitions; parentOptions.showModal = { enter, sharedElementTransitions, elementTransitions, }; } } private processDismissModal( key: string, animation: OldModalAnimationOptions | ModalAnimationOptions, parentOptions: AnimationOptions ) { if (key !== 'dismissModal') return; if (!('exit' in animation)) { const elementTransitions = animation.elementTransitions; const sharedElementTransitions = animation.sharedElementTransitions; const exit = { ...(animation as OldModalAnimationOptions) }; delete exit.sharedElementTransitions; delete exit.elementTransitions; parentOptions.dismissModal = { exit, sharedElementTransitions, elementTransitions, }; } } private processPush( key: string, animation: StackAnimationOptions, parentOptions: AnimationOptions ) { if (key !== 'push') return; if (animation.content && !has(animation, 'content.enter') && !has(animation, 'content.exit')) { parentOptions.push!!.content = { enter: animation.content as ViewAnimationOptions, }; } if (animation.topBar && !has(animation, 'topBar.enter') && !has(animation, 'topBar.exit')) { parentOptions.push!!.topBar = { enter: animation.topBar as TopBarAnimationOptions, }; } if ( animation.statusBar && !has(animation, 'statusBar.enter') && !has(animation, 'statusBar.exit') ) { parentOptions.push!!.statusBar = { enter: animation.statusBar as StatusBarAnimationOptions, }; } if ( animation.bottomTabs && !has(animation, 'bottomTabs.enter') && !has(animation, 'bottomTabs.exit') ) { parentOptions.push!!.bottomTabs = { enter: animation.bottomTabs as ViewAnimationOptions, }; } } }