diff --git a/README.md b/README.md index 706bd8a..7c67466 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,11 @@ # react-native-google-maps-plus [![npm version](https://img.shields.io/npm/v/react-native-google-maps-plus.svg?logo=npm&color=cb0000)](https://www.npmjs.com/package/react-native-google-maps-plus) -[![Dev Release](https://img.shields.io/npm/v/react-native-google-maps-plus/dev.svg?label=dev%20release&color=orange&logo=githubactions)](https://www.npmjs.com/package/react-native-google-maps-plus) -[![Release](https://github.com/pinpong/react-native-google-maps-plus/actions/workflows/release.yml/badge.svg)](https://github.com/pinpong/react-native-google-maps-plus/actions/workflows/release.yml) -[![Issues](https://img.shields.io/github/issues/pinpong/react-native-google-maps-plus?logo=github)](https://github.com/pinpong/react-native-google-maps-plus/issues) -[![License](https://img.shields.io/github/license/pinpong/react-native-google-maps-plus?logo=open-source-initiative&logoColor=green)](./LICENSE) -[![Code Style: Prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?logo=prettier&logoColor=white)](https://prettier.io/) -[![TypeScript](https://img.shields.io/badge/%3C/%3E-TypeScript-blue.svg?logo=typescript)](https://www.typescriptlang.org/) -[![Lint](https://img.shields.io/badge/lint-eslint-green.svg?logo=eslint&logoColor=white)](https://eslint.org/) -[![React Native](https://img.shields.io/badge/react--native-%3E%3D0.81.0-61dafb.svg?logo=react)](https://reactnative.dev/) -[![Platform: Android](https://img.shields.io/badge/platform-android-green.svg?logo=android&logoColor=white)](https://developer.android.com/) -[![Platform: iOS](https://img.shields.io/badge/platform-iOS-lightgrey.svg?logo=apple&logoColor=black)](https://developer.apple.com/ios/) +[![Dev Release](https://img.shields.io/npm/v/react-native-google-maps-plus/dev.svg?label=dev%20release&color=orange)](https://www.npmjs.com/package/react-native-google-maps-plus) +[![Build](https://github.com/pinpong/react-native-google-maps-plus/actions/workflows/release.yml/badge.svg)](https://github.com/pinpong/react-native-google-maps-plus/actions/workflows/release.yml) +![React Native](https://img.shields.io/badge/react--native-%3E%3D0.81.0-61dafb.svg?logo=react) +![Platform: Android](https://img.shields.io/badge/android-supported-brightgreen.svg?logo=android&logoColor=white) +![Platform: iOS](https://img.shields.io/badge/ios-supported-lightgrey.svg?logo=apple&logoColor=black) React Native wrapper for Android & iOS Google Maps SDK. @@ -22,6 +17,58 @@ React Native wrapper for Android & iOS Google Maps SDK. yarn add react-native-google-maps-plus react-native-nitro-modules ``` +**iOS** + +Add this to your Podfile only for bare React Native apps. +(Not required for Expo, handled by the config plugin.) + +```ruby +post_install do |installer| + react_native_post_install( + installer, + config[:reactNativePath], + :mac_catalyst_enabled => false, + ) + # Force iOS 16+ to avoid deployment target warnings + installer.pods_project.targets.each do |target| + target.build_configurations.each do |config| + config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '16.0' + end + end + + # --- SVGKit Patch --- + require 'fileutils' + svgkit_path = File.join(installer.sandbox.pod_dir('SVGKit'), 'Source') + + # node fix + Dir.glob(File.join(svgkit_path, '**', '*.{h,m}')).each do |file| + FileUtils.chmod("u+w", file) + text = File.read(file) + new_contents = text.gsub('#import "Node.h"', '#import "SVGKit/Node.h"') + File.open(file, 'w') { |f| f.write(new_contents) } + # puts "Patched Node import in: #{file}" + end + + # import CSSValue.h + Dir.glob(File.join(svgkit_path, '**', '*.{h,m}')).each do |file| + FileUtils.chmod("u+w", file) + text = File.read(file) + new_contents = text.gsub('#import "CSSValue.h"', '#import "SVGKit/CSSValue.h"') + File.open(file, 'w') { |f| f.write(new_contents) } + # puts "Patched CSSValue import in: #{file}" + end + + # import SVGLength.h + Dir.glob(File.join(svgkit_path, '**', '*.{h,m}')).each do |file| + FileUtils.chmod("u+w", file) + text = File.read(file) + new_contents = text.gsub('#import "SVGLength.h"', '#import "SVGKit/SVGLength.h"') + File.open(file, 'w') { |f| f.write(new_contents) } + # puts "Patched SVGLength import in: #{file}" + end +end +``` + ### Expo Projects Add your keys to the `app.json`. @@ -43,148 +90,79 @@ The config plugin automatically injects them into your native Android and iOS bu } ``` -# Dependencies - -This package builds on native libraries for SVG rendering and Google Maps integration: - -- **iOS**: [SVGKit](https://github.com/SVGKit/SVGKit) -- **Android**: [AndroidSVG](https://bigbadaboom.github.io/androidsvg/) -- **iOS Maps SDK**: [Google Maps SDK for iOS](https://developers.google.com/maps/documentation/ios-sdk) -- **Android Maps SDK**: [Google Maps SDK for Android](https://developers.google.com/maps/documentation/android-sdk) -- **Maps Utility Libraries**: [Google Maps Utils for iOS](https://developers.google.com/maps/documentation/ios-sdk/utility) and [Google Maps Utils for Android](https://developers.google.com/maps/documentation/android-sdk/utility) - -These are automatically linked when you install the package, but you may need to clean/rebuild your native projects after first install. - ## Setup API Key You will need a valid **Google Maps API Key** from the [Google Cloud Console](https://console.cloud.google.com/). ### Android -It's recommend to use [Secrets Gradle Plugin](https://developers.google.com/maps/documentation/android-sdk/secrets-gradle-plugin) to securely manage your Google Maps API Key. +**Note:** These instructions apply to **bare React Native apps only**. +Expo projects should use the config plugin instead (see Expo section above). ---- +See the official [Google Maps Android SDK configuration guide](https://developers.google.com/maps/documentation/android-sdk/config#step_3_add_your_api_key_to_the_project) for more details. ### iOS -See the official [Google Maps iOS SDK configuration guide](https://developers.google.com/maps/documentation/ios-sdk/config#get-key) for more details. +**Note:** These instructions apply to **bare React Native apps only**. +Expo projects should use the config plugin instead (see Expo section above). -1. Create a `Secrets.xcconfig` file inside the **ios/** folder: +See the official [Google Maps iOS SDK configuration guide](https://developers.google.com/maps/documentation/ios-sdk/config#get-key) for more details. - ```properties - MAPS_API_KEY=YOUR_IOS_MAPS_API_KEY - ``` +## Dependencies & Native Documentation - Include it in your project configuration file: +This package is React Native wrapper around the official Google Maps SDKs. +For full API behavior, configuration options, and feature reference, please consult the native documentation: - ```xcconfig - #include? "Secrets.xcconfig" - ``` +- **iOS Google Maps SDK** + https://developers.google.com/maps/documentation/ios-sdk -2. Reference the API key in your **Info.plist**: +- **Android Google Maps SDK** + https://developers.google.com/maps/documentation/android-sdk - ```xml - MAPS_API_KEY - $(MAPS_API_KEY) - ``` +- **Maps Utility Libraries (iOS & Android)** + https://developers.google.com/maps/documentation/ios-sdk/utility + https://developers.google.com/maps/documentation/android-sdk/utility -3. Provide the key programmatically in **AppDelegate.swift**: +- **SVG Rendering** (used for custom marker icons) + - iOS: https://github.com/SVGKit/SVGKit + - Android: https://bigbadaboom.github.io/androidsvg/ - ```swift - import GoogleMaps +These libraries are automatically linked during installation. +If you encounter build issues, try cleaning and rebuilding your native project. - @UIApplicationMain - class AppDelegate: UIResponder, UIApplicationDelegate { - func application(_ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - if let apiKey = Bundle.main.object(forInfoDictionaryKey: "MAPS_API_KEY") as? String { - GMSServices.provideAPIKey(apiKey) - } - return true - } - } - ``` - ---- +> **Note:** This package follows the native SDKs closely. Props and behavior match the underlying Google Maps APIs whenever possible. ## Usage -Checkout the example app in the [example](./example) folder. - -# Troubleshooting - -## Android - -- **API key not found** - Make sure `secrets.properties` exists under `android/` and contains your `MAPS_API_KEY`. - Run `./gradlew clean` and rebuild. - -## iOS - -- **`GMSServices must be configured before use`** - Ensure your key is in `Info.plist` and/or provided via `GMSServices.provideAPIKey(...)` in `AppDelegate.swift`. - -- **Build fails with `Node.h`, `CSSValue.h`, or `SVGLength.h` import errors from SVGKit** - SVGKit includes headers (`Node.h`, `CSSValue.h`, `SVGLength.h`) that can conflict with - iOS system headers and React Native Reanimated’s internal types. - You can patch them automatically in your **Podfile** inside the `post_install` - - ```ruby - post_install do |installer| - react_native_post_install( - installer, - config[:reactNativePath], - :mac_catalyst_enabled => false, - ) - # Force iOS 16+ to avoid deployment target warnings - installer.pods_project.targets.each do |target| - target.build_configurations.each do |config| - config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '16.0' - end - end - - # --- SVGKit Patch --- - require 'fileutils' - svgkit_path = File.join(installer.sandbox.pod_dir('SVGKit'), 'Source') - - # node fix - Dir.glob(File.join(svgkit_path, '**', '*.{h,m}')).each do |file| - FileUtils.chmod("u+w", file) - text = File.read(file) - new_contents = text.gsub('#import "Node.h"', '#import "SVGKit/Node.h"') - File.open(file, 'w') { |f| f.write(new_contents) } - # puts "Patched Node import in: #{file}" - end - - # import CSSValue.h - Dir.glob(File.join(svgkit_path, '**', '*.{h,m}')).each do |file| - FileUtils.chmod("u+w", file) - text = File.read(file) - new_contents = text.gsub('#import "CSSValue.h"', '#import "SVGKit/CSSValue.h"') - File.open(file, 'w') { |f| f.write(new_contents) } - # puts "Patched CSSValue import in: #{file}" - end - - # import SVGLength.h - Dir.glob(File.join(svgkit_path, '**', '*.{h,m}')).each do |file| - FileUtils.chmod("u+w", file) - text = File.read(file) - new_contents = text.gsub('#import "SVGLength.h"', '#import "SVGKit/SVGLength.h"') - File.open(file, 'w') { |f| f.write(new_contents) } - # puts "Patched SVGLength import in: #{file}" - end - end - ``` - - After applying this, run: - - ```sh - cd ios && pod install --repo-update - ``` +Basic map: + +```tsx +import React from 'react'; +import { GoogleMapsView } from 'react-native-google-maps-plus'; + +export default function App() { + return ( + + ); +} +``` -- **Maps not rendering** - - Check that your API key has **Maps SDK for Android/iOS** enabled in Google Cloud Console. - - Make sure the key is not restricted to wrong bundle IDs or SHA1 fingerprints. +Check out the example app in the [example](./example) folder. ## Contributing diff --git a/android/src/main/java/com/rngooglemapsplus/extensions/RNUserInterfaceExtension.kt b/android/src/main/java/com/rngooglemapsplus/extensions/RNUserInterfaceExtension.kt index 8fd4c5a..7e143b2 100644 --- a/android/src/main/java/com/rngooglemapsplus/extensions/RNUserInterfaceExtension.kt +++ b/android/src/main/java/com/rngooglemapsplus/extensions/RNUserInterfaceExtension.kt @@ -7,6 +7,6 @@ fun RNUserInterfaceStyle?.toMapColorScheme(): Int? = when (this) { RNUserInterfaceStyle.LIGHT -> MapColorScheme.LIGHT RNUserInterfaceStyle.DARK -> MapColorScheme.DARK - RNUserInterfaceStyle.DEFAULT -> MapColorScheme.FOLLOW_SYSTEM + RNUserInterfaceStyle.SYSTEM -> MapColorScheme.FOLLOW_SYSTEM null -> null } diff --git a/example/src/components/ControlPanel.tsx b/example/src/components/ControlPanel.tsx index 4dc96b3..60c6f1a 100644 --- a/example/src/components/ControlPanel.tsx +++ b/example/src/components/ControlPanel.tsx @@ -17,6 +17,11 @@ import type { GoogleMapsViewRef } from 'react-native-google-maps-plus'; import { useAppTheme } from '../hooks/useAppTheme'; import { useNavigation } from '@react-navigation/native'; import type { RootNavigationProp } from '../types/navigation'; +import { + type EdgeInsets, + useSafeAreaInsets, +} from 'react-native-safe-area-context'; +import type { AppTheme } from '../theme'; export type ButtonItem = { title: string; onPress: () => void }; @@ -29,7 +34,8 @@ export default function ControlPanel({ mapRef, buttons }: Props) { const theme = useAppTheme(); const navigation = useNavigation(); const progress = useSharedValue(0); - const styles = useMemo(() => getThemedStyles(theme), [theme]); + const layout = useSafeAreaInsets(); + const styles = useMemo(() => getThemedStyles(theme, layout), [layout, theme]); const toggle = () => { progress.value = withTiming(progress.value === 1 ? 0 : 1, { @@ -124,7 +130,7 @@ export default function ControlPanel({ mapRef, buttons }: Props) { ); } -const getThemedStyles = (theme: any) => +const getThemedStyles = (theme: AppTheme, layout: EdgeInsets) => StyleSheet.create({ scrollView: { position: 'absolute', @@ -136,7 +142,7 @@ const getThemedStyles = (theme: any) => backgroundColor: theme.bgPrimary, }, scrollContent: { - paddingBottom: 40, + paddingBottom: layout.bottom + 8, }, header: { borderRadius: 10, diff --git a/example/src/components/HeaderButton.tsx b/example/src/components/HeaderButton.tsx index 2b988c2..69ca230 100644 --- a/example/src/components/HeaderButton.tsx +++ b/example/src/components/HeaderButton.tsx @@ -1,6 +1,7 @@ import React, { useMemo } from 'react'; import { Pressable, StyleSheet, Text } from 'react-native'; import { useAppTheme } from '../hooks/useAppTheme'; +import type { AppTheme } from '../theme'; type Props = { title: string; @@ -18,7 +19,7 @@ export default function HeaderButton({ title, onPress }: Props) { ); } -const getThemedStyles = (theme: any) => +const getThemedStyles = (theme: AppTheme) => StyleSheet.create({ headerButton: { paddingHorizontal: 12, diff --git a/example/src/components/MapWrapper.tsx b/example/src/components/MapWrapper.tsx index 81415ce..b0fa0d3 100644 --- a/example/src/components/MapWrapper.tsx +++ b/example/src/components/MapWrapper.tsx @@ -25,7 +25,8 @@ import { } from 'react-native-google-maps-plus'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { callback } from 'react-native-nitro-modules'; -import { useTheme } from '@react-navigation/native'; +import type { AppTheme } from '../theme'; +import { useAppTheme } from '../hooks/useAppTheme'; type Props = ViewProps & RNGoogleMapsPlusViewProps & { @@ -47,7 +48,7 @@ function wrapCallback void>( export default function MapWrapper(props: Props) { const { children, ...rest } = props; - const theme = useTheme(); + const theme = useAppTheme(); const styles = useMemo(() => getThemedStyles(theme), [theme]); const layout = useSafeAreaInsets(); @@ -119,7 +120,8 @@ export default function MapWrapper(props: Props) { indoorEnabled={props.indoorEnabled ?? false} style={[styles.map, props.style]} userInterfaceStyle={ - props.userInterfaceStyle ?? (theme.dark ? 'dark' : 'light') + props.userInterfaceStyle ?? + (theme.theme === 'dark' ? 'dark' : 'light') } mapType={props.mapType ?? 'normal'} mapZoomConfig={props.mapZoomConfig ?? mapZoomConfig} @@ -231,18 +233,18 @@ export default function MapWrapper(props: Props) { {children} {!mapLoaded && ( - + )} ); } -const getThemedStyles = (theme: any) => +const getThemedStyles = (theme: AppTheme) => StyleSheet.create({ container: { flex: 1, - backgroundColor: theme.background, + backgroundColor: theme.bgPrimary, }, map: { position: 'absolute', diff --git a/example/src/components/maptConfigDialog/MapConfigDialog.tsx b/example/src/components/maptConfigDialog/MapConfigDialog.tsx index a348214..39bd26a 100644 --- a/example/src/components/maptConfigDialog/MapConfigDialog.tsx +++ b/example/src/components/maptConfigDialog/MapConfigDialog.tsx @@ -17,6 +17,7 @@ import { parseWithUndefined, stringifyWithUndefined, } from './utils'; +import type { AppTheme } from '../../theme'; type Props = { visible: boolean; @@ -152,7 +153,7 @@ export default function MapConfigDialog({ ); } -const getThemedStyles = (theme: any) => +const getThemedStyles = (theme: AppTheme) => StyleSheet.create({ overlay: { flex: 1, @@ -176,9 +177,10 @@ const getThemedStyles = (theme: any) => alignItems: 'center', paddingHorizontal: 16, paddingVertical: 12, + borderRadius: 16, borderBottomWidth: StyleSheet.hairlineWidth, borderColor: theme.border, - backgroundColor: theme.bgSecondary, + backgroundColor: theme.bgPrimary, }, headerActions: { flexDirection: 'row', gap: 8 }, headerButtonText: { diff --git a/example/src/components/maptConfigDialog/validator.ts b/example/src/components/maptConfigDialog/validator.ts index 80c6b91..e13e473 100644 --- a/example/src/components/maptConfigDialog/validator.ts +++ b/example/src/components/maptConfigDialog/validator.ts @@ -97,7 +97,7 @@ export const RNMapTypeValidator = unionWithValues( export const RNUserInterfaceStyleValidator = unionWithValues( 'light', 'dark', - 'default' + 'system' ); export const RNPositionValidator = object({ diff --git a/example/src/screens/BasicMapScreen.tsx b/example/src/screens/BasicMapScreen.tsx index 85bc51a..042b6b1 100644 --- a/example/src/screens/BasicMapScreen.tsx +++ b/example/src/screens/BasicMapScreen.tsx @@ -45,8 +45,7 @@ export default function BasicMapScreen() { buildingEnabled: undefined, trafficEnabled: undefined, indoorEnabled: undefined, - customMapStyle: '', - userInterfaceStyle: 'default', + customMapStyle: undefined, mapZoomConfig: { min: 0, max: 20 }, mapPadding: { top: 20, left: 20, bottom: layout.bottom + 80, right: 20 }, mapType: 'normal', diff --git a/example/src/screens/BlankScreen.tsx b/example/src/screens/BlankScreen.tsx index 8bf6cd2..51e557c 100644 --- a/example/src/screens/BlankScreen.tsx +++ b/example/src/screens/BlankScreen.tsx @@ -1,57 +1,62 @@ -import React from 'react'; +import React, { useMemo } from 'react'; import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'; -import { useNavigation, useTheme } from '@react-navigation/native'; +import { useNavigation } from '@react-navigation/native'; import type { RootNavigationProp } from '../types/navigation'; +import { useAppTheme } from '../hooks/useAppTheme'; +import type { AppTheme } from '../theme'; export default function BlankScreen() { const navigation = useNavigation(); - const { colors } = useTheme(); + const theme = useAppTheme(); + const styles = useMemo(() => getThemedStyles(theme), [theme]); return ( - - Blank Screen + + Blank Screen - - This is an empty placeholder screen. - + This is an empty placeholder screen. navigation.goBack()} > - - ← Go Back - + Go Back ); } -const styles = StyleSheet.create({ - container: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - padding: 24, - }, - title: { - fontSize: 24, - fontWeight: '700', - marginBottom: 8, - }, - subtitle: { - fontSize: 16, - opacity: 0.8, - marginBottom: 32, - }, - button: { - paddingHorizontal: 24, - paddingVertical: 12, - borderRadius: 10, - }, - buttonText: { - fontSize: 16, - fontWeight: '500', - }, -}); +const getThemedStyles = (theme: AppTheme) => + StyleSheet.create({ + container: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + padding: 24, + backgroundColor: theme.bgPrimary, + }, + title: { + color: theme.textPrimary, + fontSize: 24, + fontWeight: '700', + marginBottom: 8, + }, + subtitle: { + color: theme.textPrimary, + fontSize: 16, + opacity: 0.8, + marginBottom: 32, + }, + button: { + color: theme.buttonBg, + paddingHorizontal: 24, + paddingVertical: 12, + borderRadius: 10, + }, + buttonText: { + color: theme.textPrimary, + fontSize: 16, + fontWeight: '500', + }, + }); diff --git a/example/src/screens/HomeScreen.tsx b/example/src/screens/HomeScreen.tsx index 580ed4a..47dc544 100644 --- a/example/src/screens/HomeScreen.tsx +++ b/example/src/screens/HomeScreen.tsx @@ -1,8 +1,16 @@ import React, { useMemo } from 'react'; import { ScrollView, StyleSheet, Text, TouchableOpacity } from 'react-native'; -import type { StackNavigationProp } from '@react-navigation/stack'; import { useNavigation } from '@react-navigation/native'; import { useAppTheme } from '../hooks/useAppTheme'; +import type { AppTheme } from '../theme'; +import type { + RootNavigationProp, + RootStackParamList, +} from '../types/navigation'; +import { + type EdgeInsets, + useSafeAreaInsets, +} from 'react-native-safe-area-context'; const screens = [ { name: 'BasicMap', title: 'Basic Map' }, @@ -24,9 +32,10 @@ const screens = [ ]; export default function HomeScreen() { - const navigation = useNavigation>(); + const navigation = useNavigation(); const theme = useAppTheme(); - const styles = useMemo(() => getThemedStyles(theme), [theme]); + const layout = useSafeAreaInsets(); + const styles = useMemo(() => getThemedStyles(theme, layout), [theme, layout]); return ( @@ -35,7 +44,9 @@ export default function HomeScreen() { navigation.navigate(s.name)} + onPress={() => + navigation.navigate(s.name as keyof RootStackParamList) + } activeOpacity={0.85} > {s.title} @@ -45,13 +56,13 @@ export default function HomeScreen() { ); } -const getThemedStyles = (theme: any) => +const getThemedStyles = (theme: AppTheme, layout: EdgeInsets) => StyleSheet.create({ container: { flexGrow: 1, alignItems: 'center', justifyContent: 'center', - paddingVertical: 40, + paddingVertical: layout.bottom + 8, backgroundColor: theme.bgPrimary, }, title: { diff --git a/example/src/screens/MarkersScreen.tsx b/example/src/screens/MarkersScreen.tsx index f308f83..1668342 100644 --- a/example/src/screens/MarkersScreen.tsx +++ b/example/src/screens/MarkersScreen.tsx @@ -3,6 +3,7 @@ import MapWrapper from '../components/MapWrapper'; import { makeMarker } from '../utils/mapGenerators'; import type { GoogleMapsViewRef, + RNLatLng, RNMarker, } from 'react-native-google-maps-plus'; import MapConfigDialog from '../components/maptConfigDialog/MapConfigDialog'; @@ -10,13 +11,44 @@ import { useNavigation } from '@react-navigation/native'; import { RNMarkerValidator } from '../components/maptConfigDialog/validator'; import { useHeaderButton } from '../hooks/useHeaderButton'; import type { RNMapUiSettings } from 'react-native-google-maps-plus'; +import ControlPanel from '../components/ControlPanel'; + +export function animateSpiral( + center: RNLatLng, + duration: number, + onUpdate: (c: RNLatLng) => void, + onFinish: () => void, + opts = { rotations: 10, startRadius: 0.0001, endRadius: 0.002 } +) { + const { rotations, startRadius, endRadius } = opts; + const start = performance.now(); + + const ease = (t: number) => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t); + + const loop = (now: number) => { + const t = Math.min((now - start) / duration, 1); + const e = ease(t); + + const r = startRadius + (endRadius - startRadius) * e; + const angle = e * rotations * 2 * Math.PI; + + onUpdate({ + latitude: center.latitude + Math.cos(angle) * r, + longitude: center.longitude + Math.sin(angle) * r, + }); + + t < 1 ? requestAnimationFrame(loop) : onFinish(); + }; + + requestAnimationFrame(loop); +} export default function MarkersScreen() { const mapRef = useRef(null); const navigation = useNavigation(); const [markers, setMarkers] = useState(undefined); const [dialogVisible, setDialogVisible] = useState(true); - + const animatingRef = useRef(false); const uiSettings: RNMapUiSettings = useMemo( () => ({ allGesturesEnabled: true, @@ -40,6 +72,32 @@ export default function MarkersScreen() { setDialogVisible(true) ); + const buttons = useMemo( + () => [ + { + title: 'Animate Marker', + onPress: () => { + if (animatingRef.current) return; + if (!markers || !markers[0]) return; + const center = markers[0].coordinate; + const id = markers[0].id; + animatingRef.current = true; + animateSpiral( + center, + 5000, + (coordinate: RNLatLng) => { + setMarkers((prev) => + prev?.map((m) => (m.id === id ? { ...m, coordinate } : m)) + ); + }, + () => (animatingRef.current = false) + ); + }, + }, + ], + [markers] + ); + return ( <> mapRef.current?.showMarkerInfoWindow(id)} - /> + > + + visible={dialogVisible} title="Edit marker" diff --git a/example/src/screens/SnaptshotTestScreen.tsx b/example/src/screens/SnaptshotTestScreen.tsx index 4e760b8..7468c27 100644 --- a/example/src/screens/SnaptshotTestScreen.tsx +++ b/example/src/screens/SnaptshotTestScreen.tsx @@ -4,6 +4,7 @@ import MapWrapper from '../components/MapWrapper'; import ControlPanel from '../components/ControlPanel'; import { useAppTheme } from '../hooks/useAppTheme'; import type { GoogleMapsViewRef } from 'react-native-google-maps-plus'; +import type { AppTheme } from '../theme'; export default function SnapshotTestScreen() { const mapRef = useRef(null); @@ -90,7 +91,7 @@ export default function SnapshotTestScreen() { ); } -const getThemedStyles = (theme: any) => +const getThemedStyles = (theme: AppTheme) => StyleSheet.create({ backdrop: { flex: 1, @@ -125,7 +126,7 @@ const getThemedStyles = (theme: any) => backgroundColor: theme.bgHeader, }, noImage: { - color: theme.textSecondary, + color: theme.textOnAccent, marginBottom: 20, }, closeButton: { diff --git a/example/src/theme.ts b/example/src/theme.ts index 824183a..4f0943b 100644 --- a/example/src/theme.ts +++ b/example/src/theme.ts @@ -1,4 +1,5 @@ export const lightTheme = { + theme: 'light', bgPrimary: '#FFFFFF', bgAccent: '#3B82F6', bgHeader: '#E5E7EB', @@ -16,6 +17,7 @@ export const lightTheme = { }; export const darkTheme = { + theme: 'dark', bgPrimary: '#1E1E1E', bgAccent: '#2D6BE9', bgHeader: '#2C2C2E', diff --git a/example/src/types/navigation.ts b/example/src/types/navigation.ts index c3ace83..5778d54 100644 --- a/example/src/types/navigation.ts +++ b/example/src/types/navigation.ts @@ -1,3 +1,5 @@ +import type { StackNavigationProp } from '@react-navigation/stack'; + export type RootStackParamList = { Home: undefined; Blank: undefined; @@ -19,6 +21,4 @@ export type RootStackParamList = { Stress: undefined; }; -import type { NativeStackNavigationProp } from '@react-navigation/native-stack'; - -export type RootNavigationProp = NativeStackNavigationProp; +export type RootNavigationProp = StackNavigationProp; diff --git a/ios/extensions/RNUserInterface+Extension.swift b/ios/extensions/RNUserInterface+Extension.swift index 4ff0571..39ea6bc 100644 --- a/ios/extensions/RNUserInterface+Extension.swift +++ b/ios/extensions/RNUserInterface+Extension.swift @@ -7,7 +7,7 @@ extension RNUserInterfaceStyle { return .light case .dark: return .dark - case .default: + case .system: return .unspecified @unknown default: return .unspecified diff --git a/package.json b/package.json index 1aeca69..b882365 100644 --- a/package.json +++ b/package.json @@ -101,12 +101,12 @@ "eslint-plugin-prettier": "5.5.4", "jest": "30.2.0", "lefthook": "2.0.2", - "nitrogen": "0.30.2", + "nitrogen": "0.31.4", "prettier": "3.6.2", "react": "19.1.1", "react-native": "0.82.1", "react-native-builder-bob": "0.40.14", - "react-native-nitro-modules": "0.30.2", + "react-native-nitro-modules": "0.31.4", "semantic-release": "25.0.1", "typescript": "5.9.3" }, diff --git a/src/types.ts b/src/types.ts index 354c8da..bc9a857 100644 --- a/src/types.ts +++ b/src/types.ts @@ -61,7 +61,7 @@ export type RNMapPadding = { export type RNMapType = 'none' | 'normal' | 'hybrid' | 'satellite' | 'terrain'; -export type RNUserInterfaceStyle = 'light' | 'dark' | 'default'; +export type RNUserInterfaceStyle = 'light' | 'dark' | 'system'; export type RNFeatureType = string; diff --git a/yarn.lock b/yarn.lock index 10961e1..2154515 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2755,17 +2755,17 @@ __metadata: linkType: hard "@npmcli/package-json@npm:^7.0.0, @npmcli/package-json@npm:^7.0.1": - version: 7.0.1 - resolution: "@npmcli/package-json@npm:7.0.1" + version: 7.0.2 + resolution: "@npmcli/package-json@npm:7.0.2" dependencies: "@npmcli/git": ^7.0.0 glob: ^11.0.3 hosted-git-info: ^9.0.0 - json-parse-even-better-errors: ^4.0.0 - proc-log: ^5.0.0 + json-parse-even-better-errors: ^5.0.0 + proc-log: ^6.0.0 semver: ^7.5.3 validate-npm-package-license: ^3.0.4 - checksum: 8d6415999c80c744b1d59cdd03e9c9a3e7efe1ec744923d65e0f0cc5f72dc20fa87ad9dab95574b10d2d54b6b1d0846b9b7fd94812ecd32a5fed52eda65eaae9 + checksum: cb237240c0f844f8e4b0fb13fb425c1abbfdc510e894412cdf88071c3bfb1bd864867797bd13d214a396ae51cc4f6d68a31eb23f9682f6898a7eb09d6e5be58b languageName: node linkType: hard @@ -6084,9 +6084,9 @@ __metadata: linkType: hard "electron-to-chromium@npm:^1.5.238": - version: 1.5.244 - resolution: "electron-to-chromium@npm:1.5.244" - checksum: 0ca55077fa97c6a59e571d066bb1483399522d849758d878dd8e6b5ed0324c973da2448beb6cf630c2d700853824a633742158194d1345e6d2a53fa278dc8937 + version: 1.5.245 + resolution: "electron-to-chromium@npm:1.5.245" + checksum: 6262655d0dec8663df58056559a8f1f47648657debf0c868e49d8e3b5f866318c4a9105261272a5aaa529fc6140f4401e0e2c5fadbd24ed50cf9b5cd7e961bfc languageName: node linkType: hard @@ -10541,18 +10541,18 @@ __metadata: languageName: node linkType: hard -"nitrogen@npm:0.30.2": - version: 0.30.2 - resolution: "nitrogen@npm:0.30.2" +"nitrogen@npm:0.31.4": + version: 0.31.4 + resolution: "nitrogen@npm:0.31.4" dependencies: chalk: ^5.3.0 - react-native-nitro-modules: ^0.30.2 + react-native-nitro-modules: ^0.31.4 ts-morph: ^27.0.0 yargs: ^18.0.0 zod: ^4.0.5 bin: nitrogen: lib/index.js - checksum: b8692111891d70715b200d1c7adf129fe302cc8d85055ccb60144269970460b747d78caa110eadaee5c10f3cc077da46d53edbbc63ee9a5112a396e3256a2b32 + checksum: 566abe01767d03860834b7b95093f1de5d7d7e010a556508e727afafc7476765d146452047adeee3aeaa5b0d361c504762695d8d3d6386e77f0091ff53e00d9c languageName: node linkType: hard @@ -11955,12 +11955,12 @@ __metadata: eslint-plugin-prettier: 5.5.4 jest: 30.2.0 lefthook: 2.0.2 - nitrogen: 0.30.2 + nitrogen: 0.31.4 prettier: 3.6.2 react: 19.1.1 react-native: 0.82.1 react-native-builder-bob: 0.40.14 - react-native-nitro-modules: 0.30.2 + react-native-nitro-modules: 0.31.4 semantic-release: 25.0.1 typescript: 5.9.3 peerDependencies: @@ -12004,7 +12004,7 @@ __metadata: languageName: node linkType: hard -"react-native-nitro-modules@npm:0.30.2, react-native-nitro-modules@npm:^0.30.2": +"react-native-nitro-modules@npm:0.30.2": version: 0.30.2 resolution: "react-native-nitro-modules@npm:0.30.2" peerDependencies: @@ -12014,6 +12014,16 @@ __metadata: languageName: node linkType: hard +"react-native-nitro-modules@npm:0.31.4, react-native-nitro-modules@npm:^0.31.4": + version: 0.31.4 + resolution: "react-native-nitro-modules@npm:0.31.4" + peerDependencies: + react: "*" + react-native: "*" + checksum: 9d251ec788eafb5c66033a1f376e2a8f9dfc8962a22e8511f6451bc50bf324549fd4e5113ee5bed59a993c0afc3288e8722d854239a33c00faae26848a211fde + languageName: node + linkType: hard + "react-native-reanimated@npm:4.1.3": version: 4.1.3 resolution: "react-native-reanimated@npm:4.1.3" @@ -12496,9 +12506,9 @@ __metadata: linkType: hard "sax@npm:>=0.6.0": - version: 1.4.2 - resolution: "sax@npm:1.4.2" - checksum: 2a036105f12793350f74e1a20e96c1fd4857353b65d6905c19bb9ce2ffa2c8df7a8ced901e65f34ba464001425a49d91178c47449056dd08a307e08cbe6e74f8 + version: 1.4.3 + resolution: "sax@npm:1.4.3" + checksum: 136a202eee9364f312fb1c6abadb045ef430a7468853f804758d8f2dc8d2560801245620e2bfd4ead2e332c025bef50865271974d142a0c26f69d5fc3de9baf1 languageName: node linkType: hard @@ -13520,9 +13530,9 @@ __metadata: linkType: hard "tinyexec@npm:^1.0.0": - version: 1.0.1 - resolution: "tinyexec@npm:1.0.1" - checksum: 40f5219abf891884863b085ebe5e8c8bf95bde802f6480f279588b355835ad1604fa01eada2afe90063b48b53cd4b0be5c37393980e23f06fd10689d92fb9586 + version: 1.0.2 + resolution: "tinyexec@npm:1.0.2" + checksum: af22de2191cc70bb782eef29bbba7cf6ac16664e550b547b0db68804f988eeb2c70e12fbb7d2d688ee994b28ba831d746e9eded98c3d10042fd3a9b8de208514 languageName: node linkType: hard