react-native-maps
Version:
React Native Mapview component for iOS + Android
235 lines (208 loc) • 6.82 kB
text/typescript
import {createContext} from 'react';
import {
requireNativeComponent,
NativeModules,
Platform,
UIManager,
type HostComponent,
} from 'react-native';
import {PROVIDER_DEFAULT, PROVIDER_GOOGLE} from './ProviderConstants';
import type {Provider} from './sharedTypes';
import {MapCallout} from './MapCallout';
import {MapOverlay} from './MapOverlay';
import {MapCalloutSubview} from './MapCalloutSubview';
import {MapCircle} from './MapCircle';
import {MapHeatmap} from './MapHeatmap';
import {MapLocalTile} from './MapLocalTile';
import {MapMarker} from './MapMarker';
import {MapPolygon} from './MapPolygon';
import {MapPolyline} from './MapPolyline';
import {MapUrlTile} from './MapUrlTile';
import {MapWMSTile} from './MapWMSTile';
import {Commands} from './MapViewNativeComponent';
import GooglePolygon from './specs/NativeComponentGooglePolygon';
import FabricMarker from './specs/NativeComponentMarker';
import FabricUrlTile from './specs/NativeComponentUrlTile';
import FabricWMSTile from './specs/NativeComponentWMSTile';
import FabricCallout from './specs/NativeComponentCallout';
import FabricPolyline from './specs/NativeComponentPolyline';
import FabricCircle from './specs/NativeComponentCircle';
import FabricOverlay from './specs/NativeComponentOverlay';
export const SUPPORTED: ImplementationStatus = 'SUPPORTED';
export const USES_DEFAULT_IMPLEMENTATION: ImplementationStatus =
'USES_DEFAULT_IMPLEMENTATION';
export const NOT_SUPPORTED: ImplementationStatus = 'NOT_SUPPORTED';
export const ProviderContext = createContext<Provider>(undefined);
export function getNativeMapName(provider: Provider) {
if (Platform.OS === 'android') {
return 'AIRMap';
}
if (provider === PROVIDER_GOOGLE) {
return 'AIRGoogleMap';
}
return 'AIRMap';
}
function getNativeComponentName(provider: Provider, component: ComponentName) {
return `${getNativeMapName(provider)}${component}`;
}
export const createNotSupportedComponent = (message: string): (() => null) => {
return () => {
console.error(message);
return null;
};
};
export const googleMapIsInstalled = !!UIManager.hasViewManagerConfig(
getNativeMapName(PROVIDER_GOOGLE),
);
export default function decorateMapComponent<Type extends Component>(
Component: Type,
componentName: ComponentName,
providers: Providers,
): Type {
const components: {
[key: string]: NativeComponent;
} = {};
const getDefaultComponent = () =>
requireNativeComponent(getNativeComponentName(undefined, componentName));
Component.contextType = ProviderContext;
Component.prototype.getNativeComponent =
function getNativeComponent(): NativeComponent {
const provider = this.context;
if (
componentName === 'Marker' &&
(Platform.OS !== 'ios' || provider !== PROVIDER_GOOGLE)
) {
// @ts-ignore
return FabricMarker;
}
if (
componentName === 'Polygon' &&
((provider === PROVIDER_GOOGLE &&
Platform.OS === 'ios' &&
googleMapIsInstalled) ||
Platform.OS === 'android')
) {
// @ts-ignore
return GooglePolygon;
}
if (Platform.OS === 'android') {
if (componentName === 'Callout') {
// @ts-ignore
return FabricCallout;
} else if (componentName === 'Polyline') {
// @ts-ignore
return FabricPolyline;
} else if (componentName === 'Circle') {
// @ts-ignore
return FabricCircle;
} else if (componentName === 'Overlay') {
// @ts-ignore
return FabricOverlay;
} else if (componentName === 'UrlTile') {
// @ts-ignore
return FabricUrlTile;
} else if (componentName === 'WMSTile') {
// @ts-ignore
return FabricWMSTile;
}
}
const key = provider || 'default';
if (components[key]) {
return components[key];
}
if (provider === PROVIDER_DEFAULT) {
components[key] = getDefaultComponent();
return components[key];
}
if (!provider) {
throw new Error('react-native-maps: provider is not set');
}
const providerInfo = providers[provider];
// quick fix. Previous code assumed android | ios
if (Platform.OS !== 'android' && Platform.OS !== 'ios') {
throw new Error(`react-native-maps doesn't support ${Platform.OS}`);
}
const platformSupport = providerInfo[Platform.OS];
const nativeComponentName = getNativeComponentName(
provider,
componentName,
);
if (platformSupport === NOT_SUPPORTED) {
components[key] = createNotSupportedComponent(
`react-native-maps: ${nativeComponentName} is not supported on ${Platform.OS}`,
);
} else if (platformSupport === SUPPORTED) {
if (
provider !== PROVIDER_GOOGLE ||
(Platform.OS === 'ios' && googleMapIsInstalled)
) {
components[key] = requireNativeComponent(nativeComponentName);
}
} else {
// (platformSupport === USES_DEFAULT_IMPLEMENTATION)
if (!components.default) {
components.default = getDefaultComponent();
}
components[key] = components.default;
}
return components[key];
};
Component.prototype.getUIManagerCommand = function getUIManagerCommand(
name: string,
): UIManagerCommand {
const nativeComponentName = getNativeComponentName(
this.context,
componentName,
);
return UIManager.getViewManagerConfig(nativeComponentName).Commands[name];
};
Component.prototype.getMapManagerCommand = function getMapManagerCommand(
name: string,
): MapManagerCommand {
const nativeComponentName = `${getNativeComponentName(
this.context,
componentName,
)}Manager`;
return NativeModules[nativeComponentName][name];
};
return Component;
}
type ImplementationStatus =
| 'SUPPORTED'
| 'USES_DEFAULT_IMPLEMENTATION'
| 'NOT_SUPPORTED';
type Providers = {
google: {
ios: ImplementationStatus;
android: ImplementationStatus;
};
};
export type UIManagerCommand = number;
export type MapManagerCommand = keyof typeof Commands;
export type NativeComponent<H = unknown> =
| HostComponent<H>
| ReturnType<typeof createNotSupportedComponent>;
type Component =
| typeof MapCallout
| typeof MapCalloutSubview
| typeof MapCircle
| typeof MapHeatmap
| typeof MapLocalTile
| typeof MapMarker
| typeof MapOverlay
| typeof MapPolygon
| typeof MapPolyline
| typeof MapUrlTile
| typeof MapWMSTile;
type ComponentName =
| 'Callout'
| 'CalloutSubview'
| 'Circle'
| 'Heatmap'
| 'LocalTile'
| 'Marker'
| 'Overlay'
| 'Polygon'
| 'Polyline'
| 'UrlTile'
| 'WMSTile';