react-native-maplibre-lite 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +92 -6
- package/lib/module/components/MapView.js +116 -14
- package/lib/module/components/MapView.js.map +1 -1
- package/lib/module/components/NavigatorHud.js +152 -0
- package/lib/module/components/NavigatorHud.js.map +1 -0
- package/lib/module/components/NavigatorRecenterButton.js +48 -0
- package/lib/module/components/NavigatorRecenterButton.js.map +1 -0
- package/lib/module/components/NavigatorVoiceControl.js +173 -0
- package/lib/module/components/NavigatorVoiceControl.js.map +1 -0
- package/lib/module/components/navigatorChromeTheme.js +98 -0
- package/lib/module/components/navigatorChromeTheme.js.map +1 -0
- package/lib/module/components/navigatorManeuverIcon.js +210 -0
- package/lib/module/components/navigatorManeuverIcon.js.map +1 -0
- package/lib/module/components/navigatorVoiceCatalog.js +225 -0
- package/lib/module/components/navigatorVoiceCatalog.js.map +1 -0
- package/lib/module/components/navigatorVoiceKeys.js +14 -0
- package/lib/module/components/navigatorVoiceKeys.js.map +1 -0
- package/lib/module/components/navigatorVoicePlayer.js +100 -0
- package/lib/module/components/navigatorVoicePlayer.js.map +1 -0
- package/lib/module/components/navigatorVoiceStrings.js +31 -0
- package/lib/module/components/navigatorVoiceStrings.js.map +1 -0
- package/lib/module/components/types.js +22 -0
- package/lib/module/components/types.js.map +1 -1
- package/lib/module/components/useNavigatorVoice.js +78 -0
- package/lib/module/components/useNavigatorVoice.js.map +1 -0
- package/lib/module/components/utils.js +26 -0
- package/lib/module/components/utils.js.map +1 -1
- package/lib/module/components/webMapBuild.js +1 -1
- package/lib/module/components/webMapBuild.js.map +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/components/MapView.d.ts +14 -1
- package/lib/typescript/src/components/MapView.d.ts.map +1 -1
- package/lib/typescript/src/components/NavigatorHud.d.ts +13 -0
- package/lib/typescript/src/components/NavigatorHud.d.ts.map +1 -0
- package/lib/typescript/src/components/NavigatorRecenterButton.d.ts +11 -0
- package/lib/typescript/src/components/NavigatorRecenterButton.d.ts.map +1 -0
- package/lib/typescript/src/components/NavigatorVoiceControl.d.ts +20 -0
- package/lib/typescript/src/components/NavigatorVoiceControl.d.ts.map +1 -0
- package/lib/typescript/src/components/navigatorChromeTheme.d.ts +19 -0
- package/lib/typescript/src/components/navigatorChromeTheme.d.ts.map +1 -0
- package/lib/typescript/src/components/navigatorManeuverIcon.d.ts +20 -0
- package/lib/typescript/src/components/navigatorManeuverIcon.d.ts.map +1 -0
- package/lib/typescript/src/components/navigatorVoiceCatalog.d.ts +50 -0
- package/lib/typescript/src/components/navigatorVoiceCatalog.d.ts.map +1 -0
- package/lib/typescript/src/components/navigatorVoiceKeys.d.ts +10 -0
- package/lib/typescript/src/components/navigatorVoiceKeys.d.ts.map +1 -0
- package/lib/typescript/src/components/navigatorVoicePlayer.d.ts +15 -0
- package/lib/typescript/src/components/navigatorVoicePlayer.d.ts.map +1 -0
- package/lib/typescript/src/components/navigatorVoiceStrings.d.ts +19 -0
- package/lib/typescript/src/components/navigatorVoiceStrings.d.ts.map +1 -0
- package/lib/typescript/src/components/types.d.ts +83 -17
- package/lib/typescript/src/components/types.d.ts.map +1 -1
- package/lib/typescript/src/components/useNavigatorVoice.d.ts +20 -0
- package/lib/typescript/src/components/useNavigatorVoice.d.ts.map +1 -0
- package/lib/typescript/src/components/utils.d.ts +9 -0
- package/lib/typescript/src/components/utils.d.ts.map +1 -1
- package/lib/typescript/src/components/webMapBuild.d.ts +1 -1
- package/lib/typescript/src/components/webMapBuild.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +1 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +16 -7
- package/resources/README.md +62 -0
- package/resources/map.html +797 -0
- package/src/components/MapView.tsx +154 -8
- package/src/components/NavigatorHud.tsx +166 -0
- package/src/components/NavigatorRecenterButton.tsx +45 -0
- package/src/components/NavigatorVoiceControl.tsx +198 -0
- package/src/components/navigatorChromeTheme.ts +118 -0
- package/src/components/navigatorManeuverIcon.tsx +177 -0
- package/src/components/navigatorVoiceCatalog.ts +275 -0
- package/src/components/navigatorVoiceKeys.ts +132 -0
- package/src/components/navigatorVoicePlayer.tsx +126 -0
- package/src/components/navigatorVoiceStrings.ts +42 -0
- package/src/components/types.ts +87 -18
- package/src/components/useNavigatorVoice.ts +96 -0
- package/src/components/utils.ts +28 -0
- package/src/components/webMapBuild.ts +1 -1
- package/src/index.tsx +8 -0
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
useContext,
|
|
5
5
|
useEffect,
|
|
6
6
|
useImperativeHandle,
|
|
7
|
+
useMemo,
|
|
7
8
|
useRef,
|
|
8
9
|
useState,
|
|
9
10
|
} from 'react';
|
|
@@ -21,25 +22,40 @@ import KeepAwake from '@sayem314/react-native-keep-awake';
|
|
|
21
22
|
|
|
22
23
|
import MapPlaceholder from './MapPlaceholder';
|
|
23
24
|
import MapSelectPoint, { type MapSelectPointType } from './MapSelectPoint';
|
|
25
|
+
import { NavigatorHud } from './NavigatorHud';
|
|
26
|
+
import { NavigatorRecenterButton } from './NavigatorRecenterButton';
|
|
27
|
+
import { NavigatorVoiceControl } from './NavigatorVoiceControl';
|
|
28
|
+
import { resolveNavigatorChromeTheme } from './navigatorChromeTheme';
|
|
29
|
+
import { keysToClipUrls, volumeLevelToGain } from './navigatorVoiceCatalog';
|
|
30
|
+
import { NavigatorVoicePlayer, type NavigatorVoicePlayerRef } from './navigatorVoicePlayer';
|
|
31
|
+
import { navigatorUiStrings } from './navigatorVoiceStrings';
|
|
32
|
+
import { useNavigatorVoice } from './useNavigatorVoice';
|
|
24
33
|
import {
|
|
25
34
|
type EventParams,
|
|
26
35
|
type MapLiteInitParams,
|
|
36
|
+
type MapLiteMapErrorEventParams,
|
|
37
|
+
type MapLiteMarkerParams,
|
|
38
|
+
type MapLitePolygonParams,
|
|
39
|
+
type MapLitePolylineParams,
|
|
40
|
+
type MapLiteRemoveOverlayParams,
|
|
27
41
|
type MapLiteSetNavigatorPositionParams,
|
|
28
42
|
type MapLiteUpdateParams,
|
|
29
43
|
type MapLiteWebError,
|
|
30
44
|
type MarkerProps,
|
|
31
45
|
type NativeToWebCommand,
|
|
32
46
|
type NavigatorChromeParams,
|
|
47
|
+
type NavigatorHudState,
|
|
33
48
|
type NavigatorInstructionParams,
|
|
34
49
|
type NavigatorLang,
|
|
35
50
|
type NavigatorProfile,
|
|
36
51
|
type NavigatorPositionSetParams,
|
|
37
52
|
type NavigatorRouteSetParams,
|
|
53
|
+
type NavigatorVoicePlayParams,
|
|
38
54
|
type PolygonProps,
|
|
39
55
|
type PolylineProps,
|
|
40
56
|
type WebToNativeMessage,
|
|
41
57
|
} from './types';
|
|
42
|
-
import { loadResources } from './utils';
|
|
58
|
+
import { loadResources, getNativeMapHtmlUri } from './utils';
|
|
43
59
|
import { MAP_HTML } from './webMapBuild';
|
|
44
60
|
|
|
45
61
|
|
|
@@ -69,6 +85,8 @@ interface MapViewProps {
|
|
|
69
85
|
onZoomStart?: (eventParams: EventParams) => void;
|
|
70
86
|
onZoomEnd?: (eventParams: EventParams) => void;
|
|
71
87
|
onIdle?: (eventParams: EventParams) => void;
|
|
88
|
+
/** MapLibre `error` из WebView (`event: 'error'`). */
|
|
89
|
+
onMapError?: (params: MapLiteMapErrorEventParams) => void;
|
|
72
90
|
showSelectPoint?: boolean;
|
|
73
91
|
selectPointColor?: string;
|
|
74
92
|
selectPointBackgroundColor?: string;
|
|
@@ -98,12 +116,23 @@ interface MapViewProps {
|
|
|
98
116
|
* Sent once in WebView `init` as `navigatorChrome`.
|
|
99
117
|
*/
|
|
100
118
|
navigatorChrome?: NavigatorChromeParams;
|
|
119
|
+
/**
|
|
120
|
+
* URL каталога голосов навигатора (`.../voices/data.json`). Каталог,
|
|
121
|
+
* манифест, выбор голоса/громкости и воспроизведение — на нативной
|
|
122
|
+
* стороне. Без него озвучка и FAB выбора голоса отключены.
|
|
123
|
+
*/
|
|
124
|
+
navigatorVoiceUrl?: string;
|
|
101
125
|
onNavigatorRouteSet?: (params: NavigatorRouteSetParams) => void;
|
|
102
126
|
onNavigatorInstruction?: (params: NavigatorInstructionParams) => void;
|
|
103
127
|
onNavigatorPositionSet?: (params: NavigatorPositionSetParams) => void;
|
|
104
128
|
/** Ошибки команд WebView (`type: 'error'` из карты). */
|
|
105
129
|
onMapLiteError?: (err: MapLiteWebError) => void;
|
|
106
130
|
|
|
131
|
+
/**
|
|
132
|
+
* Загружать WebView из `map.html` в нативных ресурсах приложения вместо inline `MAP_HTML`.
|
|
133
|
+
* Размещение файла — см. `resources/README.md`.
|
|
134
|
+
*/
|
|
135
|
+
useNativeMapHtml?: boolean;
|
|
107
136
|
|
|
108
137
|
developerLocalhostBundleUrl?: string;
|
|
109
138
|
}
|
|
@@ -165,6 +194,36 @@ const getBoundsFromCoords = (
|
|
|
165
194
|
];
|
|
166
195
|
};
|
|
167
196
|
|
|
197
|
+
const toWebMarkerParams = (props: MarkerProps): MapLiteMarkerParams => ({
|
|
198
|
+
uniqueId: props.uniqueId,
|
|
199
|
+
latitude: props.latitude,
|
|
200
|
+
longitude: props.longitude,
|
|
201
|
+
color: props.color,
|
|
202
|
+
iconUrl: props.iconUrl,
|
|
203
|
+
iconWidth: props.iconWidth,
|
|
204
|
+
iconHeight: props.iconHeight,
|
|
205
|
+
html: props.html,
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
const toWebPolylineParams = (props: PolylineProps): MapLitePolylineParams => ({
|
|
209
|
+
uniqueId: props.uniqueId,
|
|
210
|
+
coordinates: props.coordinates,
|
|
211
|
+
color: props.color,
|
|
212
|
+
width: props.width,
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
const toWebPolygonParams = (props: PolygonProps): MapLitePolygonParams => ({
|
|
216
|
+
uniqueId: props.uniqueId,
|
|
217
|
+
coordinates: props.coordinates,
|
|
218
|
+
fillColor: props.fillColor,
|
|
219
|
+
fillOpacity: props.fillOpacity,
|
|
220
|
+
strokeColor: props.strokeColor,
|
|
221
|
+
strokeOpacity: props.strokeOpacity,
|
|
222
|
+
strokeWidth: props.strokeWidth,
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
const toWebRemoveParams = (uniqueId: string): MapLiteRemoveOverlayParams => ({ uniqueId });
|
|
226
|
+
|
|
168
227
|
/** Минимальный интервал между отправками позиции в WebView (мост RN↔JS). */
|
|
169
228
|
const NAVIGATOR_GPS_FORWARD_MIN_MS = 500;
|
|
170
229
|
|
|
@@ -177,11 +236,51 @@ export const MapView = forwardRef<MapViewRef, MapViewProps>((props, ref) => {
|
|
|
177
236
|
initedRef.current = inited;
|
|
178
237
|
}, [inited]);
|
|
179
238
|
|
|
239
|
+
/** View-model HUD маршрута, приходит из WebView (`navigatorHud`). */
|
|
240
|
+
const [hudState, setHudState] = useState<NavigatorHudState | null>(null);
|
|
241
|
+
const voicePlayerRef = useRef<NavigatorVoicePlayerRef | null>(null);
|
|
242
|
+
|
|
243
|
+
/** Озвучка ограничена русским набором фраз (как в веб-навигаторе). */
|
|
244
|
+
const voiceSupported = props.navigator === true && props.navigatorLang !== 'en';
|
|
245
|
+
const voice = useNavigatorVoice(voiceSupported ? props.navigatorVoiceUrl : undefined);
|
|
246
|
+
|
|
247
|
+
const chromeTheme = useMemo(
|
|
248
|
+
() => resolveNavigatorChromeTheme(props.navigatorChrome),
|
|
249
|
+
[props.navigatorChrome]
|
|
250
|
+
);
|
|
251
|
+
const navigatorStrings = useMemo(
|
|
252
|
+
() => navigatorUiStrings(props.navigatorLang),
|
|
253
|
+
[props.navigatorLang]
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
useEffect(() => {
|
|
257
|
+
voicePlayerRef.current?.setVolume(volumeLevelToGain(voice.volumeLevel));
|
|
258
|
+
}, [voice.volumeLevel]);
|
|
259
|
+
|
|
260
|
+
const handleNavigatorVoice = (params: NavigatorVoicePlayParams) => {
|
|
261
|
+
const player = voicePlayerRef.current;
|
|
262
|
+
if (!player) return;
|
|
263
|
+
if (params.action === 'stop') {
|
|
264
|
+
player.stop();
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
if (params.action === 'prefetch') return;
|
|
268
|
+
if (!voice.voiceEnabled || !voice.manifest || !voice.clipBaseUrl) return;
|
|
269
|
+
const urls = keysToClipUrls(voice.manifest, voice.clipBaseUrl, params.keys);
|
|
270
|
+
if (urls.length > 0) player.playUrls(urls);
|
|
271
|
+
};
|
|
272
|
+
|
|
180
273
|
const coordsInMapRef = useRef<Record<string, [number, number][]>>({});
|
|
181
274
|
const markersClickHandlers = useRef<Record<string, () => void>>({});
|
|
182
275
|
const mapSelectPointRef = useRef<MapSelectPointType | null>(null);
|
|
183
276
|
const performanceMode = props.performanceMode ?? (Platform.OS === 'android' ? 'balanced' : 'quality');
|
|
184
277
|
|
|
278
|
+
const webViewSource = props.developerLocalhostBundleUrl
|
|
279
|
+
? { uri: props.developerLocalhostBundleUrl }
|
|
280
|
+
: props.useNativeMapHtml
|
|
281
|
+
? { uri: getNativeMapHtmlUri() }
|
|
282
|
+
: { html: MAP_HTML };
|
|
283
|
+
|
|
185
284
|
const fallbackPixelRatio = performanceMode === 'performance'
|
|
186
285
|
? 0.85
|
|
187
286
|
: (performanceMode === 'balanced' && Platform.OS === 'android' ? 1 : undefined);
|
|
@@ -247,14 +346,14 @@ export const MapView = forwardRef<MapViewRef, MapViewProps>((props, ref) => {
|
|
|
247
346
|
|
|
248
347
|
updateMarkerClickHandler(propsMarker);
|
|
249
348
|
|
|
250
|
-
sendToWebView({ function: 'addMarker', params: propsMarker });
|
|
349
|
+
sendToWebView({ function: 'addMarker', params: toWebMarkerParams(propsMarker) });
|
|
251
350
|
scheduleAutoFitBounds();
|
|
252
351
|
};
|
|
253
352
|
|
|
254
353
|
const removeMarker = (propsMarker: MarkerProps) => {
|
|
255
354
|
delete coordsInMapRef.current[propsMarker.uniqueId];
|
|
256
355
|
delete markersClickHandlers.current[propsMarker.uniqueId];
|
|
257
|
-
sendToWebView({ function: 'removeMarker', params: propsMarker });
|
|
356
|
+
sendToWebView({ function: 'removeMarker', params: toWebRemoveParams(propsMarker.uniqueId) });
|
|
258
357
|
scheduleAutoFitBounds();
|
|
259
358
|
};
|
|
260
359
|
|
|
@@ -262,13 +361,13 @@ export const MapView = forwardRef<MapViewRef, MapViewProps>((props, ref) => {
|
|
|
262
361
|
if (propsPolyline.coordinates && !propsPolyline.ignoreFitBounds) {
|
|
263
362
|
coordsInMapRef.current[propsPolyline.uniqueId] = propsPolyline.coordinates;
|
|
264
363
|
}
|
|
265
|
-
sendToWebView({ function: 'addPolyline', params: propsPolyline });
|
|
364
|
+
sendToWebView({ function: 'addPolyline', params: toWebPolylineParams(propsPolyline) });
|
|
266
365
|
scheduleAutoFitBounds();
|
|
267
366
|
};
|
|
268
367
|
|
|
269
368
|
const removePolyline = (propsPolyline: PolylineProps) => {
|
|
270
369
|
delete coordsInMapRef.current[propsPolyline.uniqueId];
|
|
271
|
-
sendToWebView({ function: 'removePolyline', params: propsPolyline });
|
|
370
|
+
sendToWebView({ function: 'removePolyline', params: toWebRemoveParams(propsPolyline.uniqueId) });
|
|
272
371
|
scheduleAutoFitBounds();
|
|
273
372
|
};
|
|
274
373
|
|
|
@@ -276,13 +375,13 @@ export const MapView = forwardRef<MapViewRef, MapViewProps>((props, ref) => {
|
|
|
276
375
|
if (propsPolygon.coordinates && !propsPolygon.ignoreFitBounds) {
|
|
277
376
|
coordsInMapRef.current[propsPolygon.uniqueId] = propsPolygon.coordinates;
|
|
278
377
|
}
|
|
279
|
-
sendToWebView({ function: 'addPolygon', params: propsPolygon });
|
|
378
|
+
sendToWebView({ function: 'addPolygon', params: toWebPolygonParams(propsPolygon) });
|
|
280
379
|
scheduleAutoFitBounds();
|
|
281
380
|
};
|
|
282
381
|
|
|
283
382
|
const removePolygon = (propsPolygon: PolygonProps) => {
|
|
284
383
|
delete coordsInMapRef.current[propsPolygon.uniqueId];
|
|
285
|
-
sendToWebView({ function: 'removePolygon', params: propsPolygon });
|
|
384
|
+
sendToWebView({ function: 'removePolygon', params: toWebRemoveParams(propsPolygon.uniqueId) });
|
|
286
385
|
scheduleAutoFitBounds();
|
|
287
386
|
};
|
|
288
387
|
|
|
@@ -334,6 +433,10 @@ export const MapView = forwardRef<MapViewRef, MapViewProps>((props, ref) => {
|
|
|
334
433
|
sendToWebView({ function: 'pickNavigatorPosition', params: {} });
|
|
335
434
|
};
|
|
336
435
|
|
|
436
|
+
const recenterNavigatorCamera = () => {
|
|
437
|
+
sendToWebView({ function: 'recenterNavigatorCamera', params: {} });
|
|
438
|
+
};
|
|
439
|
+
|
|
337
440
|
useImperativeHandle(ref, () => ({
|
|
338
441
|
fitBounds,
|
|
339
442
|
flyTo,
|
|
@@ -407,6 +510,9 @@ export const MapView = forwardRef<MapViewRef, MapViewProps>((props, ref) => {
|
|
|
407
510
|
case 'idle':
|
|
408
511
|
props.onIdle?.(msg.params as EventParams);
|
|
409
512
|
break;
|
|
513
|
+
case 'error':
|
|
514
|
+
props.onMapError?.(msg.params as MapLiteMapErrorEventParams);
|
|
515
|
+
break;
|
|
410
516
|
case 'navigatorRouteSet':
|
|
411
517
|
props.onNavigatorRouteSet?.(msg.params as NavigatorRouteSetParams);
|
|
412
518
|
break;
|
|
@@ -416,6 +522,12 @@ export const MapView = forwardRef<MapViewRef, MapViewProps>((props, ref) => {
|
|
|
416
522
|
case 'navigatorPositionSet':
|
|
417
523
|
props.onNavigatorPositionSet?.(msg.params as NavigatorPositionSetParams);
|
|
418
524
|
break;
|
|
525
|
+
case 'navigatorHud':
|
|
526
|
+
setHudState(msg.params as NavigatorHudState);
|
|
527
|
+
break;
|
|
528
|
+
case 'navigatorVoice':
|
|
529
|
+
handleNavigatorVoice(msg.params as NavigatorVoicePlayParams);
|
|
530
|
+
break;
|
|
419
531
|
}
|
|
420
532
|
return;
|
|
421
533
|
}
|
|
@@ -523,7 +635,7 @@ export const MapView = forwardRef<MapViewRef, MapViewProps>((props, ref) => {
|
|
|
523
635
|
ref={webViewRef}
|
|
524
636
|
style={{ flex: 1, backgroundColor: 'transparent' }}
|
|
525
637
|
originWhitelist={['*']}
|
|
526
|
-
source={
|
|
638
|
+
source={webViewSource}
|
|
527
639
|
onMessage={event => {
|
|
528
640
|
onReceiveMessageFromWebView(event.nativeEvent.data);
|
|
529
641
|
}}
|
|
@@ -554,6 +666,40 @@ export const MapView = forwardRef<MapViewRef, MapViewProps>((props, ref) => {
|
|
|
554
666
|
/>
|
|
555
667
|
</View>
|
|
556
668
|
)}
|
|
669
|
+
|
|
670
|
+
{inited && props.navigator && (
|
|
671
|
+
<>
|
|
672
|
+
<NavigatorHud
|
|
673
|
+
state={hudState}
|
|
674
|
+
theme={chromeTheme}
|
|
675
|
+
pickHint={navigatorStrings.pickHint}
|
|
676
|
+
/>
|
|
677
|
+
<NavigatorRecenterButton
|
|
678
|
+
theme={chromeTheme}
|
|
679
|
+
accessibilityLabel={navigatorStrings.recenterAria}
|
|
680
|
+
onPress={recenterNavigatorCamera}
|
|
681
|
+
/>
|
|
682
|
+
{voiceSupported && (
|
|
683
|
+
<NavigatorVoiceControl
|
|
684
|
+
theme={chromeTheme}
|
|
685
|
+
strings={navigatorStrings}
|
|
686
|
+
catalog={voice.catalog}
|
|
687
|
+
selectedDir={voice.selectedDir}
|
|
688
|
+
voiceEnabled={voice.voiceEnabled}
|
|
689
|
+
volumeLevel={voice.volumeLevel}
|
|
690
|
+
onSelectVoice={voice.selectVoice}
|
|
691
|
+
onSelectVolume={voice.selectVolume}
|
|
692
|
+
onDisable={voice.disableVoice}
|
|
693
|
+
/>
|
|
694
|
+
)}
|
|
695
|
+
{voiceSupported && (
|
|
696
|
+
<NavigatorVoicePlayer
|
|
697
|
+
ref={voicePlayerRef}
|
|
698
|
+
initialVolume={volumeLevelToGain(voice.volumeLevel)}
|
|
699
|
+
/>
|
|
700
|
+
)}
|
|
701
|
+
</>
|
|
702
|
+
)}
|
|
557
703
|
</MapViewContext.Provider>
|
|
558
704
|
{!inited && (
|
|
559
705
|
<MapPlaceholder theme={props.placeholderTheme ?? 'light'} />
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { Text, View } from 'react-native';
|
|
2
|
+
|
|
3
|
+
import { NavigatorManeuverIcon } from './navigatorManeuverIcon';
|
|
4
|
+
import type { NavigatorChromeTheme } from './navigatorChromeTheme';
|
|
5
|
+
import type { NavigatorHudState } from './types';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Нативная панель навигатора (HUD): порт DOM-плашки из веб-навигатора.
|
|
9
|
+
* Получает уже отформатированную view-model (`navigatorHud`) и тему из
|
|
10
|
+
* `navigatorChrome`; вся локализация/форматирование остаются в вебе.
|
|
11
|
+
*/
|
|
12
|
+
export function NavigatorHud({
|
|
13
|
+
state,
|
|
14
|
+
theme,
|
|
15
|
+
pickHint,
|
|
16
|
+
}: {
|
|
17
|
+
state: NavigatorHudState | null;
|
|
18
|
+
theme: NavigatorChromeTheme;
|
|
19
|
+
pickHint: string;
|
|
20
|
+
}) {
|
|
21
|
+
if (!state || !state.visible) return null;
|
|
22
|
+
|
|
23
|
+
if (state.pick) {
|
|
24
|
+
return (
|
|
25
|
+
<View
|
|
26
|
+
pointerEvents="none"
|
|
27
|
+
style={{
|
|
28
|
+
position: 'absolute',
|
|
29
|
+
top: 12,
|
|
30
|
+
left: 12,
|
|
31
|
+
right: 12,
|
|
32
|
+
padding: 14,
|
|
33
|
+
borderRadius: 14,
|
|
34
|
+
backgroundColor: 'rgba(120, 53, 15, 0.92)',
|
|
35
|
+
borderWidth: 2,
|
|
36
|
+
borderColor: '#facc15',
|
|
37
|
+
}}
|
|
38
|
+
>
|
|
39
|
+
<Text
|
|
40
|
+
style={{
|
|
41
|
+
textAlign: 'center',
|
|
42
|
+
fontSize: 14,
|
|
43
|
+
fontWeight: '600',
|
|
44
|
+
color: '#fef3c7',
|
|
45
|
+
}}
|
|
46
|
+
>
|
|
47
|
+
{pickHint}
|
|
48
|
+
</Text>
|
|
49
|
+
</View>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const speedLimit = state.speedLimitKmh;
|
|
54
|
+
const showSpeed = typeof speedLimit === 'number' && Number.isFinite(speedLimit) && speedLimit > 0;
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<View
|
|
58
|
+
pointerEvents="none"
|
|
59
|
+
style={{
|
|
60
|
+
position: 'absolute',
|
|
61
|
+
top: 12,
|
|
62
|
+
left: 12,
|
|
63
|
+
right: 12,
|
|
64
|
+
flexDirection: 'row',
|
|
65
|
+
alignItems: 'center',
|
|
66
|
+
gap: 12,
|
|
67
|
+
padding: 14,
|
|
68
|
+
borderRadius: 14,
|
|
69
|
+
backgroundColor: theme.background,
|
|
70
|
+
shadowColor: '#0f172a',
|
|
71
|
+
shadowOpacity: 0.32,
|
|
72
|
+
shadowRadius: 24,
|
|
73
|
+
shadowOffset: { width: 0, height: 10 },
|
|
74
|
+
elevation: 6,
|
|
75
|
+
}}
|
|
76
|
+
>
|
|
77
|
+
<View
|
|
78
|
+
style={{
|
|
79
|
+
width: 44,
|
|
80
|
+
height: 44,
|
|
81
|
+
borderRadius: 12,
|
|
82
|
+
backgroundColor: theme.iconBackground,
|
|
83
|
+
alignItems: 'center',
|
|
84
|
+
justifyContent: 'center',
|
|
85
|
+
}}
|
|
86
|
+
>
|
|
87
|
+
{typeof state.sign === 'number' ? (
|
|
88
|
+
<NavigatorManeuverIcon sign={state.sign} color={theme.iconForeground} />
|
|
89
|
+
) : null}
|
|
90
|
+
</View>
|
|
91
|
+
|
|
92
|
+
<View style={{ flex: 1, minWidth: 0, gap: 2 }}>
|
|
93
|
+
{state.distanceLabel ? (
|
|
94
|
+
<Text
|
|
95
|
+
numberOfLines={1}
|
|
96
|
+
style={{
|
|
97
|
+
fontSize: 18,
|
|
98
|
+
fontWeight: '700',
|
|
99
|
+
lineHeight: 20,
|
|
100
|
+
color: theme.foreground,
|
|
101
|
+
}}
|
|
102
|
+
>
|
|
103
|
+
{state.distanceLabel}
|
|
104
|
+
</Text>
|
|
105
|
+
) : null}
|
|
106
|
+
|
|
107
|
+
{state.maneuverText ? (
|
|
108
|
+
<Text
|
|
109
|
+
numberOfLines={1}
|
|
110
|
+
style={{ fontSize: 14, fontWeight: '500', color: theme.subtle }}
|
|
111
|
+
>
|
|
112
|
+
{state.maneuverText}
|
|
113
|
+
</Text>
|
|
114
|
+
) : null}
|
|
115
|
+
|
|
116
|
+
{state.streetName ? (
|
|
117
|
+
<Text numberOfLines={1} style={{ fontSize: 12, color: theme.muted }}>
|
|
118
|
+
{state.streetName}
|
|
119
|
+
</Text>
|
|
120
|
+
) : null}
|
|
121
|
+
|
|
122
|
+
{state.summaryText ? (
|
|
123
|
+
<Text
|
|
124
|
+
style={{
|
|
125
|
+
marginTop: 4,
|
|
126
|
+
paddingTop: 6,
|
|
127
|
+
borderTopWidth: 1,
|
|
128
|
+
borderTopColor: theme.divider,
|
|
129
|
+
fontSize: 12,
|
|
130
|
+
fontWeight: '600',
|
|
131
|
+
color: theme.summary,
|
|
132
|
+
lineHeight: 16,
|
|
133
|
+
}}
|
|
134
|
+
>
|
|
135
|
+
{state.summaryText}
|
|
136
|
+
</Text>
|
|
137
|
+
) : null}
|
|
138
|
+
|
|
139
|
+
{state.extrasText ? (
|
|
140
|
+
<Text style={{ marginTop: 2, fontSize: 11, fontWeight: '500', color: theme.muted }}>
|
|
141
|
+
{state.extrasText}
|
|
142
|
+
</Text>
|
|
143
|
+
) : null}
|
|
144
|
+
</View>
|
|
145
|
+
|
|
146
|
+
{showSpeed ? (
|
|
147
|
+
<View
|
|
148
|
+
style={{
|
|
149
|
+
width: 52,
|
|
150
|
+
height: 52,
|
|
151
|
+
borderRadius: 26,
|
|
152
|
+
borderWidth: 3,
|
|
153
|
+
borderColor: '#dc2626',
|
|
154
|
+
backgroundColor: '#ffffff',
|
|
155
|
+
alignItems: 'center',
|
|
156
|
+
justifyContent: 'center',
|
|
157
|
+
}}
|
|
158
|
+
>
|
|
159
|
+
<Text style={{ fontSize: 14, fontWeight: '800', color: '#0f172a' }}>
|
|
160
|
+
{Math.round(speedLimit as number)}
|
|
161
|
+
</Text>
|
|
162
|
+
</View>
|
|
163
|
+
) : null}
|
|
164
|
+
</View>
|
|
165
|
+
);
|
|
166
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Pressable } from 'react-native';
|
|
2
|
+
|
|
3
|
+
import { NavigatorRecenterIcon } from './navigatorManeuverIcon';
|
|
4
|
+
import type { NavigatorChromeTheme } from './navigatorChromeTheme';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* FAB «вернуть камеру на положение». При нажатии шлёт команду
|
|
8
|
+
* `recenterNavigatorCamera` в WebView — навигатор делает recenter.
|
|
9
|
+
*/
|
|
10
|
+
export function NavigatorRecenterButton({
|
|
11
|
+
theme,
|
|
12
|
+
accessibilityLabel,
|
|
13
|
+
onPress,
|
|
14
|
+
}: {
|
|
15
|
+
theme: NavigatorChromeTheme;
|
|
16
|
+
accessibilityLabel: string;
|
|
17
|
+
onPress: () => void;
|
|
18
|
+
}) {
|
|
19
|
+
return (
|
|
20
|
+
<Pressable
|
|
21
|
+
accessibilityRole="button"
|
|
22
|
+
accessibilityLabel={accessibilityLabel}
|
|
23
|
+
onPress={onPress}
|
|
24
|
+
style={({ pressed }) => ({
|
|
25
|
+
position: 'absolute',
|
|
26
|
+
right: 16,
|
|
27
|
+
bottom: 16,
|
|
28
|
+
width: 56,
|
|
29
|
+
height: 56,
|
|
30
|
+
borderRadius: 28,
|
|
31
|
+
alignItems: 'center',
|
|
32
|
+
justifyContent: 'center',
|
|
33
|
+
backgroundColor: theme.background,
|
|
34
|
+
shadowColor: '#0f172a',
|
|
35
|
+
shadowOpacity: 0.32,
|
|
36
|
+
shadowRadius: 24,
|
|
37
|
+
shadowOffset: { width: 0, height: 10 },
|
|
38
|
+
elevation: 6,
|
|
39
|
+
transform: [{ scale: pressed ? 0.96 : 1 }],
|
|
40
|
+
})}
|
|
41
|
+
>
|
|
42
|
+
<NavigatorRecenterIcon color={theme.iconForeground} />
|
|
43
|
+
</Pressable>
|
|
44
|
+
);
|
|
45
|
+
}
|