Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions android/src/main/java/com/luggmaps/LuggPolylineView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,7 @@ interface LuggPolylineViewDelegate {
fun polylineViewDidUpdate(polylineView: LuggPolylineView)
}

data class AnimatedOptions(
val duration: Long = 2150L,
val easing: String = "linear",
val trailLength: Float = 1f,
val delay: Long = 0L
)
data class AnimatedOptions(val duration: Long = 2150L, val easing: String = "linear", val trailLength: Float = 1f, val delay: Long = 0L)

class LuggPolylineView(context: Context) : ReactViewGroup(context) {
var coordinates: List<LatLng> = emptyList()
Expand Down
8 changes: 5 additions & 3 deletions android/src/main/java/com/luggmaps/core/PolylineAnimator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -59,16 +59,18 @@ class PolylineAnimator {
startAnimation()
}

private fun getInterpolator(): TimeInterpolator {
return when (animatedOptions.easing) {
private fun getInterpolator(): TimeInterpolator =
when (animatedOptions.easing) {
"easeIn" -> TimeInterpolator { t -> t * t }

"easeOut" -> TimeInterpolator { t -> t * (2 - t) }

"easeInOut" -> TimeInterpolator { t ->
if (t < 0.5f) 2 * t * t else -1 + (4 - 2 * t) * t
}

else -> LinearInterpolator()
}
}

fun update() {
if (animated) return
Expand Down
60 changes: 37 additions & 23 deletions example/shared/src/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,15 @@ import {
type CameraEventPayload,
} from '@lugg/maps';
import {
TrueSheet,
type TrueSheet,
TrueSheetProvider,
type PositionChangeEvent,
} from '@lodev09/react-native-true-sheet';
import {
ReanimatedTrueSheet,
ReanimatedTrueSheetProvider,
useReanimatedTrueSheet,
} from '@lodev09/react-native-true-sheet/reanimated';
import { useAnimatedProps, useDerivedValue } from 'react-native-reanimated';

import { Button, Map } from './components';
import { randomFrom, randomLetter } from './utils';
Expand All @@ -28,18 +33,32 @@ import {
} from './markers';
import { useLocationPermission } from './useLocationPermission';

export function Home() {
function HomeContent() {
const mapRef = useRef<MapView>(null);
const sheetRef = useRef<TrueSheet>(null);
const { height: screenHeight } = useWindowDimensions();
const locationPermission = useLocationPermission();
const [provider, setProvider] = useState<MapProviderType>('google');
const [provider, setProvider] = useState<MapProviderType>('apple');
const [showMap, setShowMap] = useState(true);
const [markers, setMarkers] = useState(INITIAL_MARKERS);
const [sheetHeight, setSheetHeight] = useState(0);
const [cameraPosition, setCameraPosition] = useState<CameraEventPayload>();
const [isIdle, setIsIdle] = useState(true);

const { animatedPosition } = useReanimatedTrueSheet();

const animatedPaddingBottom = useDerivedValue(
() => screenHeight - animatedPosition.value
);

const animatedProps = useAnimatedProps(() => ({
padding: {
top: 0,
left: 0,
right: 0,
bottom: animatedPaddingBottom.value,
},
}));

const handleCameraMove = useCallback(
(event: { nativeEvent: CameraEventPayload }) => {
setCameraPosition(event.nativeEvent);
Expand All @@ -56,13 +75,6 @@ export function Home() {
[]
);

const handleSheetPositionChange = useCallback(
(event: PositionChangeEvent) => {
setSheetHeight(screenHeight - event.nativeEvent.position);
},
[screenHeight]
);

const handleMapReady = useCallback(() => {
sheetRef.current?.present();
}, []);
Expand Down Expand Up @@ -124,27 +136,21 @@ export function Home() {
ref={mapRef}
provider={provider}
markers={markers}
padding={{
top: 0,
left: 0,
right: 0,
bottom: sheetHeight,
}}
animatedProps={animatedProps}
animatedPaddingBottom={animatedPaddingBottom}
userLocationEnabled={locationPermission}
onReady={handleMapReady}
onCameraMove={handleCameraMove}
onCameraIdle={handleCameraIdle}
/>
)}

<TrueSheet
<ReanimatedTrueSheet
ref={sheetRef}
detents={['auto', 1]}
detents={[0.1, 'auto', 1]}
dimmed={false}
backgroundBlur="system-material-light"
dismissible={false}
grabber
onPositionChange={handleSheetPositionChange}
>
{cameraPosition && (
<Text style={styles.positionText}>
Expand Down Expand Up @@ -188,13 +194,21 @@ export function Home() {
}
/>
</View>
</TrueSheet>
</ReanimatedTrueSheet>
</View>
</MapProvider>
</TrueSheetProvider>
);
}

export function Home() {
return (
<ReanimatedTrueSheetProvider>
<HomeContent />
</ReanimatedTrueSheetProvider>
);
}

const styles = StyleSheet.create({
container: { flex: 1 },
positionText: {
Expand Down
41 changes: 31 additions & 10 deletions example/shared/src/components/Map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ import {
type CameraEventPayload,
} from '@lugg/maps';
import type { NativeSyntheticEvent } from 'react-native';
import Animated, {
useAnimatedStyle,
type SharedValue,
} from 'react-native-reanimated';

const AnimatedMapView = Animated.createAnimatedComponent(MapView);

import { MarkerIcon } from './MarkerIcon';
import { MarkerText } from './MarkerText';
Expand All @@ -17,6 +23,8 @@ import { Route, smoothCoordinates } from './Route';

interface MapProps extends MapViewProps {
markers: MarkerData[];
animatedProps?: Partial<MapViewProps>;
animatedPaddingBottom?: SharedValue<number>;
}

const renderMarker = (marker: MarkerData) => {
Expand Down Expand Up @@ -77,7 +85,18 @@ const renderMarker = (marker: MarkerData) => {
const INITIAL_ZOOM = 14;

export const Map = forwardRef<MapView, MapProps>(
({ markers, padding, onCameraIdle, onCameraMove, ...props }, ref) => {
(
{
markers,
padding,
animatedProps,
animatedPaddingBottom,
onCameraIdle,
onCameraMove,
...props
},
ref
) => {
const [zoom, setZoom] = useState(INITIAL_ZOOM);
const polylineCoordinates = useMemo(
() => markers.map((m) => m.coordinate),
Expand All @@ -87,7 +106,13 @@ export const Map = forwardRef<MapView, MapProps>(
() => smoothCoordinates(polylineCoordinates),
[polylineCoordinates]
);
const bottomOffset = padding?.bottom ?? 0;

const centerPinStyle = useAnimatedStyle(() => {
const bottomOffset = animatedPaddingBottom?.value ?? padding?.bottom ?? 0;
return {
transform: [{ translateY: -bottomOffset / 2 }],
};
});

const handleCameraMove = (e: NativeSyntheticEvent<CameraEventPayload>) => {
onCameraMove?.(e);
Expand All @@ -100,13 +125,14 @@ export const Map = forwardRef<MapView, MapProps>(

return (
<View style={styles.container}>
<MapView
<AnimatedMapView
ref={ref}
style={StyleSheet.absoluteFill}
mapId="6939261d95ee48fd57332474"
initialCoordinate={{ latitude: 37.78, longitude: -122.43 }}
initialZoom={INITIAL_ZOOM}
padding={padding}
animatedProps={animatedProps}
onCameraMove={handleCameraMove}
onCameraIdle={handleCameraIdle}
{...props}
Expand All @@ -120,13 +146,8 @@ export const Map = forwardRef<MapView, MapProps>(
text="LO"
color="#34A853"
/>
</MapView>
<View
style={[
styles.centerPin,
{ transform: [{ translateY: -bottomOffset / 2 }] },
]}
/>
</AnimatedMapView>
<Animated.View style={[styles.centerPin, centerPinStyle]} />
</View>
);
}
Expand Down
58 changes: 55 additions & 3 deletions ios/LuggAppleMapView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,10 @@ - (void)setCameraWithLatitude:(double)latitude
animated:(BOOL)animated {
CLLocationCoordinate2D center =
CLLocationCoordinate2DMake(latitude, longitude);
NSLog(@"[Maps] setCameraWithLatitude: before zoom=%.2f", _mapView.zoomLevel);
[_mapView setCenterCoordinate:center zoomLevel:zoom animated:animated];
NSLog(@"[Maps] setCameraWithLatitude: after zoom=%.2f (requested=%.2f)",
_mapView.zoomLevel, zoom);
}

- (CLLocationDistance)cameraDistanceForZoomLevel:(double)zoomLevel {
Expand Down Expand Up @@ -235,6 +238,8 @@ - (MKMapView *)mapView {

- (void)updateProps:(Props::Shared const &)props
oldProps:(Props::Shared const &)oldProps {
const auto &oldViewProps =
*std::static_pointer_cast<LuggAppleMapViewProps const>(oldProps);
const auto &newViewProps =
*std::static_pointer_cast<LuggAppleMapViewProps const>(props);

Expand All @@ -244,9 +249,52 @@ - (void)updateProps:(Props::Shared const &)props
_mapView.rotateEnabled = newViewProps.rotateEnabled;
_mapView.pitchEnabled = newViewProps.pitchEnabled;
_mapView.showsUserLocation = newViewProps.userLocationEnabled;
_mapView.layoutMargins = UIEdgeInsetsMake(
newViewProps.padding.top, newViewProps.padding.left,
newViewProps.padding.bottom, newViewProps.padding.right);

// Check if padding changed
BOOL paddingChanged =
oldViewProps.padding.top != newViewProps.padding.top ||
oldViewProps.padding.left != newViewProps.padding.left ||
oldViewProps.padding.bottom != newViewProps.padding.bottom ||
oldViewProps.padding.right != newViewProps.padding.right;

if (paddingChanged) {
// Calculate the offset difference to keep visual center stable
CGFloat oldOffsetX =
(oldViewProps.padding.left - oldViewProps.padding.right) / 2.0;
CGFloat oldOffsetY =
(oldViewProps.padding.top - oldViewProps.padding.bottom) / 2.0;
CGFloat newOffsetX =
(newViewProps.padding.left - newViewProps.padding.right) / 2.0;
CGFloat newOffsetY =
(newViewProps.padding.top - newViewProps.padding.bottom) / 2.0;

CGFloat deltaX = newOffsetX - oldOffsetX;
CGFloat deltaY = newOffsetY - oldOffsetY;

// Apply new padding first
_mapView.layoutMargins = UIEdgeInsetsMake(
newViewProps.padding.top, newViewProps.padding.left,
newViewProps.padding.bottom, newViewProps.padding.right);

// Convert pixel offset to coordinate offset
if (deltaX != 0 || deltaY != 0) {
double zoomBefore = _mapView.zoomLevel;
CLLocationCoordinate2D currentCenter = _mapView.centerCoordinate;
CGPoint centerPoint = [_mapView convertCoordinate:currentCenter
toPointToView:_mapView];
CGPoint newPoint =
CGPointMake(centerPoint.x - deltaX, centerPoint.y - deltaY);
CLLocationCoordinate2D newCenter = [_mapView convertPoint:newPoint
toCoordinateFromView:_mapView];
[_mapView setCenterCoordinate:newCenter animated:NO];
NSLog(@"[Maps] padding changed: deltaX=%.2f deltaY=%.2f zoomBefore=%.2f zoomAfter=%.2f",
deltaX, deltaY, zoomBefore, _mapView.zoomLevel);
}
} else {
_mapView.layoutMargins = UIEdgeInsetsMake(
newViewProps.padding.top, newViewProps.padding.left,
newViewProps.padding.bottom, newViewProps.padding.right);
}

_minZoom = newViewProps.minZoom;
_maxZoom = newViewProps.maxZoom;
Expand Down Expand Up @@ -483,6 +531,8 @@ - (void)mapViewDidChangeVisibleRegion:(MKMapView *)mapView {
}

- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated {
NSLog(@"[Maps] regionDidChangeAnimated: zoom=%.2f animated=%d",
mapView.zoomLevel, animated);
BOOL wasDragging = _isDragging;
_isDragging = NO;
if (wasDragging) {
Expand Down Expand Up @@ -573,6 +623,8 @@ - (void)moveCamera:(double)latitude
}

double targetZoom = zoom > 0 ? zoom : _mapView.zoomLevel;
NSLog(@"[Maps] moveCamera: zoom=%.2f targetZoom=%.2f currentZoom=%.2f",
zoom, targetZoom, _mapView.zoomLevel);

if (duration < 0) {
[self setCameraWithLatitude:latitude
Expand Down
5 changes: 3 additions & 2 deletions ios/LuggPolylineView.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#import "core/PolylineAnimatorBase.h"
#import <CoreLocation/CoreLocation.h>
#import <React/RCTViewComponentView.h>
#import <UIKit/UIKit.h>
#import "core/PolylineAnimatorBase.h"

NS_ASSUME_NONNULL_BEGIN

Expand All @@ -17,7 +17,8 @@ NS_ASSUME_NONNULL_BEGIN
@property(nonatomic, readonly) NSArray<CLLocation *> *coordinates;
@property(nonatomic, readonly) NSArray<UIColor *> *strokeColors;
@property(nonatomic, readonly) BOOL animated;
@property(nonatomic, readonly, nullable) PolylineAnimatedOptions *animatedOptions;
@property(nonatomic, readonly, nullable)
PolylineAnimatedOptions *animatedOptions;
@property(nonatomic, readonly) CGFloat strokeWidth;
@property(nonatomic, readonly) NSInteger zIndex;
@property(nonatomic, weak, nullable) id<LuggPolylineViewDelegate> delegate;
Expand Down
8 changes: 6 additions & 2 deletions ios/LuggPolylineView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,13 @@ - (void)updateProps:(Props::Shared const &)props
const auto &opts = newViewProps.animatedOptions;
PolylineAnimatedOptions *options = [[PolylineAnimatedOptions alloc] init];
options.duration = opts.duration > 0 ? opts.duration : 2150;
options.trailLength = (opts.trailLength > 0 && opts.trailLength <= 1.0) ? opts.trailLength : 1.0;
options.trailLength = (opts.trailLength > 0 && opts.trailLength <= 1.0)
? opts.trailLength
: 1.0;
options.delay = opts.delay;
options.easing = !opts.easing.empty() ? [NSString stringWithUTF8String:opts.easing.c_str()] : @"linear";
options.easing = !opts.easing.empty()
? [NSString stringWithUTF8String:opts.easing.c_str()]
: @"linear";
_animatedOptions = options;

_strokeWidth = newViewProps.strokeWidth > 0 ? newViewProps.strokeWidth : 1.0;
Expand Down
2 changes: 1 addition & 1 deletion ios/core/MKPolylineAnimator.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#import <MapKit/MapKit.h>
#import "PolylineAnimatorBase.h"
#import <MapKit/MapKit.h>

@interface MKPolylineAnimator : MKOverlayPathRenderer

Expand Down
1 change: 1 addition & 0 deletions ios/extensions/MKMapView+Zoom.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ NS_ASSUME_NONNULL_BEGIN
(CLLocationCoordinate2D)centerCoordinate
zoomLevel:(double)zoomLevel;

/// Returns the zoom level based on the full map region, not affected by layoutMargins
- (double)zoomLevel;

@end
Expand Down
Loading