feat(smart-app): implement complete mobile app MVP

- App.tsx: full navigation (Auth stack + Main tabs with 5 screens)
- Auth: LoginScreen, RegisterScreen, ForgotPasswordScreen
- HomeScreen: dashboard with IoT metrics, weather widget, alerts, quick actions, sensors
- MapScreen: interactive map with layer toggles (6 layers)
- MarketplaceScreen: categories (6), products (5), search
- ChatScreen: AI chat with quick prompts (4), bot responses
- ProfileScreen: user info, stats, menu (9 items), logout
- AlertsScreen: alert list with severity, acknowledge
- SensorsScreen: sensor list with type filters (6 types), search
- ZonesScreen: zone cards with stats
- SettingsScreen: language picker (FR/EN/ES/DE), privacy, about
- Stores: iotStore (sensors, zones, alerts), notificationStore, uiStore + i18n
- Hooks: useSensors, useAlerts, useNotifications, useLocation
- Components: Card, Button, LoadingSpinner, ErrorBoundary, Header
- Services: iotService, notificationService (with axios API client)
- Utils: formatters (temp, AQI, noise, dates), validators (email, password, IBAN)
- Theme: colors.ts with full design system (Blue Ocean palette)
- Ditto: fixed MongoDB connection, new JWT secrets, official gateway image
This commit is contained in:
Eric FELIXINE
2026-06-01 18:00:35 -04:00
parent 08ca495bde
commit e30ae8ed09
35578 changed files with 3703534 additions and 43 deletions

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015 Airbnb
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,628 @@
# react-native-maps [![npm version](https://img.shields.io/npm/v/react-native-maps.svg?style=flat)](https://www.npmjs.com/package/react-native-maps)
React Native Map components for iOS + Android
## Contributing
This project is being maintained by a small group of people, and any help with issues and pull requests are always appreciated. If you are able and willing to contribute, please read the [guidelines](./CONTRIBUTING.md).
## Installation
See [Installation Instructions](docs/installation.md).
See [Setup Instructions for the Included Example Project](docs/examples-setup.md).
## Compatibility
## React Native Compatibility
### Version Requirements:
## Compatibility
### Fabric (New Architecture)
| Version | React Native Requirement |
| ---------------- | ------------------------ |
| 1.26.1+ | >= 0.81.1 |
| 1.26.0 and below | >= 0.76 |
### Old Architecture
| Version | React Native Requirement |
| --------------- | ------------------------ |
| 1.14.0 - 1.20.1 | >= 0.74 |
| < 1.14.0 | >= 0.64.3 |
## Component API
[`<MapView />` Component API](docs/mapview.md)
[`<Marker />` Component API](docs/marker.md)
[`<Callout />` Component API](docs/callout.md)
[`<Polygon />` Component API](docs/polygon.md)
[`<Polyline />` Component API](docs/polyline.md)
[`<Circle />` Component API](docs/circle.md)
[`<Overlay />` Component API](docs/overlay.md)
[`<Heatmap />` Component API](docs/heatmap.md)
[`<Geojson />` Component API](docs/geojson.md)
## General Usage
```js
import MapView from 'react-native-maps';
```
or
```js
var MapView = require('react-native-maps');
```
This MapView component is built so that features on the map (such as Markers, Polygons, etc.) are
specified as children of the MapView itself. This provides an intuitive and react-like API for
declaratively controlling features on the map.
### Rendering a Map with an initial region
## MapView
```jsx
<MapView
initialRegion={{
latitude: 37.78825,
longitude: -122.4324,
latitudeDelta: 0.0922,
longitudeDelta: 0.0421,
}}
/>
```
### Using a MapView while controlling the region as state
```jsx
getInitialState() {
return {
region: {
latitude: 37.78825,
longitude: -122.4324,
latitudeDelta: 0.0922,
longitudeDelta: 0.0421,
},
};
}
onRegionChange(region) {
this.setState({ region });
}
render() {
return (
<MapView
region={this.state.region}
onRegionChange={this.onRegionChange}
/>
);
}
```
### Rendering a list of markers on a map
```jsx
import {Marker} from 'react-native-maps';
<MapView region={this.state.region} onRegionChange={this.onRegionChange}>
{this.state.markers.map((marker, index) => (
<Marker
key={index}
coordinate={marker.latlng}
title={marker.title}
description={marker.description}
/>
))}
</MapView>;
```
### Rendering a Marker with a custom image
1. You need to generate an `png` image with various resolution (lets call them `custom_pin`) - for more information go to [Android](https://developer.android.com/studio/write/resource-manager#import), [iOS](https://developer.apple.com/documentation/xcode/adding-images-to-your-xcode-project)
2. put all images in Android drawables and iOS assets dir
3. Now you can use the following code:
```jsx
<Marker
coordinate={{latitude: latitude, longitude: longitude}}
image={{uri: 'custom_pin'}}
/>
```
Note: You can also pass the image binary data like `image={require('custom_pin.png')}`, but this will not scale good with the different screen sizes.
### Rendering a Marker with a custom view
Note: This has performance implications, if you wish for a simpler solution go with a custom image (save your self the headache)
```jsx
<Marker coordinate={{latitude: latitude, longitude: longitude}}>
<MyCustomMarkerView {...marker} />
</Marker>
```
### Rendering a custom Marker with a custom Callout
```jsx
import {Callout} from 'react-native-maps';
<Marker coordinate={marker.latlng}>
<MyCustomMarkerView {...marker} />
<Callout>
<MyCustomCalloutView {...marker} />
</Callout>
</Marker>;
```
### Draggable Markers
```jsx
<MapView initialRegion={...}>
<Marker draggable
coordinate={this.state.x}
onDragEnd={(e) => this.setState({ x: e.nativeEvent.coordinate })}
/>
</MapView>
```
### Using a custom Tile Overlay
#### Tile Overlay using tile server
```jsx
import {UrlTile} from 'react-native-maps';
<MapView region={this.state.region} onRegionChange={this.onRegionChange}>
<UrlTile
/**
* The url template of the tile server. The patterns {x} {y} {z} will be replaced at runtime
* For example, http://c.tile.openstreetmap.org/{z}/{x}/{y}.png
*/
urlTemplate={this.state.urlTemplate}
/**
* The maximum zoom level for this tile overlay. Corresponds to the maximumZ setting in
* MKTileOverlay. iOS only.
*/
maximumZ={19}
/**
* flipY allows tiles with inverted y coordinates (origin at bottom left of map)
* to be used. Its default value is false.
*/
flipY={false}
/>
</MapView>;
```
For Android: add the following line in your AndroidManifest.xml
```xml
<uses-permission android:name="android.permission.INTERNET" />
```
For IOS: configure [App Transport Security](https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html#//apple_ref/doc/uid/TP40009251-SW33) in your app
#### Tile Overlay using local tiles
Tiles can be stored locally within device using xyz tiling scheme and displayed as tile overlay as well. This is usefull especially for offline map usage when tiles are available for selected map region within device storage.
```jsx
import {LocalTile} from 'react-native-maps';
<MapView region={this.state.region} onRegionChange={this.onRegionChange}>
<LocalTile
/**
* The path template of the locally stored tiles. The patterns {x} {y} {z} will be replaced at runtime
* For example, /storage/emulated/0/mytiles/{z}/{x}/{y}.png
*/
pathTemplate={this.state.pathTemplate}
/**
* The size of provided local tiles (usually 256 or 512).
*/
tileSize={256}
/>
</MapView>;
```
For Android: LocalTile is still just overlay over original map tiles. It means that if device is online, underlying tiles will be still downloaded. If original tiles download/display is not desirable set mapType to 'none'. For example:
```
<MapView
mapType={Platform.OS == "android" ? "none" : "standard"}
>
```
See [OSM Wiki](https://wiki.openstreetmap.org/wiki/Category:Tile_downloading) for how to download tiles for offline usage.
### Overlaying other components on the map
Place components that you wish to overlay `MapView` underneath the `MapView` closing tag. Absolutely position these elements.
```jsx
render() {
return (
<MapView
region={this.state.region}
/>
<OverlayComponent
style={{position: "absolute", bottom: 50}}
/>
);
}
```
### Customizing the map style (Google Maps Only)
The `<MapView provider="google" googleMapId="yourStyledMapId" />` Google Maps on iOS and Android supports styling via google cloud platform, the styled maps are published under a googleMapId, by simply setting the property googleMapId to the MapView you can use that styled map
more info here: [google map id](https://developers.google.com/maps/documentation/get-map-id)
### MapView Events
The `<MapView />` component and its child components have several events that you can subscribe to.
This example displays some of them in a log as a demonstration.
![](http://i.giphy.com/3o6UBpncYQASu2WTW8.gif) ![](http://i.giphy.com/xT77YdviLqtjaecRYA.gif)
### Tracking Region / Location
![](http://i.giphy.com/3o6UBoPSLlIKQ2dv7q.gif) ![](http://i.giphy.com/xT77XWjqECvdgjx9oA.gif)
### Programmatically Changing Region
One can change the mapview's position using refs and component methods, or by passing in an updated
`region` prop. The component methods will allow one to animate to a given position like the native
API could.
![](http://i.giphy.com/3o6UB7poyB6YJ0KPWU.gif) ![](http://i.giphy.com/xT77Yc4wK3pzZusEbm.gif)
### Changing the style of the map
![](http://i.imgur.com/a9WqCL6.png)
### Arbitrary React Views as Markers
![](http://i.giphy.com/3o6UBcsCLoLQtksJxe.gif) ![](http://i.giphy.com/3o6UB1qGEM9jYni3KM.gif)
### Using the MapView with the Animated API
The `<MapView />` component can be made to work with the Animated API, having the entire `region` prop
be declared as an animated value. This allows one to animate the zoom and position of the MapView along
with other gestures, giving a nice feel.
Further, Marker views can use the animated API to enhance the effect.
![](http://i.giphy.com/xT77XMw9IwS6QAv0nC.gif) ![](http://i.giphy.com/3o6UBdGQdM1GmVoIdq.gif)
Issue: Since android needs to render its marker views as a bitmap, the animations APIs may not be
compatible with the Marker views. Not sure if this can be worked around yet or not.
Markers' coordinates can also be animated, as shown in this example:
![](http://i.giphy.com/xTcnTelp1OwGPu1Wh2.gif) ![](http://i.giphy.com/xTcnT6WVpwlCiQnFW8.gif)
### Polygon Creator
![](http://i.giphy.com/3o6UAZWqQBkOzs8HE4.gif) ![](http://i.giphy.com/xT77XVBRErNZl3zyWQ.gif)
### Other Overlays
So far, `<Circle />`, `<Polygon />`, and `<Polyline />` are available to pass in as children to the
`<MapView />` component.
![](http://i.giphy.com/xT77XZCH8JpEhzVcNG.gif) ![](http://i.giphy.com/xT77XZyA0aYeOX5jsA.gif)
### Gradient Polylines (iOS MapKit only)
Gradient polylines can be created using the `strokeColors` prop of the `<Polyline>` component.
![](https://i.imgur.com/P7UeqAm.png?1)
### Default Markers
Default markers will be rendered unless a custom marker is specified. One can optionally adjust the
color of the default marker by using the `pinColor` prop.
![](http://i.giphy.com/xT77Y0pWKmUUnguHK0.gif) ![](http://i.giphy.com/3o6UBfk3I58VIwZjVe.gif)
### Custom Callouts
Callouts to markers can be completely arbitrary react views, similar to markers. As a result, they
can be interacted with like any other view.
Additionally, you can fall back to the standard behavior of just having a title/description through
the `<Marker />`'s `title` and `description` props.
Custom callout views can be the entire tooltip bubble, or just the content inside of the system
default bubble.
To handle press on specific subview of callout use `<CalloutSubview />` with `onPress`.
See `Callouts.js` example.
![](http://i.giphy.com/xT77XNePGnMIIDpbnq.gif) ![](http://i.giphy.com/xT77YdU0HXryvoRqaQ.gif)
### Image-based Markers
Markers can be customized by just using images, and specified using the `image` prop.
![](http://i.imgur.com/mzrOjTR.png)
### Draggable Markers
Markers are draggable, and emit continuous drag events to update other UI during drags.
![](http://i.giphy.com/l2JImnZxdv1WbpQfC.gif) ![](http://i.giphy.com/l2JIhv4Jx6Ugx1EGI.gif)
### Lite Mode ( Android )
Enable lite mode on Android with `liteMode` prop. Ideal when having multiple maps in a View or ScrollView.
![](http://i.giphy.com/qZ2lAf18s89na.gif)
### On Poi Click (Google Maps Only)
Poi are clickable, you can catch the event to get its information (usually to get the full detail from Google Place using the placeId).
![](https://media.giphy.com/media/3480VsCKnHr31uCLU3/giphy.gif)
### Animated Region
The MapView can accept an `AnimatedRegion` value as its `region` prop. This allows you to utilize the Animated API to control the map's center and zoom.
```jsx
import MapView, { AnimatedRegion, Animated } from 'react-native-maps';
getInitialState() {
return {
region: new AnimatedRegion({
latitude: LATITUDE,
longitude: LONGITUDE,
latitudeDelta: LATITUDE_DELTA,
longitudeDelta: LONGITUDE_DELTA,
}),
};
}
onRegionChange(region) {
this.state.region.setValue(region);
}
render() {
return (
<Animated
region={this.state.region}
onRegionChange={this.onRegionChange}
/>
);
}
```
### Animated Marker Position
Markers can also accept an `AnimatedRegion` value as a coordinate.
```jsx
import MapView, { AnimatedRegion, MarkerAnimated } from 'react-native-maps';
getInitialState() {
return {
coordinate: new AnimatedRegion({
latitude: LATITUDE,
longitude: LONGITUDE,
}),
};
}
componentWillReceiveProps(nextProps) {
const duration = 500
if (this.props.coordinate !== nextProps.coordinate) {
if (Platform.OS === 'android') {
if (this.marker) {
this.marker.animateMarkerToCoordinate(
nextProps.coordinate,
duration
);
}
} else {
this.state.coordinate.timing({
...nextProps.coordinate,
useNativeDriver: true, // defaults to false if not passed explicitly
duration
}).start();
}
}
}
render() {
return (
<MapView initialRegion={...}>
<MarkerAnimated
ref={marker => { this.marker = marker }}
coordinate={this.state.coordinate}
/>
</MapView>
);
}
```
### Take Snapshot of map
```jsx
import MapView, { Marker } from 'react-native-maps';
getInitialState() {
return {
coordinate: {
latitude: LATITUDE,
longitude: LONGITUDE,
},
};
}
takeSnapshot () {
// 'takeSnapshot' takes a config object with the
// following options
const snapshot = this.map.takeSnapshot({
width: 300, // optional, when omitted the view-width is used
height: 300, // optional, when omitted the view-height is used
region: {..}, // iOS only, optional region to render
format: 'png', // image formats: 'png', 'jpg' (default: 'png')
quality: 0.8, // image quality: 0..1 (only relevant for jpg, default: 1)
result: 'file' // result types: 'file', 'base64' (default: 'file')
});
snapshot.then((uri) => {
this.setState({ mapSnapshot: uri });
});
}
render() {
return (
<View>
<MapView initialRegion={...} ref={map => { this.map = map }}>
<Marker coordinate={this.state.coordinate} />
</MapView>
<Image source={{ uri: this.state.mapSnapshot.uri }} />
<TouchableOpacity onPress={this.takeSnapshot}>
Take Snapshot
</TouchableOpacity>
</View>
);
}
```
### Zoom to Specified Markers
Pass an array of marker identifiers to have the map re-focus.
![](http://i.giphy.com/3o7qEbOQnO0yoXqKJ2.gif) ![](http://i.giphy.com/l41YdrQZ7m6Dz4h0c.gif)
### Zoom to Specified Coordinates
Pass an array of coordinates to focus a map region on said coordinates.
![](https://cloud.githubusercontent.com/assets/1627824/18609960/da5d9e06-7cdc-11e6-811e-34e255093df9.gif)
### Troubleshooting
#### My map is blank
- Make sure that you have [properly installed](docs/installation.md) react-native-maps.
- Check in the logs if there is more informations about the issue.
- Try setting the style of the MapView to an absolute position with top, left, right and bottom values set.
- Make sure you have enabled Google Maps API in [Google developer console](https://console.developers.google.com/apis/library)
```javascript
const styles = StyleSheet.create({
map: {
...StyleSheet.absoluteFillObject,
},
});
```
```jsx
<MapView
style={styles.map}
// other props
/>
```
#### Inputs don't focus
- When inputs don't focus or elements don't respond to tap, look at the order of the view hierarchy, sometimes the issue could be due to ordering of rendered components, prefer putting MapView as the first component.
Bad:
```jsx
<View>
<TextInput />
<MapView />
</View>
```
Good:
```jsx
<View>
<MapView />
<TextInput />
</View>
```
#### Children Components Not Re-Rendering
Components that aren't declared by this library (Ex: Markers, Polyline) must not be children of the MapView component due to MapView's unique rendering methodology. Have your custom components / views outside the MapView component and position absolute to ensure they only re-render as needed.
Example:
Bad:
```jsx
<View style={StyleSheet.absoluteFillObject}>
<MapView style={StyleSheet.absoluteFillObject}>
<View style={{position: 'absolute', top: 100, left: 50}} />
</MapView>
</View>
```
Good:
```jsx
<View style={StyleSheet.absoluteFillObject}>
<MapView style={StyleSheet.absoluteFillObject} />
<View style={{position: 'absolute', top: 100, left: 50}} />
</View>
```
Source: https://github.com/react-native-maps/react-native-maps/issues/1901
#### Crashing with EXC_BAD_ACCESS on iOS when switching apps
`<MapView>` using Apple Maps in `mapType: "standard"` will sometimes crash when you background the app or switch into another app. This is only an issue in XCode using Metal API Validation, and won't happen in production. To eliminate this problem even while debugging in XCode, go to `Edit Scheme... -> Run (Debug) -> Diagnostics` and uncheck `Metal -> API Validation`. (h/t [@Simon-TechForm](https://github.com/Simon-TechForm)).
Source: https://github.com/react-native-maps/react-native-maps/issues/3957#issuecomment-924161121
#### onRegionChangeComplete() callback is called infinitely
If changing the state in `onRegionChangeComplete` is called infinitely, add a condition to limit these calls to occur only when the region change was done as a result of a user's action.
```javascript
onRegionChangeComplete={ (region, gesture) => {
// This fix only works on Google Maps because isGesture is NOT available on Apple Maps
if (!gesture.isGesture) {
return;
}
// You can use
dispatch({ type: "map_region", payload: { mapRegion: region }}); // if using useReducer
// setMapRegionState(region); // if using useState
}}
```
Source: https://github.com/react-native-maps/react-native-maps/issues/846#issuecomment-1210079461
## License
Copyright (c) 2017 Airbnb
Licensed under the The MIT License (MIT) (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://raw.githubusercontent.com/airbnb/react-native-maps/master/LICENSE
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -0,0 +1,106 @@
def safeExtGet(prop, fallback) {
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
}
buildscript {
def kotlin_version = rootProject.ext.has('kotlinVersion') ? rootProject.ext.get('kotlinVersion') : '2.0.21'
// The Android Gradle plugin is only required when opening the android folder stand-alone.
// This avoids unnecessary downloads and potential conflicts when the library is included as a
// module dependency in an application project.
if (project == rootProject) {
repositories {
google()
mavenCentral()
}
dependencies {
classpath("com.android.tools.build:gradle:8.7.2")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlin_version}")
}
}
}
def supportsNamespace() {
def parsed = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')
def major = parsed[0].toInteger()
def minor = parsed[1].toInteger()
// Namespace support was added in 7.3.0
if (major == 7 && minor >= 3) {
return true
}
return major >= 8
}
def isNewArchitectureEnabled() {
return project.hasProperty("newArchEnabled") && (project.newArchEnabled == "true" || project.newArchEnabled == true)
}
apply plugin: 'com.android.library'
if (isNewArchitectureEnabled()) {
apply plugin: 'com.facebook.react'
}
apply plugin: 'kotlin-android'
android {
if (supportsNamespace()) {
namespace "com.rnmaps.maps"
}
if (rootProject.hasProperty("ndkPath")) {
ndkPath rootProject.ext.ndkPath
}
if (rootProject.hasProperty("ndkVersion")) {
ndkVersion rootProject.ext.ndkVersion
}
compileSdk safeExtGet('compileSdkVersion', 35)
defaultConfig {
minSdkVersion safeExtGet('minSdkVersion', 21)
targetSdkVersion safeExtGet('targetSdkVersion', 35)
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
ndk {
abiFilters(*reactNativeArchitectures())
}
}
packagingOptions {
excludes = [
"META-INF",
"META-INF/**",
"**/libreact_render*.so",
]
}
buildFeatures {
buildConfig = true
prefab = true
}
}
def reactNativeArchitectures() {
def value = project.getProperties().get("reactNativeArchitectures")
return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
}
repositories {
mavenLocal()
mavenCentral()
maven {
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
url "$projectDir/../node_modules/react-native/android"
}
google()
}
dependencies {
implementation 'com.facebook.react:react-native:+'
implementation "com.google.android.gms:play-services-base:${safeExtGet('googlePlayServicesBaseVersion', '18.5.0')}"
implementation "com.google.android.gms:play-services-maps:${safeExtGet('googlePlayServicesMapsVersion', '19.1.0')}"
implementation "com.google.android.gms:play-services-location:${safeExtGet('googlePlayServicesLocationVersion', '21.3.0')}"
implementation 'com.google.maps.android:android-maps-utils:3.10.0'
implementation "androidx.work:work-runtime:2.9.1"
}

View File

@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -0,0 +1,251 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

View File

@@ -0,0 +1,94 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@@ -0,0 +1,2 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>

View File

@@ -0,0 +1,25 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
#if __has_include(<React-RCTAppDelegate/RCTDependencyProvider.h>)
#import <React-RCTAppDelegate/RCTDependencyProvider.h>
#elif __has_include(<React_RCTAppDelegate/RCTDependencyProvider.h>)
#import <React_RCTAppDelegate/RCTDependencyProvider.h>
#else
#import "RCTDependencyProvider.h"
#endif
NS_ASSUME_NONNULL_BEGIN
@interface RCTAppDependencyProvider : NSObject <RCTDependencyProvider>
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,55 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTAppDependencyProvider.h"
#import <ReactCodegen/RCTModulesConformingToProtocolsProvider.h>
#import <ReactCodegen/RCTThirdPartyComponentsProvider.h>
@implementation RCTAppDependencyProvider {
NSArray<NSString *> * _URLRequestHandlerClassNames;
NSArray<NSString *> * _imageDataDecoderClassNames;
NSArray<NSString *> * _imageURLLoaderClassNames;
NSDictionary<NSString *,Class<RCTComponentViewProtocol>> * _thirdPartyFabricComponents;
}
- (nonnull NSArray<NSString *> *)URLRequestHandlerClassNames {
static dispatch_once_t requestUrlToken;
dispatch_once(&requestUrlToken, ^{
self->_URLRequestHandlerClassNames = RCTModulesConformingToProtocolsProvider.URLRequestHandlerClassNames;
});
return _URLRequestHandlerClassNames;
}
- (nonnull NSArray<NSString *> *)imageDataDecoderClassNames {
static dispatch_once_t dataDecoderToken;
dispatch_once(&dataDecoderToken, ^{
_imageDataDecoderClassNames = RCTModulesConformingToProtocolsProvider.imageDataDecoderClassNames;
});
return _imageDataDecoderClassNames;
}
- (nonnull NSArray<NSString *> *)imageURLLoaderClassNames {
static dispatch_once_t urlLoaderToken;
dispatch_once(&urlLoaderToken, ^{
_imageURLLoaderClassNames = RCTModulesConformingToProtocolsProvider.imageURLLoaderClassNames;
});
return _imageURLLoaderClassNames;
}
- (nonnull NSDictionary<NSString *,Class<RCTComponentViewProtocol>> *)thirdPartyFabricComponents {
static dispatch_once_t nativeComponentsToken;
dispatch_once(&nativeComponentsToken, ^{
_thirdPartyFabricComponents = RCTThirdPartyComponentsProvider.thirdPartyFabricComponents;
});
return _thirdPartyFabricComponents;
}
@end

View File

@@ -0,0 +1,18 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
@interface RCTModulesConformingToProtocolsProvider: NSObject
+(NSArray<NSString *> *)imageURLLoaderClassNames;
+(NSArray<NSString *> *)imageDataDecoderClassNames;
+(NSArray<NSString *> *)URLRequestHandlerClassNames;
@end

View File

@@ -0,0 +1,33 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTModulesConformingToProtocolsProvider.h"
@implementation RCTModulesConformingToProtocolsProvider
+(NSArray<NSString *> *)imageURLLoaderClassNames
{
return @[
];
}
+(NSArray<NSString *> *)imageDataDecoderClassNames
{
return @[
];
}
+(NSArray<NSString *> *)URLRequestHandlerClassNames
{
return @[
];
}
@end

View File

@@ -0,0 +1,16 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
@protocol RCTComponentViewProtocol;
@interface RCTThirdPartyComponentsProvider: NSObject
+ (NSDictionary<NSString *, Class<RCTComponentViewProtocol>> *)thirdPartyFabricComponents;
@end

View File

@@ -0,0 +1,26 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
#import "RCTThirdPartyComponentsProvider.h"
#import <React/RCTComponentViewProtocol.h>
@implementation RCTThirdPartyComponentsProvider
+ (NSDictionary<NSString *, Class<RCTComponentViewProtocol>> *)thirdPartyFabricComponents
{
return @{
@"RNMapsGoogleMapView": NSClassFromString(@"RNMapsGoogleMapView"), // react-native-maps
@"RNMapsGooglePolygon": NSClassFromString(@"RNMapsGooglePolygonView"), // react-native-maps
@"RNMapsMapView": NSClassFromString(@"RNMapsMapView"), // react-native-maps
@"RNMapsMarker": NSClassFromString(@"RNMapsMarkerView"), // react-native-maps
};
}
@end

View File

@@ -0,0 +1,34 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
version = "0.77.2"
source = { :git => 'https://github.com/facebook/react-native.git' }
if version == '1000.0.0'
# This is an unpublished version, use the latest commit hash of the react-native repo, which were presumably in.
source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1")
else
source[:tag] = "v#{version}"
end
Pod::Spec.new do |s|
s.name = "ReactAppDependencyProvider"
s.version = version
s.summary = "The third party dependency provider for the app"
s.homepage = "https://reactnative.dev/"
s.documentation_url = "https://reactnative.dev/"
s.license = "MIT"
s.author = "Meta Platforms, Inc. and its affiliates"
s.platforms = min_supported_versions
s.source = source
s.source_files = "**/RCTAppDependencyProvider.{h,mm}"
# This guard prevent to install the dependencies when we run `pod install` in the old architecture.
s.pod_target_xcconfig = {
"CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(),
"DEFINES_MODULE" => "YES"
}
s.dependency "ReactCodegen"
end

View File

@@ -0,0 +1,63 @@
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* @generated by codegen project: GenerateModuleJavaSpec.js
*
* @nolint
*/
package com.facebook.fbreact.specs;
import com.facebook.proguard.annotations.DoNotStrip;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.turbomodule.core.interfaces.TurboModule;
import javax.annotation.Nonnull;
public abstract class NativeAirMapsModuleSpec extends ReactContextBaseJavaModule implements TurboModule {
public static final String NAME = "RNMapsAirModule";
public NativeAirMapsModuleSpec(ReactApplicationContext reactContext) {
super(reactContext);
}
@Override
public @Nonnull String getName() {
return NAME;
}
@ReactMethod
@DoNotStrip
public abstract void getCamera(double tag, Promise promise);
@ReactMethod
@DoNotStrip
public abstract void getMarkersFrames(double tag, boolean onlyVisible, Promise promise);
@ReactMethod
@DoNotStrip
public abstract void getMapBoundaries(double tag, Promise promise);
@ReactMethod
@DoNotStrip
public abstract void takeSnapshot(double tag, String config, Promise promise);
@ReactMethod
@DoNotStrip
public abstract void getAddressFromCoordinates(double tag, ReadableMap coordinate, Promise promise);
@ReactMethod
@DoNotStrip
public abstract void getPointForCoordinate(double tag, ReadableMap coordinate, Promise promise);
@ReactMethod
@DoNotStrip
public abstract void getCoordinateForPoint(double tag, ReadableMap point, Promise promise);
}

View File

@@ -0,0 +1,35 @@
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* @generated by codegen project: GeneratePropsJavaDelegate.js
*/
package com.facebook.react.viewmanagers;
import android.view.View;
import androidx.annotation.Nullable;
import com.facebook.react.uimanager.BaseViewManager;
import com.facebook.react.uimanager.BaseViewManagerDelegate;
import com.facebook.react.uimanager.LayoutShadowNode;
public class RNMapsCalloutManagerDelegate<T extends View, U extends BaseViewManager<T, ? extends LayoutShadowNode> & RNMapsCalloutManagerInterface<T>> extends BaseViewManagerDelegate<T, U> {
public RNMapsCalloutManagerDelegate(U viewManager) {
super(viewManager);
}
@Override
public void setProperty(T view, String propName, @Nullable Object value) {
switch (propName) {
case "alphaHitTest":
mViewManager.setAlphaHitTest(view, value == null ? false : (boolean) value);
break;
case "tooltip":
mViewManager.setTooltip(view, value == null ? false : (boolean) value);
break;
default:
super.setProperty(view, propName, value);
}
}
}

View File

@@ -0,0 +1,18 @@
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* @generated by codegen project: GeneratePropsJavaInterface.js
*/
package com.facebook.react.viewmanagers;
import android.view.View;
import com.facebook.react.uimanager.ViewManagerWithGeneratedInterface;
public interface RNMapsCalloutManagerInterface<T extends View> extends ViewManagerWithGeneratedInterface {
void setAlphaHitTest(T view, boolean value);
void setTooltip(T view, boolean value);
}

View File

@@ -0,0 +1,49 @@
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* @generated by codegen project: GeneratePropsJavaDelegate.js
*/
package com.facebook.react.viewmanagers;
import android.view.View;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ColorPropConverter;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.uimanager.BaseViewManager;
import com.facebook.react.uimanager.BaseViewManagerDelegate;
import com.facebook.react.uimanager.LayoutShadowNode;
public class RNMapsCircleManagerDelegate<T extends View, U extends BaseViewManager<T, ? extends LayoutShadowNode> & RNMapsCircleManagerInterface<T>> extends BaseViewManagerDelegate<T, U> {
public RNMapsCircleManagerDelegate(U viewManager) {
super(viewManager);
}
@Override
public void setProperty(T view, String propName, @Nullable Object value) {
switch (propName) {
case "center":
mViewManager.setCenter(view, (ReadableMap) value);
break;
case "fillColor":
mViewManager.setFillColor(view, ColorPropConverter.getColor(value, view.getContext()));
break;
case "radius":
mViewManager.setRadius(view, value == null ? Double.NaN : ((Double) value).doubleValue());
break;
case "strokeColor":
mViewManager.setStrokeColor(view, ColorPropConverter.getColor(value, view.getContext()));
break;
case "strokeWidth":
mViewManager.setStrokeWidth(view, value == null ? 1f : ((Double) value).floatValue());
break;
case "tappable":
mViewManager.setTappable(view, value == null ? false : (boolean) value);
break;
default:
super.setProperty(view, propName, value);
}
}
}

View File

@@ -0,0 +1,24 @@
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* @generated by codegen project: GeneratePropsJavaInterface.js
*/
package com.facebook.react.viewmanagers;
import android.view.View;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.uimanager.ViewManagerWithGeneratedInterface;
public interface RNMapsCircleManagerInterface<T extends View> extends ViewManagerWithGeneratedInterface {
void setCenter(T view, @Nullable ReadableMap value);
void setFillColor(T view, @Nullable Integer value);
void setRadius(T view, double value);
void setStrokeColor(T view, @Nullable Integer value);
void setStrokeWidth(T view, float value);
void setTappable(T view, boolean value);
}

View File

@@ -0,0 +1,52 @@
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* @generated by codegen project: GeneratePropsJavaDelegate.js
*/
package com.facebook.react.viewmanagers;
import android.view.View;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ColorPropConverter;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.uimanager.BaseViewManager;
import com.facebook.react.uimanager.BaseViewManagerDelegate;
import com.facebook.react.uimanager.LayoutShadowNode;
public class RNMapsGooglePolygonManagerDelegate<T extends View, U extends BaseViewManager<T, ? extends LayoutShadowNode> & RNMapsGooglePolygonManagerInterface<T>> extends BaseViewManagerDelegate<T, U> {
public RNMapsGooglePolygonManagerDelegate(U viewManager) {
super(viewManager);
}
@Override
public void setProperty(T view, String propName, @Nullable Object value) {
switch (propName) {
case "coordinates":
mViewManager.setCoordinates(view, (ReadableArray) value);
break;
case "fillColor":
mViewManager.setFillColor(view, ColorPropConverter.getColor(value, view.getContext()));
break;
case "strokeColor":
mViewManager.setStrokeColor(view, ColorPropConverter.getColor(value, view.getContext()));
break;
case "strokeWidth":
mViewManager.setStrokeWidth(view, value == null ? 1f : ((Double) value).floatValue());
break;
case "geodesic":
mViewManager.setGeodesic(view, value == null ? false : (boolean) value);
break;
case "holes":
mViewManager.setHoles(view, (ReadableArray) value);
break;
case "tappable":
mViewManager.setTappable(view, value == null ? false : (boolean) value);
break;
default:
super.setProperty(view, propName, value);
}
}
}

View File

@@ -0,0 +1,25 @@
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* @generated by codegen project: GeneratePropsJavaInterface.js
*/
package com.facebook.react.viewmanagers;
import android.view.View;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.uimanager.ViewManagerWithGeneratedInterface;
public interface RNMapsGooglePolygonManagerInterface<T extends View> extends ViewManagerWithGeneratedInterface {
void setCoordinates(T view, @Nullable ReadableArray value);
void setFillColor(T view, @Nullable Integer value);
void setStrokeColor(T view, @Nullable Integer value);
void setStrokeWidth(T view, float value);
void setGeodesic(T view, boolean value);
void setHoles(T view, @Nullable ReadableArray value);
void setTappable(T view, boolean value);
}

View File

@@ -0,0 +1,215 @@
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* @generated by codegen project: GeneratePropsJavaDelegate.js
*/
package com.facebook.react.viewmanagers;
import android.view.View;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ColorPropConverter;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.uimanager.BaseViewManager;
import com.facebook.react.uimanager.BaseViewManagerDelegate;
import com.facebook.react.uimanager.LayoutShadowNode;
public class RNMapsMapViewManagerDelegate<T extends View, U extends BaseViewManager<T, ? extends LayoutShadowNode> & RNMapsMapViewManagerInterface<T>> extends BaseViewManagerDelegate<T, U> {
public RNMapsMapViewManagerDelegate(U viewManager) {
super(viewManager);
}
@Override
public void setProperty(T view, String propName, @Nullable Object value) {
switch (propName) {
case "cacheEnabled":
mViewManager.setCacheEnabled(view, value == null ? false : (boolean) value);
break;
case "camera":
mViewManager.setCamera(view, (ReadableMap) value);
break;
case "compassOffset":
mViewManager.setCompassOffset(view, (ReadableMap) value);
break;
case "followsUserLocation":
mViewManager.setFollowsUserLocation(view, value == null ? false : (boolean) value);
break;
case "poiClickEnabled":
mViewManager.setPoiClickEnabled(view, value == null ? false : (boolean) value);
break;
case "initialCamera":
mViewManager.setInitialCamera(view, (ReadableMap) value);
break;
case "initialRegion":
mViewManager.setInitialRegion(view, (ReadableMap) value);
break;
case "kmlSrc":
mViewManager.setKmlSrc(view, value == null ? null : (String) value);
break;
case "legalLabelInsets":
mViewManager.setLegalLabelInsets(view, (ReadableMap) value);
break;
case "liteMode":
mViewManager.setLiteMode(view, value == null ? false : (boolean) value);
break;
case "googleMapId":
mViewManager.setGoogleMapId(view, value == null ? null : (String) value);
break;
case "googleRenderer":
mViewManager.setGoogleRenderer(view, (String) value);
break;
case "loadingBackgroundColor":
mViewManager.setLoadingBackgroundColor(view, ColorPropConverter.getColor(value, view.getContext()));
break;
case "loadingEnabled":
mViewManager.setLoadingEnabled(view, value == null ? false : (boolean) value);
break;
case "loadingIndicatorColor":
mViewManager.setLoadingIndicatorColor(view, ColorPropConverter.getColor(value, view.getContext()));
break;
case "mapPadding":
mViewManager.setMapPadding(view, (ReadableMap) value);
break;
case "mapType":
mViewManager.setMapType(view, (String) value);
break;
case "maxDelta":
mViewManager.setMaxDelta(view, value == null ? 0f : ((Double) value).doubleValue());
break;
case "maxZoom":
mViewManager.setMaxZoom(view, value == null ? 0f : ((Double) value).floatValue());
break;
case "minDelta":
mViewManager.setMinDelta(view, value == null ? 0f : ((Double) value).doubleValue());
break;
case "minZoom":
mViewManager.setMinZoom(view, value == null ? 0f : ((Double) value).floatValue());
break;
case "moveOnMarkerPress":
mViewManager.setMoveOnMarkerPress(view, value == null ? true : (boolean) value);
break;
case "handlePanDrag":
mViewManager.setHandlePanDrag(view, value == null ? false : (boolean) value);
break;
case "paddingAdjustmentBehavior":
mViewManager.setPaddingAdjustmentBehavior(view, (String) value);
break;
case "pitchEnabled":
mViewManager.setPitchEnabled(view, value == null ? true : (boolean) value);
break;
case "region":
mViewManager.setRegion(view, (ReadableMap) value);
break;
case "rotateEnabled":
mViewManager.setRotateEnabled(view, value == null ? true : (boolean) value);
break;
case "scrollDuringRotateOrZoomEnabled":
mViewManager.setScrollDuringRotateOrZoomEnabled(view, value == null ? true : (boolean) value);
break;
case "scrollEnabled":
mViewManager.setScrollEnabled(view, value == null ? true : (boolean) value);
break;
case "showsBuildings":
mViewManager.setShowsBuildings(view, value == null ? true : (boolean) value);
break;
case "showsCompass":
mViewManager.setShowsCompass(view, value == null ? true : (boolean) value);
break;
case "showsIndoorLevelPicker":
mViewManager.setShowsIndoorLevelPicker(view, value == null ? false : (boolean) value);
break;
case "showsIndoors":
mViewManager.setShowsIndoors(view, value == null ? true : (boolean) value);
break;
case "showsPointsOfInterests":
mViewManager.setShowsPointsOfInterests(view, value == null ? true : (boolean) value);
break;
case "pointsOfInterestFilter":
mViewManager.setPointsOfInterestFilter(view, (ReadableArray) value);
break;
case "showsMyLocationButton":
mViewManager.setShowsMyLocationButton(view, value == null ? false : (boolean) value);
break;
case "showsScale":
mViewManager.setShowsScale(view, value == null ? false : (boolean) value);
break;
case "showsUserLocation":
mViewManager.setShowsUserLocation(view, value == null ? false : (boolean) value);
break;
case "tintColor":
mViewManager.setTintColor(view, ColorPropConverter.getColor(value, view.getContext()));
break;
case "toolbarEnabled":
mViewManager.setToolbarEnabled(view, value == null ? true : (boolean) value);
break;
case "userInterfaceStyle":
mViewManager.setUserInterfaceStyle(view, (String) value);
break;
case "customMapStyleString":
mViewManager.setCustomMapStyleString(view, value == null ? null : (String) value);
break;
case "userLocationAnnotationTitle":
mViewManager.setUserLocationAnnotationTitle(view, value == null ? null : (String) value);
break;
case "userLocationCalloutEnabled":
mViewManager.setUserLocationCalloutEnabled(view, value == null ? false : (boolean) value);
break;
case "userLocationFastestInterval":
mViewManager.setUserLocationFastestInterval(view, value == null ? 5000 : ((Double) value).intValue());
break;
case "userLocationPriority":
mViewManager.setUserLocationPriority(view, (String) value);
break;
case "userLocationUpdateInterval":
mViewManager.setUserLocationUpdateInterval(view, value == null ? 5000 : ((Double) value).intValue());
break;
case "zoomControlEnabled":
mViewManager.setZoomControlEnabled(view, value == null ? true : (boolean) value);
break;
case "zoomEnabled":
mViewManager.setZoomEnabled(view, value == null ? true : (boolean) value);
break;
case "showsTraffic":
mViewManager.setShowsTraffic(view, value == null ? false : (boolean) value);
break;
case "zoomTapEnabled":
mViewManager.setZoomTapEnabled(view, value == null ? true : (boolean) value);
break;
case "cameraZoomRange":
mViewManager.setCameraZoomRange(view, (ReadableMap) value);
break;
default:
super.setProperty(view, propName, value);
}
}
@Override
public void receiveCommand(T view, String commandName, ReadableArray args) {
switch (commandName) {
case "animateToRegion":
mViewManager.animateToRegion(view, args.getString(0), args.getInt(1));
break;
case "setCamera":
mViewManager.setCamera(view, args.getString(0));
break;
case "animateCamera":
mViewManager.animateCamera(view, args.getString(0), args.getInt(1));
break;
case "fitToElements":
mViewManager.fitToElements(view, args.getString(0), args.getBoolean(1));
break;
case "fitToSuppliedMarkers":
mViewManager.fitToSuppliedMarkers(view, args.getString(0), args.getString(1), args.getBoolean(2));
break;
case "fitToCoordinates":
mViewManager.fitToCoordinates(view, args.getString(0), args.getString(1), args.getBoolean(2));
break;
case "setIndoorActiveLevelIndex":
mViewManager.setIndoorActiveLevelIndex(view, args.getInt(0));
break;
}
}
}

View File

@@ -0,0 +1,78 @@
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* @generated by codegen project: GeneratePropsJavaInterface.js
*/
package com.facebook.react.viewmanagers;
import android.view.View;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.uimanager.ViewManagerWithGeneratedInterface;
public interface RNMapsMapViewManagerInterface<T extends View> extends ViewManagerWithGeneratedInterface {
void setCacheEnabled(T view, boolean value);
void setCamera(T view, @Nullable ReadableMap value);
void setCompassOffset(T view, @Nullable ReadableMap value);
void setFollowsUserLocation(T view, boolean value);
void setPoiClickEnabled(T view, boolean value);
void setInitialCamera(T view, @Nullable ReadableMap value);
void setInitialRegion(T view, @Nullable ReadableMap value);
void setKmlSrc(T view, @Nullable String value);
void setLegalLabelInsets(T view, @Nullable ReadableMap value);
void setLiteMode(T view, boolean value);
void setGoogleMapId(T view, @Nullable String value);
void setGoogleRenderer(T view, @Nullable String value);
void setLoadingBackgroundColor(T view, @Nullable Integer value);
void setLoadingEnabled(T view, boolean value);
void setLoadingIndicatorColor(T view, @Nullable Integer value);
void setMapPadding(T view, @Nullable ReadableMap value);
void setMapType(T view, @Nullable String value);
void setMaxDelta(T view, double value);
void setMaxZoom(T view, float value);
void setMinDelta(T view, double value);
void setMinZoom(T view, float value);
void setMoveOnMarkerPress(T view, boolean value);
void setHandlePanDrag(T view, boolean value);
void setPaddingAdjustmentBehavior(T view, @Nullable String value);
void setPitchEnabled(T view, boolean value);
void setRegion(T view, @Nullable ReadableMap value);
void setRotateEnabled(T view, boolean value);
void setScrollDuringRotateOrZoomEnabled(T view, boolean value);
void setScrollEnabled(T view, boolean value);
void setShowsBuildings(T view, boolean value);
void setShowsCompass(T view, boolean value);
void setShowsIndoorLevelPicker(T view, boolean value);
void setShowsIndoors(T view, boolean value);
void setShowsPointsOfInterests(T view, boolean value);
void setPointsOfInterestFilter(T view, @Nullable ReadableArray value);
void setShowsMyLocationButton(T view, boolean value);
void setShowsScale(T view, boolean value);
void setShowsUserLocation(T view, boolean value);
void setTintColor(T view, @Nullable Integer value);
void setToolbarEnabled(T view, boolean value);
void setUserInterfaceStyle(T view, @Nullable String value);
void setCustomMapStyleString(T view, @Nullable String value);
void setUserLocationAnnotationTitle(T view, @Nullable String value);
void setUserLocationCalloutEnabled(T view, boolean value);
void setUserLocationFastestInterval(T view, int value);
void setUserLocationPriority(T view, @Nullable String value);
void setUserLocationUpdateInterval(T view, int value);
void setZoomControlEnabled(T view, boolean value);
void setZoomEnabled(T view, boolean value);
void setShowsTraffic(T view, boolean value);
void setZoomTapEnabled(T view, boolean value);
void setCameraZoomRange(T view, @Nullable ReadableMap value);
void animateToRegion(T view, String regionJSON, int duration);
void setCamera(T view, String cameraJSON);
void animateCamera(T view, String cameraJSON, int duration);
void fitToElements(T view, String edgePaddingJSON, boolean animated);
void fitToSuppliedMarkers(T view, String markersJSON, String edgePaddingJSON, boolean animated);
void fitToCoordinates(T view, String coordinatesJSON, String edgePaddingJSON, boolean animated);
void setIndoorActiveLevelIndex(T view, int activeLevelIndex);
}

View File

@@ -0,0 +1,110 @@
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* @generated by codegen project: GeneratePropsJavaDelegate.js
*/
package com.facebook.react.viewmanagers;
import android.view.View;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ColorPropConverter;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.uimanager.BaseViewManager;
import com.facebook.react.uimanager.BaseViewManagerDelegate;
import com.facebook.react.uimanager.LayoutShadowNode;
public class RNMapsMarkerManagerDelegate<T extends View, U extends BaseViewManager<T, ? extends LayoutShadowNode> & RNMapsMarkerManagerInterface<T>> extends BaseViewManagerDelegate<T, U> {
public RNMapsMarkerManagerDelegate(U viewManager) {
super(viewManager);
}
@Override
public void setProperty(T view, String propName, @Nullable Object value) {
switch (propName) {
case "anchor":
mViewManager.setAnchor(view, (ReadableMap) value);
break;
case "calloutAnchor":
mViewManager.setCalloutAnchor(view, (ReadableMap) value);
break;
case "image":
mViewManager.setImage(view, (ReadableMap) value);
break;
case "calloutOffset":
mViewManager.setCalloutOffset(view, (ReadableMap) value);
break;
case "displayPriority":
mViewManager.setDisplayPriority(view, (String) value);
break;
case "centerOffset":
mViewManager.setCenterOffset(view, (ReadableMap) value);
break;
case "coordinate":
mViewManager.setCoordinate(view, (ReadableMap) value);
break;
case "description":
mViewManager.setDescription(view, value == null ? null : (String) value);
break;
case "draggable":
mViewManager.setDraggable(view, value == null ? false : (boolean) value);
break;
case "title":
mViewManager.setTitle(view, value == null ? null : (String) value);
break;
case "tracksViewChanges":
mViewManager.setTracksViewChanges(view, value == null ? true : (boolean) value);
break;
case "identifier":
mViewManager.setIdentifier(view, value == null ? null : (String) value);
break;
case "isPreselected":
mViewManager.setIsPreselected(view, value == null ? false : (boolean) value);
break;
case "opacity":
mViewManager.setOpacity(view, value == null ? 1f : ((Double) value).doubleValue());
break;
case "pinColor":
mViewManager.setPinColor(view, ColorPropConverter.getColor(value, view.getContext()));
break;
case "titleVisibility":
mViewManager.setTitleVisibility(view, (String) value);
break;
case "subtitleVisibility":
mViewManager.setSubtitleVisibility(view, (String) value);
break;
case "useLegacyPinView":
mViewManager.setUseLegacyPinView(view, value == null ? false : (boolean) value);
break;
default:
super.setProperty(view, propName, value);
}
}
@Override
public void receiveCommand(T view, String commandName, ReadableArray args) {
switch (commandName) {
case "animateToCoordinates":
mViewManager.animateToCoordinates(view, args.getDouble(0), args.getDouble(1), args.getInt(2));
break;
case "setCoordinates":
mViewManager.setCoordinates(view, args.getDouble(0), args.getDouble(1));
break;
case "showCallout":
mViewManager.showCallout(view);
break;
case "hideCallout":
mViewManager.hideCallout(view);
break;
case "redrawCallout":
mViewManager.redrawCallout(view);
break;
case "redraw":
mViewManager.redraw(view);
break;
}
}
}

View File

@@ -0,0 +1,42 @@
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* @generated by codegen project: GeneratePropsJavaInterface.js
*/
package com.facebook.react.viewmanagers;
import android.view.View;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.uimanager.ViewManagerWithGeneratedInterface;
public interface RNMapsMarkerManagerInterface<T extends View> extends ViewManagerWithGeneratedInterface {
void setAnchor(T view, @Nullable ReadableMap value);
void setCalloutAnchor(T view, @Nullable ReadableMap value);
void setImage(T view, @Nullable ReadableMap value);
void setCalloutOffset(T view, @Nullable ReadableMap value);
void setDisplayPriority(T view, @Nullable String value);
void setCenterOffset(T view, @Nullable ReadableMap value);
void setCoordinate(T view, @Nullable ReadableMap value);
void setDescription(T view, @Nullable String value);
void setDraggable(T view, boolean value);
void setTitle(T view, @Nullable String value);
void setTracksViewChanges(T view, boolean value);
void setIdentifier(T view, @Nullable String value);
void setIsPreselected(T view, boolean value);
void setOpacity(T view, double value);
void setPinColor(T view, @Nullable Integer value);
void setTitleVisibility(T view, @Nullable String value);
void setSubtitleVisibility(T view, @Nullable String value);
void setUseLegacyPinView(T view, boolean value);
void animateToCoordinates(T view, double latitude, double longitude, int duration);
void setCoordinates(T view, double latitude, double longitude);
void showCallout(T view);
void hideCallout(T view);
void redrawCallout(T view);
void redraw(T view);
}

View File

@@ -0,0 +1,45 @@
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* @generated by codegen project: GeneratePropsJavaDelegate.js
*/
package com.facebook.react.viewmanagers;
import android.view.View;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.uimanager.BaseViewManager;
import com.facebook.react.uimanager.BaseViewManagerDelegate;
import com.facebook.react.uimanager.LayoutShadowNode;
public class RNMapsOverlayManagerDelegate<T extends View, U extends BaseViewManager<T, ? extends LayoutShadowNode> & RNMapsOverlayManagerInterface<T>> extends BaseViewManagerDelegate<T, U> {
public RNMapsOverlayManagerDelegate(U viewManager) {
super(viewManager);
}
@Override
public void setProperty(T view, String propName, @Nullable Object value) {
switch (propName) {
case "bearing":
mViewManager.setBearing(view, value == null ? 0f : ((Double) value).floatValue());
break;
case "bounds":
mViewManager.setBounds(view, (ReadableMap) value);
break;
case "image":
mViewManager.setImage(view, (ReadableMap) value);
break;
case "opacity":
mViewManager.setOpacity(view, value == null ? 1f : ((Double) value).floatValue());
break;
case "tappable":
mViewManager.setTappable(view, value == null ? false : (boolean) value);
break;
default:
super.setProperty(view, propName, value);
}
}
}

View File

@@ -0,0 +1,23 @@
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* @generated by codegen project: GeneratePropsJavaInterface.js
*/
package com.facebook.react.viewmanagers;
import android.view.View;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.uimanager.ViewManagerWithGeneratedInterface;
public interface RNMapsOverlayManagerInterface<T extends View> extends ViewManagerWithGeneratedInterface {
void setBearing(T view, float value);
void setBounds(T view, @Nullable ReadableMap value);
void setImage(T view, @Nullable ReadableMap value);
void setOpacity(T view, float value);
void setTappable(T view, boolean value);
}

View File

@@ -0,0 +1,58 @@
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* @generated by codegen project: GeneratePropsJavaDelegate.js
*/
package com.facebook.react.viewmanagers;
import android.view.View;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ColorPropConverter;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.uimanager.BaseViewManager;
import com.facebook.react.uimanager.BaseViewManagerDelegate;
import com.facebook.react.uimanager.LayoutShadowNode;
public class RNMapsPolylineManagerDelegate<T extends View, U extends BaseViewManager<T, ? extends LayoutShadowNode> & RNMapsPolylineManagerInterface<T>> extends BaseViewManagerDelegate<T, U> {
public RNMapsPolylineManagerDelegate(U viewManager) {
super(viewManager);
}
@Override
public void setProperty(T view, String propName, @Nullable Object value) {
switch (propName) {
case "coordinates":
mViewManager.setCoordinates(view, (ReadableArray) value);
break;
case "geodesic":
mViewManager.setGeodesic(view, value == null ? false : (boolean) value);
break;
case "lineCap":
mViewManager.setLineCap(view, (String) value);
break;
case "lineDashPattern":
mViewManager.setLineDashPattern(view, (ReadableArray) value);
break;
case "lineJoin":
mViewManager.setLineJoin(view, (String) value);
break;
case "strokeColor":
mViewManager.setStrokeColor(view, ColorPropConverter.getColor(value, view.getContext()));
break;
case "strokeColors":
mViewManager.setStrokeColors(view, (ReadableArray) value);
break;
case "strokeWidth":
mViewManager.setStrokeWidth(view, value == null ? 1f : ((Double) value).floatValue());
break;
case "tappable":
mViewManager.setTappable(view, value == null ? false : (boolean) value);
break;
default:
super.setProperty(view, propName, value);
}
}
}

View File

@@ -0,0 +1,27 @@
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* @generated by codegen project: GeneratePropsJavaInterface.js
*/
package com.facebook.react.viewmanagers;
import android.view.View;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.uimanager.ViewManagerWithGeneratedInterface;
public interface RNMapsPolylineManagerInterface<T extends View> extends ViewManagerWithGeneratedInterface {
void setCoordinates(T view, @Nullable ReadableArray value);
void setGeodesic(T view, boolean value);
void setLineCap(T view, @Nullable String value);
void setLineDashPattern(T view, @Nullable ReadableArray value);
void setLineJoin(T view, @Nullable String value);
void setStrokeColor(T view, @Nullable Integer value);
void setStrokeColors(T view, @Nullable ReadableArray value);
void setStrokeWidth(T view, float value);
void setTappable(T view, boolean value);
}

View File

@@ -0,0 +1,62 @@
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* @generated by codegen project: GeneratePropsJavaDelegate.js
*/
package com.facebook.react.viewmanagers;
import android.view.View;
import androidx.annotation.Nullable;
import com.facebook.react.uimanager.BaseViewManager;
import com.facebook.react.uimanager.BaseViewManagerDelegate;
import com.facebook.react.uimanager.LayoutShadowNode;
public class RNMapsUrlTileManagerDelegate<T extends View, U extends BaseViewManager<T, ? extends LayoutShadowNode> & RNMapsUrlTileManagerInterface<T>> extends BaseViewManagerDelegate<T, U> {
public RNMapsUrlTileManagerDelegate(U viewManager) {
super(viewManager);
}
@Override
public void setProperty(T view, String propName, @Nullable Object value) {
switch (propName) {
case "doubleTileSize":
mViewManager.setDoubleTileSize(view, value == null ? false : (boolean) value);
break;
case "flipY":
mViewManager.setFlipY(view, value == null ? false : (boolean) value);
break;
case "maximumNativeZ":
mViewManager.setMaximumNativeZ(view, value == null ? 100 : ((Double) value).intValue());
break;
case "maximumZ":
mViewManager.setMaximumZ(view, value == null ? 100 : ((Double) value).intValue());
break;
case "minimumZ":
mViewManager.setMinimumZ(view, value == null ? 0 : ((Double) value).intValue());
break;
case "offlineMode":
mViewManager.setOfflineMode(view, value == null ? false : (boolean) value);
break;
case "shouldReplaceMapContent":
mViewManager.setShouldReplaceMapContent(view, value == null ? false : (boolean) value);
break;
case "tileCacheMaxAge":
mViewManager.setTileCacheMaxAge(view, value == null ? 0 : ((Double) value).intValue());
break;
case "tileCachePath":
mViewManager.setTileCachePath(view, value == null ? null : (String) value);
break;
case "tileSize":
mViewManager.setTileSize(view, value == null ? 256 : ((Double) value).intValue());
break;
case "urlTemplate":
mViewManager.setUrlTemplate(view, value == null ? null : (String) value);
break;
default:
super.setProperty(view, propName, value);
}
}
}

View File

@@ -0,0 +1,28 @@
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* @generated by codegen project: GeneratePropsJavaInterface.js
*/
package com.facebook.react.viewmanagers;
import android.view.View;
import androidx.annotation.Nullable;
import com.facebook.react.uimanager.ViewManagerWithGeneratedInterface;
public interface RNMapsUrlTileManagerInterface<T extends View> extends ViewManagerWithGeneratedInterface {
void setDoubleTileSize(T view, boolean value);
void setFlipY(T view, boolean value);
void setMaximumNativeZ(T view, int value);
void setMaximumZ(T view, int value);
void setMinimumZ(T view, int value);
void setOfflineMode(T view, boolean value);
void setShouldReplaceMapContent(T view, boolean value);
void setTileCacheMaxAge(T view, int value);
void setTileCachePath(T view, @Nullable String value);
void setTileSize(T view, int value);
void setUrlTemplate(T view, @Nullable String value);
}

View File

@@ -0,0 +1,56 @@
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* @generated by codegen project: GeneratePropsJavaDelegate.js
*/
package com.facebook.react.viewmanagers;
import android.view.View;
import androidx.annotation.Nullable;
import com.facebook.react.uimanager.BaseViewManager;
import com.facebook.react.uimanager.BaseViewManagerDelegate;
import com.facebook.react.uimanager.LayoutShadowNode;
public class RNMapsWMSTileManagerDelegate<T extends View, U extends BaseViewManager<T, ? extends LayoutShadowNode> & RNMapsWMSTileManagerInterface<T>> extends BaseViewManagerDelegate<T, U> {
public RNMapsWMSTileManagerDelegate(U viewManager) {
super(viewManager);
}
@Override
public void setProperty(T view, String propName, @Nullable Object value) {
switch (propName) {
case "maximumNativeZ":
mViewManager.setMaximumNativeZ(view, value == null ? 100 : ((Double) value).intValue());
break;
case "maximumZ":
mViewManager.setMaximumZ(view, value == null ? 100 : ((Double) value).intValue());
break;
case "minimumZ":
mViewManager.setMinimumZ(view, value == null ? 0 : ((Double) value).intValue());
break;
case "offlineMode":
mViewManager.setOfflineMode(view, value == null ? false : (boolean) value);
break;
case "shouldReplaceMapContent":
mViewManager.setShouldReplaceMapContent(view, value == null ? false : (boolean) value);
break;
case "tileCacheMaxAge":
mViewManager.setTileCacheMaxAge(view, value == null ? 0 : ((Double) value).intValue());
break;
case "tileCachePath":
mViewManager.setTileCachePath(view, value == null ? null : (String) value);
break;
case "tileSize":
mViewManager.setTileSize(view, value == null ? 256 : ((Double) value).intValue());
break;
case "urlTemplate":
mViewManager.setUrlTemplate(view, value == null ? null : (String) value);
break;
default:
super.setProperty(view, propName, value);
}
}
}

View File

@@ -0,0 +1,26 @@
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* @generated by codegen project: GeneratePropsJavaInterface.js
*/
package com.facebook.react.viewmanagers;
import android.view.View;
import androidx.annotation.Nullable;
import com.facebook.react.uimanager.ViewManagerWithGeneratedInterface;
public interface RNMapsWMSTileManagerInterface<T extends View> extends ViewManagerWithGeneratedInterface {
void setMaximumNativeZ(T view, int value);
void setMaximumZ(T view, int value);
void setMinimumZ(T view, int value);
void setOfflineMode(T view, boolean value);
void setShouldReplaceMapContent(T view, boolean value);
void setTileCacheMaxAge(T view, int value);
void setTileCachePath(T view, @Nullable String value);
void setTileSize(T view, int value);
void setUrlTemplate(T view, @Nullable String value);
}

View File

@@ -0,0 +1,77 @@
package com.rnmaps.fabric;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.uimanager.LayoutShadowNode;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.ViewManagerDelegate;
import com.facebook.react.viewmanagers.RNMapsCalloutManagerDelegate;
import com.facebook.react.viewmanagers.RNMapsCalloutManagerInterface;
import com.rnmaps.maps.MapCallout;
import com.rnmaps.maps.SizeReportingShadowNode;
import java.util.HashMap;
import java.util.Map;
@ReactModule(name = CalloutManager.REACT_CLASS)
public class CalloutManager extends ViewGroupManager<MapCallout> implements RNMapsCalloutManagerInterface<MapCallout> {
public CalloutManager(ReactApplicationContext context){
super(context);
}
private final RNMapsCalloutManagerDelegate<MapCallout, CalloutManager> delegate =
new RNMapsCalloutManagerDelegate<>(this);
@Override
public ViewManagerDelegate<MapCallout> getDelegate() {
return delegate;
}
@Override
public String getName() {
return REACT_CLASS;
}
@Override
public MapCallout createViewInstance(ThemedReactContext context) {
return new MapCallout(context);
}
public static final String REACT_CLASS = "RNMapsCallout";
@Nullable
@Override
public Map<String, Object> getExportedCustomBubblingEventTypeConstants() {
return MapCallout.getExportedCustomBubblingEventTypeConstants();
}
@Nullable
@Override
public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
return new HashMap<>();
}
@Override
public void setAlphaHitTest(MapCallout view, boolean value) {
/// do nothing
}
@Override
public void setTooltip(MapCallout view, boolean value) {
view.setTooltip(value);
}
@Override
public LayoutShadowNode createShadowNodeInstance() {
// we use a custom shadow node that emits the width/height of the view
// after layout with the updateExtraData method. Without this, we can't generate
// a bitmap of the appropriate width/height of the rendered view.
return new SizeReportingShadowNode();
}
}

View File

@@ -0,0 +1,94 @@
package com.rnmaps.fabric;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.ViewManagerDelegate;
import com.facebook.react.viewmanagers.RNMapsCircleManagerDelegate;
import com.facebook.react.viewmanagers.RNMapsCircleManagerInterface;
import com.rnmaps.maps.MapCircle;
import java.util.HashMap;
import java.util.Map;
@ReactModule(name = CircleManager.REACT_CLASS)
public class CircleManager extends ViewGroupManager<MapCircle> implements RNMapsCircleManagerInterface<MapCircle> {
public CircleManager(ReactApplicationContext context) {
super(context);
}
private final RNMapsCircleManagerDelegate<MapCircle, CircleManager> delegate =
new RNMapsCircleManagerDelegate<>(this);
@Override
public ViewManagerDelegate<MapCircle> getDelegate() {
return delegate;
}
@Override
public String getName() {
return REACT_CLASS;
}
@Override
public MapCircle createViewInstance(ThemedReactContext context) {
return new MapCircle(context);
}
public static final String REACT_CLASS = "RNMapsCircle";
@Nullable
@Override
public Map<String, Object> getExportedCustomBubblingEventTypeConstants() {
return MapCircle.getExportedCustomBubblingEventTypeConstants();
}
@Nullable
@Override
public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
return new HashMap<>();
}
@Override
public void setZIndex(MapCircle view, float value) {
view.setZIndex(value);
}
@Override
public void setCenter(MapCircle view, @Nullable ReadableMap value) {
view.setCenter(value);
}
@Override
public void setFillColor(MapCircle view, @Nullable Integer value) {
view.setFillColor(value);
}
@Override
public void setRadius(MapCircle view, double value) {
view.setRadius(value);
}
@Override
public void setStrokeColor(MapCircle view, @Nullable Integer value) {
view.setStrokeColor(value);
}
@Override
public void setStrokeWidth(MapCircle view, float value) {
view.setStrokeWidth(value);
}
@Override
public void setTappable(MapCircle view, boolean value) {
view.setTappable(value);
}
}

View File

@@ -0,0 +1,86 @@
package com.rnmaps.fabric;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.Iterator;
public class JSONUtil {
public static WritableMap convertJsonToWritable(JSONObject jsonObject) throws JSONException {
WritableMap map = Arguments.createMap();
Iterator<String> iterator = jsonObject.keys();
while (iterator.hasNext()) {
String key = iterator.next();
Object value = jsonObject.get(key);
if (value == JSONObject.NULL) {
map.putNull(key);
}
// Check primitive types first
else if (value instanceof Integer) {
map.putInt(key, jsonObject.getInt(key));
}
else if (value instanceof Long) {
map.putDouble(key, ((Long) value).doubleValue());
}
else if (value instanceof Double) {
map.putDouble(key, jsonObject.getDouble(key));
}
else if (value instanceof Boolean) {
map.putBoolean(key, jsonObject.getBoolean(key));
}
else if (value instanceof String) {
map.putString(key, jsonObject.getString(key));
}
// Check complex types
else if (value instanceof JSONObject) {
map.putMap(key, convertJsonToWritable(jsonObject.getJSONObject(key)));
}
else if (value instanceof JSONArray) {
map.putArray(key, convertJsonArrayToWritable(jsonObject.getJSONArray(key)));
}
}
return map;
}
public static WritableArray convertJsonArrayToWritable(JSONArray jsonArray) throws JSONException {
WritableArray array = Arguments.createArray();
for (int i = 0; i < jsonArray.length(); i++) {
Object value = jsonArray.get(i);
if (value == JSONObject.NULL) {
array.pushNull();
}
else if (value instanceof Integer) {
array.pushInt((Integer) value);
}
else if (value instanceof Long) {
array.pushDouble(((Long) value).doubleValue());
}
else if (value instanceof Double) {
array.pushDouble((Double) value);
}
else if (value instanceof Boolean) {
array.pushBoolean((Boolean) value);
}
else if (value instanceof String) {
array.pushString((String) value);
}
else if (value instanceof JSONObject) {
array.pushMap(convertJsonToWritable((JSONObject) value));
}
else if (value instanceof JSONArray) {
array.pushArray(convertJsonArrayToWritable((JSONArray) value));
}
}
return array;
}
}

View File

@@ -0,0 +1,642 @@
package com.rnmaps.fabric;
import static com.rnmaps.maps.MapManager.MY_LOCATION_PRIORITY;
import android.util.Log;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.uimanager.LayoutShadowNode;
import com.facebook.react.uimanager.ReactStylesDiffMap;
import com.facebook.react.uimanager.StateWrapper;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.ViewManagerDelegate;
import com.facebook.react.viewmanagers.RNMapsMapViewManagerInterface;
import com.facebook.react.viewmanagers.RNMapsMapViewManagerDelegate;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.GoogleMapOptions;
import com.google.android.gms.maps.MapsInitializer;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
import com.google.android.gms.maps.model.MapColorScheme;
import com.rnmaps.maps.MapMarker;
import com.rnmaps.maps.MapView;
import com.rnmaps.maps.SizeReportingShadowNode;
import java.util.Map;
@ReactModule(name = MapViewManager.REACT_CLASS)
public class MapViewManager extends ViewGroupManager<MapView> implements RNMapsMapViewManagerInterface<MapView> {
private static boolean rendererInitialized = false;
private final RNMapsMapViewManagerDelegate<MapView, MapViewManager> delegate =
new RNMapsMapViewManagerDelegate<>(this);
public MapViewManager(ReactApplicationContext context) {
super(context);
}
@Override
public ViewManagerDelegate<MapView> getDelegate() {
return delegate;
}
@Override
public String getName() {
return REACT_CLASS;
}
@Override
public MapView createViewInstance(ThemedReactContext context) {
return new MapView(context, new GoogleMapOptions());
}
@Override
public int getChildCount(MapView view) {
return view.getFeatureCount();
}
@Override
public View getChildAt(MapView view, int index) {
return view.getFeatureAt(index);
}
@Override
public void removeViewAt(MapView parent, int index) {
parent.removeFeatureAt(index);
}
@Override
protected void setupViewRecycling() {
// override parent to block recycling / allow reliable GoogleMapsOptions passing
}
private GoogleMapOptions optionsForInitialProps(ReactStylesDiffMap initialProps){
GoogleMapOptions options = new GoogleMapOptions();
if (initialProps != null) {
setGoogleRenderer(null, initialProps.getString("googleRenderer"));
if (initialProps.hasKey("liteMode")) {
options.liteMode(initialProps.getBoolean("liteMode", false));
}
if (initialProps.hasKey("googleMapId")) {
String googleMapId = initialProps.getString("googleMapId");
options.mapId(googleMapId);
}
if (initialProps.getMap("initialCamera") != null) {
ReadableMap initialCamera = initialProps.getMap("initialCamera");
CameraPosition camera = MapView.cameraPositionFromMap(initialCamera);
if (camera != null) {
options.camera(camera);
}
}
if (initialProps.hasKey("mapType")) {
if (initialProps.getString("mapType") != null) {
options.mapType(mapTypeFromStrValue(initialProps.getString("mapType")));
}
}
if (initialProps.hasKey("minZoom")) {
if (initialProps.getInt("minZoom", 0) != 0) {
options.minZoomPreference(initialProps.getInt("minZoom", 0));
}
}
if (initialProps.hasKey("maxZoom")) {
if (initialProps.getInt("maxZoom", 0) != 0) {
options.maxZoomPreference(initialProps.getInt("maxZoom", 0));
}
}
if (initialProps.hasKey("userInterfaceStyle") && !initialProps.hasKey("liteMode")){
String style = initialProps.getString("userInterfaceStyle");
if ("system".equals(style)) {
options.mapColorScheme(MapColorScheme.FOLLOW_SYSTEM);
} else if ("light".equals(style)){
options.mapColorScheme(MapColorScheme.LIGHT);
} else if ("dark".equals(style)){
options.mapColorScheme(MapColorScheme.DARK);
}
}
if (initialProps.hasKey("pitchEnabled")) {
options.tiltGesturesEnabled(initialProps.getBoolean("pitchEnabled", true));
}
if (initialProps.hasKey("rotateEnabled")) {
options.rotateGesturesEnabled(initialProps.getBoolean("rotateEnabled", true));
}
if (initialProps.hasKey("scrollDuringRotateOrZoomEnabled")) {
options.scrollGesturesEnabledDuringRotateOrZoom(initialProps.getBoolean("scrollDuringRotateOrZoomEnabled", true));
}
if (initialProps.hasKey("scrollEnabled")) {
options.scrollGesturesEnabled(initialProps.getBoolean("scrollEnabled", true));
}
if (initialProps.hasKey("showsCompass")) {
options.compassEnabled(initialProps.getBoolean("showsCompass", true));
}
if (initialProps.hasKey("toolbarEnabled")) {
options.mapToolbarEnabled(initialProps.getBoolean("toolbarEnabled", true));
}
if (initialProps.hasKey("zoomControlEnabled")) {
options.zoomControlsEnabled(initialProps.getBoolean("zoomControlEnabled", true));
}
if (initialProps.hasKey("zoomEnabled")) {
options.zoomGesturesEnabled(initialProps.getBoolean("zoomEnabled", true));
}
}
return options;
}
@Override
protected MapView createViewInstance(int reactTag, @NonNull ThemedReactContext reactContext, @Nullable ReactStylesDiffMap initialProps, @Nullable StateWrapper stateWrapper) {
MapView view = null;
view = new MapView(reactContext, optionsForInitialProps(initialProps));
view.setId(reactTag);
this.addEventEmitters(reactContext, view);
if (initialProps != null) {
this.updateProperties(view, initialProps);
}
if (stateWrapper != null) {
Object extraData = this.updateState(view, initialProps, stateWrapper);
if (extraData != null) {
this.updateExtraData(view, extraData);
}
}
return view;
}
public static final String REACT_CLASS = "RNMapsMapView";
@Nullable
@Override
public Map<String, Object> getExportedCustomBubblingEventTypeConstants() {
return MapView.getExportedCustomBubblingEventTypeConstants();
}
@Override
public LayoutShadowNode createShadowNodeInstance() {
// A custom shadow node is needed in order to pass back the width/height of the map to the
// view manager so that it can start applying camera moves with bounds.
return new SizeReportingShadowNode();
}
@Nullable
@Override
public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
return MapView.getExportedCustomDirectEventTypeConstants();
}
@Override
public void setCacheEnabled(MapView view, boolean value) {
view.setCacheEnabled(value);
}
@Override
public void setCamera(MapView view, @Nullable ReadableMap value) {
view.setCamera(value);
}
@Override
public void setCompassOffset(MapView view, @Nullable ReadableMap value) {
// not supported
}
@Override
public void setFollowsUserLocation(MapView view, boolean value) {
// not supported
}
@Override
public void setPoiClickEnabled(MapView view, boolean value) {
view.setPoiClickEnabled(value);
}
@Override
public void setInitialCamera(MapView view, @Nullable ReadableMap value) {
CameraPosition camera = MapView.cameraPositionFromMap(value);
if (camera != null) {
view.setInitialCameraSet(true);
}
}
@Override
public void setInitialRegion(MapView view, @Nullable ReadableMap value) {
view.setInitialRegion(value);
}
@Override
public void setKmlSrc(MapView view, @Nullable String value) {
view.setKmlSrc(value);
}
@Override
public void setLegalLabelInsets(MapView view, @Nullable ReadableMap value) {
// not supported
}
@Override
public void setLiteMode(MapView view, boolean value) {
// do nothing (initialProp)
}
@Override
public void setGoogleMapId(MapView view, @Nullable String value) {
// do nothing (initialProp)
}
@Override
public void setGoogleRenderer(MapView view, @Nullable String value) {
if (!rendererInitialized) {
MapsInitializer.Renderer renderer = MapsInitializer.Renderer.LATEST;
if ("LEGACY".equals(value)) {
renderer = MapsInitializer.Renderer.LEGACY;
}
MapsInitializer.initialize(getReactApplicationContext(), renderer, r -> Log.d("AirMapRenderer", "Init with renderer: " + r));
rendererInitialized = true;
}
}
@Override
public void setLoadingBackgroundColor(MapView view, @Nullable Integer value) {
view.setLoadingBackgroundColor(value);
}
@Override
public void setLoadingEnabled(MapView view, boolean value) {
view.setLoadingEnabled(value);
}
@Override
public void setLoadingIndicatorColor(MapView view, @Nullable Integer value) {
view.setLoadingIndicatorColor(value);
}
@Override
public void setMapPadding(MapView view, @Nullable ReadableMap padding) {
int left = 0;
int top = 0;
int right = 0;
int bottom = 0;
double density = (double) view.getResources().getDisplayMetrics().density;
if (padding != null) {
if (padding.hasKey("left")) {
left = (int) (padding.getDouble("left") * density);
}
if (padding.hasKey("top")) {
top = (int) (padding.getDouble("top") * density);
}
if (padding.hasKey("right")) {
right = (int) (padding.getDouble("right") * density);
}
if (padding.hasKey("bottom")) {
bottom = (int) (padding.getDouble("bottom") * density);
}
}
view.applyBaseMapPadding(left, top, right, bottom);
}
@Override
public void addView(MapView parent, View child, int index) {
parent.addFeature(child, index);
if (child instanceof MapMarker && ((MapMarker) child).isLoadingImage()){
MapMarker markerView = (MapMarker) child;
// Marker is already added as invisible, restore visibility when image loads
markerView.setImageLoadedListener((uri, drawable, b) -> {
com.google.android.gms.maps.model.Marker googleMarker =
(com.google.android.gms.maps.model.Marker) markerView.getFeature();
if (googleMarker != null) {
// Restore original visibility (marker was hidden temporarily during image load)
if (View.VISIBLE == markerView.getVisibility()) {
googleMarker.setVisible(true);
}
}
markerView.update(true);
});
}
}
@Override
public void setMapType(MapView view, @Nullable String value) {
view.setMapType(mapTypeFromStrValue(value));
}
private static int mapTypeFromStrValue(String value) {
int mapType;
//hybrid | none | satellite | standard | terrain
if ("hybrid".equals(value)) {
mapType = GoogleMap.MAP_TYPE_HYBRID;
} else if ("none".equals(value)) {
mapType = GoogleMap.MAP_TYPE_NONE;
} else if ("satellite".equals(value)) {
mapType = GoogleMap.MAP_TYPE_SATELLITE;
} else if ("standard".equals(value)) {
mapType = GoogleMap.MAP_TYPE_NORMAL;
} else if ("terrain".equals(value)) {
mapType = GoogleMap.MAP_TYPE_TERRAIN;
} else {
mapType = GoogleMap.MAP_TYPE_NORMAL;
}
return mapType;
}
@Override
public void setMaxDelta(MapView view, double value) {
// not supported
}
@Override
public void setMaxZoom(MapView view, float value) {
view.setMaxZoomLevel(value);
}
@Override
public void setMinDelta(MapView view, double value) {
// not supported
}
@Override
public void setMinZoom(MapView view, float value) {
view.setMinZoomLevel(value);
}
@Override
public void setMoveOnMarkerPress(MapView view, boolean value) {
view.setMoveOnMarkerPress(value);
}
@Override
public void setHandlePanDrag(MapView view, boolean value) {
view.setHandlePanDrag(value);
}
@Override
public void setPaddingAdjustmentBehavior(MapView view, @Nullable String value) {
// not supported
}
@Override
public void setPitchEnabled(MapView view, boolean value) {
view.setPitchEnabled(value);
}
@Override
public void setRegion(MapView view, @Nullable ReadableMap value) {
view.setRegion(value);
}
@Override
public void setRotateEnabled(MapView view, boolean value) {
view.setRotateEnabled(value);
}
@Override
public void setScrollDuringRotateOrZoomEnabled(MapView view, boolean value) {
view.setScrollDuringRotateOrZoomEnabled(value);
}
@Override
public void setScrollEnabled(MapView view, boolean value) {
view.setScrollEnabled(value);
}
@Override
public void setShowsBuildings(MapView view, boolean value) {
view.setShowBuildings(value);
}
@Override
public void setShowsCompass(MapView view, boolean value) {
view.setShowsCompass(value);
}
@Override
public void setShowsIndoorLevelPicker(MapView view, boolean value) {
view.setShowsIndoorLevelPicker(value);
}
@Override
public void setShowsIndoors(MapView view, boolean value) {
view.setShowIndoors(value);
}
@Override
public void setShowsPointsOfInterests(MapView view, boolean value) {
// not supported
}
@Override
public void setPointsOfInterestFilter(MapView view, @Nullable ReadableArray value) {
// not supported
}
@Override
public void setShowsMyLocationButton(MapView view, boolean value) {
view.setShowsMyLocationButton(value);
}
@Override
public void setShowsScale(MapView view, boolean value) {
// not supported
}
@Override
public void setShowsUserLocation(MapView view, boolean value) {
view.setShowsUserLocation(value);
}
@Override
public void setTintColor(MapView view, @Nullable Integer value) {
// not supported
}
@Override
public void setToolbarEnabled(MapView view, boolean value) {
view.setToolbarEnabled(value);
}
@Override
public void setUserInterfaceStyle(MapView view, @Nullable String value) {
// do nothing (initialProp)
}
@Override
public void setCustomMapStyleString(MapView view, @Nullable String value) {
view.setMapStyle(value);
}
@Override
public void setUserLocationAnnotationTitle(MapView view, @Nullable String value) {
// not supported
}
@Override
public void setUserLocationCalloutEnabled(MapView view, boolean value) {
// not supported
}
@Override
public void setUserLocationFastestInterval(MapView view, int value) {
view.setUserLocationFastestInterval(value);
}
@Override
public void setUserLocationPriority(MapView view, @Nullable String value) {
view.setUserLocationPriority(MY_LOCATION_PRIORITY.get(value));
}
@Override
public void setUserLocationUpdateInterval(MapView view, int value) {
view.setUserLocationUpdateInterval(value);
}
@Override
public void setZoomControlEnabled(MapView view, boolean value) {
view.setZoomControlEnabled(value);
}
@Override
public void setZoomEnabled(MapView view, boolean value) {
view.setZoomEnabled(value);
}
@Override
public void setShowsTraffic(MapView view, boolean value) {
view.setShowsTraffic(value);
}
@Override
public void setZoomTapEnabled(MapView view, boolean value) {
// not supported
}
@Override
public void setCameraZoomRange(MapView view, @Nullable ReadableMap value) {
// not supported
}
@Override
public void animateToRegion(MapView view, String regionJSON, int duration) {
try {
JSONObject region = new JSONObject(regionJSON);
double lng = region.getDouble("longitude");
double lat = region.getDouble("latitude");
double lngDelta = region.getDouble("longitudeDelta");
double latDelta = region.getDouble("latitudeDelta");
LatLngBounds bounds = new LatLngBounds(
new LatLng(lat - latDelta / 2, lng - lngDelta / 2), // southwest
new LatLng(lat + latDelta / 2, lng + lngDelta / 2) // northeast
);
view.animateToRegion(bounds, duration);
} catch (JSONException e) {
throw new RuntimeException(e);
}
}
@Override
public void setCamera(MapView view, String cameraJSON) {
try {
JSONObject camera = new JSONObject(cameraJSON);
CameraPosition position = view.cameraPositionFromJSON(camera);
view.moveToCamera(position);
} catch (JSONException e) {
Log.e("MapViewManager", "parse camera exception " + e);
}
}
@Override
public void animateCamera(MapView view, String cameraJSON, int duration) {
try {
JSONObject camera = new JSONObject(cameraJSON);
CameraPosition position = view.cameraPositionFromJSON(camera);
view.animateToCamera(position, duration);
} catch (JSONException e) {
Log.e("MapViewManager", "parse camera exception " + e);
}
}
@Override
public void fitToElements(MapView view, String edgePaddingJSON, boolean animated) {
try {
WritableMap map = null;
if (edgePaddingJSON != null) {
JSONObject jsonObject = new JSONObject(edgePaddingJSON);
map = JSONUtil.convertJsonToWritable(jsonObject);
}
view.fitToElements(map, animated);
} catch (JSONException e){
Log.e("MapViewManager", "parse edgePaddingJSON exception " + e);
}
}
@Override
public void fitToSuppliedMarkers(MapView view, String markersJSON, String edgePaddingJSON, boolean animated) {
try {
WritableArray markers = null;
if (markersJSON != null) {
JSONArray array = new JSONArray(markersJSON);
markers = JSONUtil.convertJsonArrayToWritable(array);
}
WritableMap edgePadding = null;
if (edgePaddingJSON != null) {
JSONObject jsonObject = new JSONObject(edgePaddingJSON);
edgePadding = JSONUtil.convertJsonToWritable(jsonObject);
}
view.fitToSuppliedMarkers(markers, edgePadding, animated);
} catch (JSONException e) {
throw new RuntimeException(e);
}
}
@Override
public void fitToCoordinates(MapView view, String coordinatesJSON, String edgePaddingJSON, boolean animated) {
try {
WritableArray coordinates = null;
if (coordinatesJSON != null) {
JSONArray array = new JSONArray(coordinatesJSON);
coordinates = JSONUtil.convertJsonArrayToWritable(array);
}
WritableMap edgePadding = null;
if (edgePaddingJSON != null) {
JSONObject jsonObject = new JSONObject(edgePaddingJSON);
edgePadding = JSONUtil.convertJsonToWritable(jsonObject);
}
view.fitToCoordinates(coordinates, edgePadding, animated);
} catch (JSONException e) {
throw new RuntimeException(e);
}
}
@Override
public void setIndoorActiveLevelIndex(MapView view, int activeLevelIndex) {
view.setIndoorActiveLevelIndex(activeLevelIndex);
}
@Override
public void onDropViewInstance(MapView view) {
view.doDestroy();
super.onDropViewInstance(view);
}
}

View File

@@ -0,0 +1,302 @@
package com.rnmaps.fabric;
import android.graphics.Color;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.uimanager.ReactStylesDiffMap;
import com.facebook.react.uimanager.StateWrapper;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.ViewManagerDelegate;
import com.facebook.react.viewmanagers.RNMapsMarkerManagerDelegate;
import com.facebook.react.viewmanagers.RNMapsMarkerManagerInterface;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;
import com.rnmaps.maps.MapCallout;
import com.rnmaps.maps.MapMarker;
import java.util.Map;
@ReactModule(name = MarkerManager.REACT_CLASS)
public class MarkerManager extends ViewGroupManager<MapMarker> implements RNMapsMarkerManagerInterface<MapMarker> {
public MarkerManager(ReactApplicationContext context){
super(context);
}
private final RNMapsMarkerManagerDelegate<MapMarker, MarkerManager> delegate =
new RNMapsMarkerManagerDelegate<>(this);
@Override
public ViewManagerDelegate<MapMarker> getDelegate() {
return delegate;
}
@Override
public String getName() {
return REACT_CLASS;
}
@Override
public MapMarker createViewInstance(ThemedReactContext context) {
return new MapMarker(context, null);
}
private MarkerOptions optionsForInitialProps(ReactStylesDiffMap initialProps){
MarkerOptions options = new MarkerOptions();
if (initialProps != null) {
if (initialProps.hasKey("opacity")) {
options.alpha(initialProps.getFloat("opacity", 1));
}
if (initialProps.hasKey("anchor")) {
ReadableMap map = initialProps.getMap("anchor");
float x = (float) (map != null && map.hasKey("x") ? map.getDouble("x") : 0.5);
float y = (float) (map != null && map.hasKey("y") ? map.getDouble("y") : 1.0);
options.anchor(x, y);
}
if (initialProps.hasKey("coordinate")) {
ReadableMap coordinate = initialProps.getMap("coordinate");
LatLng position = new LatLng(coordinate.getDouble("latitude"), coordinate.getDouble("longitude"));
options.position(position);
}
if (initialProps.hasKey("title")) {
options.title(initialProps.getString("title"));
}
if (initialProps.hasKey("description")) {
options.snippet(initialProps.getString("description"));
}
if (initialProps.hasKey("draggable")){
options.draggable(initialProps.getBoolean("draggable", false));
}
if (initialProps.hasKey("rotation")){
options.rotation(initialProps.getFloat("rotation", 0));
}
if (initialProps.hasKey("flat")) {
options.flat(initialProps.getBoolean("flat", false));
}
if (initialProps.hasKey("calloutAnchor")){
ReadableMap map = initialProps.getMap("calloutAnchor");
float x = (float) (map != null && map.hasKey("x") ? map.getDouble("x") : 0.5);
float y = (float) (map != null && map.hasKey("y") ? map.getDouble("y") : 1.0);
options.infoWindowAnchor(x, y);
}
if (initialProps.hasKey("zIndex")){
options.zIndex(initialProps.getFloat("zIndex", 0));
}
}
return options;
}
@Override
protected MapMarker createViewInstance(int reactTag, @NonNull ThemedReactContext reactContext, @Nullable ReactStylesDiffMap initialProps, @Nullable StateWrapper stateWrapper) {
MapMarker view = null;
view = new MapMarker(reactContext, optionsForInitialProps(initialProps), null);
view.setId(reactTag);
this.addEventEmitters(reactContext, view);
if (initialProps != null) {
this.updateProperties(view, initialProps);
}
if (stateWrapper != null) {
Object extraData = this.updateState(view, initialProps, stateWrapper);
if (extraData != null) {
this.updateExtraData(view, extraData);
}
}
return view;
}
public static final String REACT_CLASS = "RNMapsMarker";
@Nullable
@Override
public Map<String, Object> getExportedCustomBubblingEventTypeConstants() {
return MapMarker.getExportedCustomBubblingEventTypeConstants();
}
@Nullable
@Override
public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
return MapMarker.getExportedCustomDirectEventTypeConstants();
}
@Override
public void setAnchor(MapMarker view, @Nullable ReadableMap map) {
double x = map != null && map.hasKey("x") ? map.getDouble("x") : 0.5;
double y = map != null && map.hasKey("y") ? map.getDouble("y") : 1.0;
view.setAnchor(x, y);
view.setUpdated(true);
}
@Override
public void setCalloutAnchor(MapMarker view, @Nullable ReadableMap value) {
}
@Override
public void setImage(MapMarker view, @Nullable ReadableMap value) {
if (value != null && value.hasKey("uri")) {
view.setImage(value.getString("uri"));
} else {
view.setImage(null);
}
view.setUpdated(true);
}
@Override
public void onDropViewInstance(MapMarker view) {
super.onDropViewInstance(view);
view.doDestroy();
}
@Override
public void setCalloutOffset(MapMarker view, @Nullable ReadableMap value) {
}
@Override
public void setDisplayPriority(MapMarker view, @Nullable String value) {
}
@Override
public void setCenterOffset(MapMarker view, @Nullable ReadableMap value) {
}
@Override
public void setCoordinate(MapMarker view, @Nullable ReadableMap value) {
view.setCoordinate(value);
view.setUpdated(true);
}
@Override
public void setDescription(MapMarker view, @Nullable String value) {
view.setSnippet(value);
view.setUpdated(true);
}
@Override
public void setDraggable(MapMarker view, boolean value) {
view.setDraggable(value);
view.setUpdated(true);
}
@Override
public void setTitle(MapMarker view, @Nullable String value) {
view.setTitle(value);
view.setUpdated(true);
}
@Override
public void setTracksViewChanges(MapMarker view, boolean value) {
view.setTracksViewChanges(value);
}
@Override
public void setIdentifier(MapMarker view, @Nullable String value) {
view.setIdentifier(value);
view.setUpdated(true);
}
@Override
public void setIsPreselected(MapMarker view, boolean value) {
}
@Override
public void setOpacity(MapMarker view, double value) {
view.setOpacity((float) value);
view.setUpdated(true);
}
@Override
public void setPinColor(MapMarker view, @Nullable Integer value) {
float[] hsv = new float[3];
Color.colorToHSV(value, hsv);
// NOTE: android only supports a hue
view.setMarkerHue(hsv[0]);
view.setUpdated(true);
}
@Override
public void setTitleVisibility(MapMarker view, @Nullable String value) {
}
@Override
public void setSubtitleVisibility(MapMarker view, @Nullable String value) {
}
@Override
public void setUseLegacyPinView(MapMarker view, boolean value) {
}
@Override
public void animateToCoordinates(MapMarker view, double latitude, double longitude, int duration) {
view.animateToCoodinate(new LatLng(latitude, longitude), duration);
}
@Override
public void setCoordinates(MapMarker view, double latitude, double longitude) {
view.setCoordinate(new LatLng(latitude, longitude));
view.setUpdated(true);
}
@Override
public void showCallout(MapMarker view) {
((Marker) view.getFeature()).showInfoWindow();
view.setUpdated(true);
}
@Override
public void hideCallout(MapMarker view) {
((Marker) view.getFeature()).hideInfoWindow();
view.setUpdated(true);
}
@Override
public void redrawCallout(MapMarker view) {
}
@Override
public void redraw(MapMarker view) {
view.redraw();
}
@Override
public void addView(MapMarker parent, View child, int index) {
// if an <Callout /> component is a child, then it is a callout view, NOT part of the
// marker.
if (child instanceof MapCallout) {
parent.setCalloutView((MapCallout) child);
} else {
super.addView(parent, child, index);
if (index == 0) {
child.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
int newWidth = right - left;
int newHeight = bottom - top;
MapMarker marker = (MapMarker) v.getParent();
if(marker != null){
marker.update(newWidth, newHeight);
}
}
});
}
parent.update(true);
}
}
}

View File

@@ -0,0 +1,304 @@
package com.rnmaps.fabric;
import static com.rnmaps.maps.MapModule.SNAPSHOT_FORMAT_JPG;
import static com.rnmaps.maps.MapModule.SNAPSHOT_FORMAT_PNG;
import static com.rnmaps.maps.MapModule.SNAPSHOT_RESULT_BASE64;
import static com.rnmaps.maps.MapModule.SNAPSHOT_RESULT_FILE;
import static com.rnmaps.maps.MapModule.closeQuietly;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.location.Address;
import android.location.Geocoder;
import android.net.Uri;
import android.util.Base64;
import android.util.DisplayMetrics;
import androidx.annotation.Nullable;
import com.facebook.fbreact.specs.NativeAirMapsModuleSpec;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.UIManager;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeMap;
import com.facebook.react.uimanager.UIManagerHelper;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.LatLng;
import com.rnmaps.maps.MapUIBlock;
import com.rnmaps.maps.MapView;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
public class NativeAirMapsModule extends NativeAirMapsModuleSpec {
public NativeAirMapsModule(ReactApplicationContext reactContext) {
super(reactContext);
}
@Override
public String getName() {
return NAME;
}
@Override
public void getCamera(double tag, Promise promise) {
UIManager uiManager = UIManagerHelper.getUIManagerForReactTag(getReactApplicationContext(), (int) tag);
getReactApplicationContext().runOnUiQueueThread(new Runnable() {
@Override
public void run() {
MapView view = (MapView) uiManager.resolveView((int) tag);
if (view == null || view.map == null) {
promise.reject("E_MAP_CAMERA", "Cannot get camera position because map view is null");
return;
}
CameraPosition position = view.map.getCameraPosition();
WritableMap map = Arguments.createMap();
WritableMap center = Arguments.createMap();
center.putDouble("latitude", position.target.latitude);
center.putDouble("longitude", position.target.longitude);
map.putMap("center", center);
map.putDouble("heading", position.bearing);
map.putDouble("pitch", position.tilt);
map.putDouble("zoom", position.zoom);
promise.resolve(map);
}
});
}
@Override
public void getMarkersFrames(double tag, boolean onlyVisible, Promise promise) {
UIManager uiManager = UIManagerHelper.getUIManagerForReactTag(getReactApplicationContext(), (int) tag);
getReactApplicationContext().runOnUiQueueThread(() -> {
MapView view = (MapView) uiManager.resolveView((int) tag);
if (view == null) {
promise.reject("E_MAP_MARKERS", "Cannot get markers frames because map view is null");
return;
}
double[][] boundaries = view.getMarkersFrames(onlyVisible);
if (boundaries != null) {
WritableMap coordinates = new WritableNativeMap();
WritableMap northEastHash = new WritableNativeMap();
WritableMap southWestHash = new WritableNativeMap();
northEastHash.putDouble("longitude", boundaries[0][0]);
northEastHash.putDouble("latitude", boundaries[0][1]);
southWestHash.putDouble("longitude", boundaries[1][0]);
southWestHash.putDouble("latitude", boundaries[1][1]);
coordinates.putMap("northEast", northEastHash);
coordinates.putMap("southWest", southWestHash);
promise.resolve(coordinates);
} else {
promise.resolve(null);
}
});
}
@Override
public void getMapBoundaries(double tag, Promise promise) {
UIManager uiManager = UIManagerHelper.getUIManagerForReactTag(getReactApplicationContext(), (int) tag);
getReactApplicationContext().runOnUiQueueThread(() -> {
MapView view = (MapView) uiManager.resolveView((int) tag);
if (view == null) {
promise.reject("E_MAP_BOUNDARIES", "Cannot get map boundaries because map view is null");
return;
}
double[][] boundaries = view.getMapBoundaries();
if (boundaries == null) {
promise.reject("E_MAP_BOUNDARIES", "Map boundaries are null");
return;
}
WritableMap coordinates = new WritableNativeMap();
WritableMap northEastHash = new WritableNativeMap();
WritableMap southWestHash = new WritableNativeMap();
northEastHash.putDouble("longitude", boundaries[0][0]);
northEastHash.putDouble("latitude", boundaries[0][1]);
southWestHash.putDouble("longitude", boundaries[1][0]);
southWestHash.putDouble("latitude", boundaries[1][1]);
coordinates.putMap("northEast", northEastHash);
coordinates.putMap("southWest", southWestHash);
promise.resolve(coordinates);
});
}
@Override
public void takeSnapshot(double tag, String config, Promise promise) {
WritableMap options = null;
try {
if (config != null) {
JSONObject jsonObject = new JSONObject(config);
options = JSONUtil.convertJsonToWritable(jsonObject);
WritableMap finalOptions = options;
final ReactApplicationContext context = getReactApplicationContext();
final String format = finalOptions.hasKey("format") ? finalOptions.getString("format") : "png";
final Bitmap.CompressFormat compressFormat =
format.equals(SNAPSHOT_FORMAT_PNG) ? Bitmap.CompressFormat.PNG :
format.equals(SNAPSHOT_FORMAT_JPG) ? Bitmap.CompressFormat.JPEG : null;
final double quality = finalOptions.hasKey("quality") ? finalOptions.getDouble("quality") : 1.0;
final DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
final Integer width =
finalOptions.hasKey("width") ? (int) (displayMetrics.density * finalOptions.getDouble("width")) : 0;
final Integer height =
finalOptions.hasKey("height") ? (int) (displayMetrics.density * finalOptions.getDouble("height")) : 0;
final String result = finalOptions.hasKey("result") ? finalOptions.getString("result") : "file";
UIManager uiManager = UIManagerHelper.getUIManagerForReactTag(getReactApplicationContext(), (int) tag);
getReactApplicationContext().runOnUiQueueThread(new Runnable() {
@Override
public void run() {
MapView view = (MapView) uiManager.resolveView((int) tag);
if (view == null || view.map == null) {
promise.reject("E_SNAPSHOT", "Cannot take snapshot because map view or map is null");
return;
}
view.map.snapshot(snapshot -> {
// Convert image to requested width/height if necessary
if (snapshot == null) {
promise.reject("Failed to generate bitmap, snapshot = null");
return;
}
if ((width != 0) && (height != 0) &&
(width != snapshot.getWidth() || height != snapshot.getHeight())) {
snapshot = Bitmap.createScaledBitmap(snapshot, width, height, true);
}
// Save the snapshot to disk
if (result.equals(SNAPSHOT_RESULT_FILE)) {
File tempFile;
FileOutputStream outputStream;
try {
tempFile =
File.createTempFile("AirMapSnapshot", "." + format, context.getCacheDir());
outputStream = new FileOutputStream(tempFile);
} catch (Exception e) {
promise.reject(e);
return;
}
snapshot.compress(compressFormat, (int) (100.0 * quality), outputStream);
closeQuietly(outputStream);
String uri = Uri.fromFile(tempFile).toString();
promise.resolve(uri);
} else if (result.equals(SNAPSHOT_RESULT_BASE64)) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
snapshot.compress(compressFormat, (int) (100.0 * quality), outputStream);
closeQuietly(outputStream);
byte[] bytes = outputStream.toByteArray();
String data = Base64.encodeToString(bytes, Base64.NO_WRAP);
promise.resolve(data);
}
});
}
});
}
} catch (JSONException e) {
promise.reject("Failed to parse config ", config);
}
}
@Override
public void getAddressFromCoordinates(double tag, ReadableMap coordinate, Promise promise) {
final ReactApplicationContext context = getReactApplicationContext();
if (coordinate == null ||
!coordinate.hasKey("latitude") ||
!coordinate.hasKey("longitude")) {
promise.reject("Invalid coordinate format");
return;
}
Geocoder geocoder = new Geocoder(context);
try {
List<Address> list =
geocoder.getFromLocation(coordinate.getDouble("latitude"), coordinate.getDouble("longitude"), 1);
if (list.isEmpty()) {
promise.reject("Can not get address location");
return;
}
Address address = list.get(0);
WritableMap addressJson = new WritableNativeMap();
addressJson.putString("name", address.getFeatureName());
addressJson.putString("locality", address.getLocality());
addressJson.putString("thoroughfare", address.getThoroughfare());
addressJson.putString("subThoroughfare", address.getSubThoroughfare());
addressJson.putString("subLocality", address.getSubLocality());
addressJson.putString("administrativeArea", address.getAdminArea());
addressJson.putString("subAdministrativeArea", address.getSubAdminArea());
addressJson.putString("postalCode", address.getPostalCode());
addressJson.putString("countryCode", address.getCountryCode());
addressJson.putString("country", address.getCountryName());
promise.resolve(addressJson);
} catch (IOException e) {
promise.reject("Can not get address location");
}
}
@Override
public void getPointForCoordinate(double tag, ReadableMap coordinate, Promise promise) {
final ReactApplicationContext context = getReactApplicationContext();
final double density = (double) context.getResources().getDisplayMetrics().density;
final LatLng coord = new LatLng(
coordinate.hasKey("latitude") ? coordinate.getDouble("latitude") : 0.0,
coordinate.hasKey("longitude") ? coordinate.getDouble("longitude") : 0.0
);
MapUIBlock uiBlock = new MapUIBlock((int) tag, promise, context, view -> {
Point pt = view.map.getProjection().toScreenLocation(coord);
WritableMap ptJson = new WritableNativeMap();
ptJson.putDouble("x", (double) pt.x / density);
ptJson.putDouble("y", (double) pt.y / density);
promise.resolve(ptJson);
return null;
});
uiBlock.addToUIManager();
}
@Override
public void getCoordinateForPoint(double tag, ReadableMap point, Promise promise) {
final ReactApplicationContext context = getReactApplicationContext();
final double density = (double) context.getResources().getDisplayMetrics().density;
final Point pt = new Point(
point.hasKey("x") ? (int) (point.getDouble("x") * density) : 0,
point.hasKey("y") ? (int) (point.getDouble("y") * density) : 0
);
MapUIBlock uiBlock = new MapUIBlock((int)tag, promise, context, view -> {
LatLng coord = view.map.getProjection().fromScreenLocation(pt);
WritableMap coordJson = new WritableNativeMap();
coordJson.putDouble("latitude", coord.latitude);
coordJson.putDouble("longitude", coord.longitude);
promise.resolve(coordJson);
return null;
});
uiBlock.addToUIManager();
}
}

View File

@@ -0,0 +1,101 @@
package com.rnmaps.fabric;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.ViewManagerDelegate;
import com.facebook.react.viewmanagers.RNMapsOverlayManagerDelegate;
import com.facebook.react.viewmanagers.RNMapsOverlayManagerInterface;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
import com.rnmaps.maps.MapOverlay;
import java.util.HashMap;
import java.util.Map;
@ReactModule(name = OverlayManager.REACT_CLASS)
public class OverlayManager extends ViewGroupManager<MapOverlay> implements RNMapsOverlayManagerInterface<MapOverlay> {
public OverlayManager(ReactApplicationContext context) {
super(context);
}
private final RNMapsOverlayManagerDelegate<MapOverlay, OverlayManager> delegate =
new RNMapsOverlayManagerDelegate<>(this);
@Override
public ViewManagerDelegate<MapOverlay> getDelegate() {
return delegate;
}
@Override
public String getName() {
return REACT_CLASS;
}
@Override
public MapOverlay createViewInstance(ThemedReactContext context) {
return new MapOverlay(context);
}
public static final String REACT_CLASS = "RNMapsOverlay";
@Nullable
@Override
public Map<String, Object> getExportedCustomBubblingEventTypeConstants() {
return MapOverlay.getExportedCustomBubblingEventTypeConstants();
}
@Nullable
@Override
public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
return new HashMap<>();
}
@Override
public void setZIndex(MapOverlay view, float value) {
view.setZIndex(value);
}
@Override
public void setBearing(MapOverlay view, float value) {
view.setBearing(value);
}
@Override
public void setBounds(MapOverlay view, @Nullable ReadableMap value) {
LatLng sw = new LatLng(value.getMap("southWest").getDouble("latitude"),
value.getMap("southWest").getDouble("longitude"));
LatLng ne = new LatLng(value.getMap("northEast").getDouble("latitude"),
value.getMap("northEast").getDouble("longitude"));
LatLngBounds bounds = new LatLngBounds(sw, ne);
view.setBounds(bounds);
}
@Override
public void setImage(MapOverlay view, @Nullable ReadableMap value) {
if (value != null) {
view.setImage(value.getString("uri"));
}
}
@Override
public void setOpacity(MapOverlay view, float value) {
// todo report to google that it's not working / fix it
// view.setTransparency(value);
}
@Override
public void setTappable(MapOverlay view, boolean value) {
view.setTappable(value);
}
}

View File

@@ -0,0 +1,96 @@
package com.rnmaps.fabric;
import androidx.annotation.Nullable;
import android.util.DisplayMetrics;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.ViewManagerDelegate;
import com.facebook.react.viewmanagers.RNMapsGooglePolygonManagerDelegate;
import com.facebook.react.viewmanagers.RNMapsGooglePolygonManagerInterface;
import com.rnmaps.maps.MapPolygon;
import java.util.HashMap;
import java.util.Map;
@ReactModule(name = PolygonManager.REACT_CLASS)
public class PolygonManager extends ViewGroupManager<MapPolygon> implements RNMapsGooglePolygonManagerInterface<MapPolygon> {
public PolygonManager(ReactApplicationContext context){
super(context);
}//
private final RNMapsGooglePolygonManagerDelegate<MapPolygon, PolygonManager> delegate =
new RNMapsGooglePolygonManagerDelegate<>(this);
@Override
public ViewManagerDelegate<MapPolygon> getDelegate() {
return delegate;
}
@Override
public String getName() {
return REACT_CLASS;
}
@Override
public MapPolygon createViewInstance(ThemedReactContext context) {
return new MapPolygon(context);
}
public static final String REACT_CLASS = "RNMapsGooglePolygon";
@Nullable
@Override
public Map<String, Object> getExportedCustomBubblingEventTypeConstants() {
return MapPolygon.getExportedCustomBubblingEventTypeConstants();
}
@Nullable
@Override
public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
return new HashMap<>();
}
@Override
public void setCoordinates(MapPolygon view, @Nullable ReadableArray value) {
view.setCoordinates(value);
}
@Override
public void setFillColor(MapPolygon view, @Nullable Integer value) {
view.setFillColor(value);
}
@Override
public void setStrokeColor(MapPolygon view, @Nullable Integer value) {
view.setStrokeColor(value);
}
@Override
public void setStrokeWidth(MapPolygon view, float value) {
DisplayMetrics metrics = view.getContext().getResources().getDisplayMetrics();
float widthInScreenPx = metrics.density * value; // done for parity with iOS
view.setStrokeWidth(widthInScreenPx);
}
@Override
public void setGeodesic(MapPolygon view, boolean value) {
view.setGeodesic(value);
}
@Override
public void setHoles(MapPolygon view, @Nullable ReadableArray value) {
view.setHoles(value);
}
@Override
public void setTappable(MapPolygon view, boolean value) {
view.setTappable(value);
}
}

View File

@@ -0,0 +1,121 @@
package com.rnmaps.fabric;
import android.content.Context;
import android.util.DisplayMetrics;
import android.view.WindowManager;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.ViewManagerDelegate;
import com.facebook.react.viewmanagers.RNMapsPolylineManagerDelegate;
import com.facebook.react.viewmanagers.RNMapsPolylineManagerInterface;
import com.rnmaps.maps.MapPolyline;
import java.util.HashMap;
import java.util.Map;
@ReactModule(name = PolylineManager.REACT_CLASS)
public class PolylineManager extends ViewGroupManager<MapPolyline> implements RNMapsPolylineManagerInterface<MapPolyline> {
private DisplayMetrics metrics;
public PolylineManager(ReactApplicationContext context) {
super(context);
metrics = new DisplayMetrics();
((WindowManager) context.getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay()
.getRealMetrics(metrics);
}
private final RNMapsPolylineManagerDelegate<MapPolyline, PolylineManager> delegate =
new RNMapsPolylineManagerDelegate<>(this);
@Override
public ViewManagerDelegate<MapPolyline> getDelegate() {
return delegate;
}
@Override
public String getName() {
return REACT_CLASS;
}
@Override
public MapPolyline createViewInstance(ThemedReactContext context) {
return new MapPolyline(context);
}
public static final String REACT_CLASS = "RNMapsPolyline";
@Nullable
@Override
public Map<String, Object> getExportedCustomBubblingEventTypeConstants() {
return MapPolyline.getExportedCustomBubblingEventTypeConstants();
}
@Nullable
@Override
public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
return new HashMap<>();
}
@Override
public void setCoordinates(MapPolyline view, @Nullable ReadableArray value) {
view.setCoordinates(value);
}
@Override
public void setStrokeColor(MapPolyline view, @Nullable Integer value) {
view.setColor(value);
}
@Override
public void setStrokeColors(MapPolyline view, @Nullable ReadableArray value) {
view.setStrokeColors(value);
}
@Override
public void setStrokeWidth(MapPolyline view, float value) {
float widthInScreenPx = metrics.density * value; // done for parity with iOS
view.setWidth(widthInScreenPx);
}
@Override
public void setGeodesic(MapPolyline view, boolean value) {
view.setGeodesic(value);
}
@Override
public void setLineCap(MapPolyline view, @Nullable String value) {
view.setLineCap(value);
}
@Override
public void setLineDashPattern(MapPolyline view, @Nullable ReadableArray value) {
view.setLineDashPattern(value);
}
@Override
public void setLineJoin(MapPolyline view, @Nullable String value) {
view.setLineJoin(value);
}
@Override
public void setTappable(MapPolyline view, boolean value) {
view.setTappable(value);
}
@Override
public void setZIndex(MapPolyline view, float value) {
view.setZIndex(value);
}
}

View File

@@ -0,0 +1,124 @@
package com.rnmaps.fabric;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.ViewManagerDelegate;
import com.facebook.react.viewmanagers.RNMapsUrlTileManagerDelegate;
import com.facebook.react.viewmanagers.RNMapsUrlTileManagerInterface;
import com.rnmaps.maps.MapPolyline;
import com.rnmaps.maps.MapUrlTile;
import java.util.HashMap;
import java.util.Map;
@ReactModule(name = UrlTileManager.REACT_CLASS)
public class UrlTileManager extends ViewGroupManager<MapUrlTile> implements RNMapsUrlTileManagerInterface<MapUrlTile> {
public static final String REACT_CLASS = "RNMapsUrlTile";
public UrlTileManager(ReactApplicationContext context) {
super(context);
}
private final RNMapsUrlTileManagerDelegate<MapUrlTile, UrlTileManager> delegate =
new RNMapsUrlTileManagerDelegate<>(this);
@Override
public ViewManagerDelegate<MapUrlTile> getDelegate() {
return delegate;
}
@Override
public String getName() {
return REACT_CLASS;
}
@Override
public MapUrlTile createViewInstance(ThemedReactContext context) {
return new MapUrlTile(context);
}
@Nullable
@Override
public Map<String, Object> getExportedCustomBubblingEventTypeConstants() {
return new HashMap<>();
}
@Nullable
@Override
public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
return new HashMap<>();
}
@Override
public void setDoubleTileSize(MapUrlTile view, boolean value) {
view.setDoubleTileSize(value);
}
@Override
public void setFlipY(MapUrlTile view, boolean value) {
view.setFlipY(value);
}
@Override
public void setMaximumNativeZ(MapUrlTile view, int value) {
view.setMaximumNativeZ(value);
}
@Override
public void setMaximumZ(MapUrlTile view, int value) {
view.setMaximumZ(value);
}
@Override
public void setMinimumZ(MapUrlTile view, int value) {
view.setMinimumZ(value);
}
@Override
public void setOfflineMode(MapUrlTile view, boolean value) {
view.setOfflineMode(value);
}
@Override
public void setShouldReplaceMapContent(MapUrlTile view, boolean value) {
// not supported
}
@Override
public void setZIndex(@NonNull MapUrlTile view, float zIndex) {
super.setZIndex(view, zIndex);
view.setZIndex(zIndex);
}
@Override
public void setOpacity(@NonNull MapUrlTile view, float opacity) {
super.setOpacity(view, opacity);
view.setOpacity(opacity);
}
@Override
public void setTileCacheMaxAge(MapUrlTile view, int value) {
view.setTileCacheMaxAge(value);
}
@Override
public void setTileCachePath(MapUrlTile view, @Nullable String value) {
view.setTileCachePath(value);
}
@Override
public void setTileSize(MapUrlTile view, int value) {
view.setTileSize(value);
}
@Override
public void setUrlTemplate(MapUrlTile view, @Nullable String value) {
view.setUrlTemplate(value);
}
}

View File

@@ -0,0 +1,114 @@
package com.rnmaps.fabric;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.ViewManagerDelegate;
import com.facebook.react.viewmanagers.RNMapsWMSTileManagerDelegate;
import com.facebook.react.viewmanagers.RNMapsWMSTileManagerInterface;
import com.rnmaps.maps.MapWMSTile;
import java.util.HashMap;
import java.util.Map;
@ReactModule(name = WMSTileManager.REACT_CLASS)
public class WMSTileManager extends ViewGroupManager<MapWMSTile> implements RNMapsWMSTileManagerInterface<MapWMSTile> {
public static final String REACT_CLASS = "RNMapsWMSTile";
public WMSTileManager(ReactApplicationContext context) {
super(context);
}
private final RNMapsWMSTileManagerDelegate<MapWMSTile, WMSTileManager> delegate =
new RNMapsWMSTileManagerDelegate<>(this);
@Override
public ViewManagerDelegate<MapWMSTile> getDelegate() {
return delegate;
}
@Override
public String getName() {
return REACT_CLASS;
}
@Override
public MapWMSTile createViewInstance(ThemedReactContext context) {
return new MapWMSTile(context);
}
@Nullable
@Override
public Map<String, Object> getExportedCustomBubblingEventTypeConstants() {
return new HashMap<>();
}
@Nullable
@Override
public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
return new HashMap<>();
}
@Override
public void setMaximumNativeZ(MapWMSTile view, int value) {
view.setMaximumNativeZ(value);
}
@Override
public void setMaximumZ(MapWMSTile view, int value) {
view.setMaximumZ(value);
}
@Override
public void setMinimumZ(MapWMSTile view, int value) {
view.setMinimumZ(value);
}
@Override
public void setOfflineMode(MapWMSTile view, boolean value) {
view.setOfflineMode(value);
}
@Override
public void setShouldReplaceMapContent(MapWMSTile view, boolean value) {
// not supported
}
@Override
public void setZIndex(@NonNull MapWMSTile view, float zIndex) {
super.setZIndex(view, zIndex);
view.setZIndex(zIndex);
}
@Override
public void setOpacity(@NonNull MapWMSTile view, float opacity) {
super.setOpacity(view, opacity);
view.setOpacity(opacity);
}
@Override
public void setTileCacheMaxAge(MapWMSTile view, int value) {
view.setTileCacheMaxAge(value);
}
@Override
public void setTileCachePath(MapWMSTile view, @Nullable String value) {
view.setTileCachePath(value);
}
@Override
public void setTileSize(MapWMSTile view, int value) {
view.setTileSize(value);
}
@Override
public void setUrlTemplate(MapWMSTile view, @Nullable String value) {
view.setUrlTemplate(value);
}
}

View File

@@ -0,0 +1,25 @@
package com.rnmaps.fabric.event;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
public class OnCalloutPressEvent extends Event<OnCalloutPressEvent> {
public static final String EVENT_NAME = "topCalloutPress";
private final WritableMap payload;
public OnCalloutPressEvent(int surfaceId, int viewId, WritableMap payload) {
super(surfaceId, viewId);
this.payload = payload;
}
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public WritableMap getEventData() {
return payload;
}
}

View File

@@ -0,0 +1,28 @@
package com.rnmaps.fabric.event;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
public class OnDeselectEvent extends Event<OnDeselectEvent> {
public static final String EVENT_NAME = "topDeselect";
private final WritableMap payload;
public OnDeselectEvent(int surfaceId, int viewId, WritableMap payload) {
super(surfaceId, viewId);
this.payload = payload;
}
@NonNull
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public WritableMap getEventData() {
return payload;
}
}

View File

@@ -0,0 +1,27 @@
package com.rnmaps.fabric.event;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
public class OnDoublePressEvent extends Event<OnDoublePressEvent> {
public static final String EVENT_NAME = "topDoublePress";
private final WritableMap payload;
public OnDoublePressEvent(int surfaceId, int viewId, WritableMap payload) {
super(surfaceId, viewId);
this.payload = payload;
}
@NonNull
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public WritableMap getEventData() {
return payload;
}
}

View File

@@ -0,0 +1,28 @@
package com.rnmaps.fabric.event;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
public class OnDragEndEvent extends Event<OnDragEndEvent> {
public static final String EVENT_NAME = "topDragEnd";
private final WritableMap payload;
public OnDragEndEvent(int surfaceId, int viewId, WritableMap payload) {
super(surfaceId, viewId);
this.payload = payload;
}
@NonNull
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public WritableMap getEventData() {
return payload;
}
}

View File

@@ -0,0 +1,28 @@
package com.rnmaps.fabric.event;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
public class OnDragEvent extends Event<OnDragEvent> {
public static final String EVENT_NAME = "topDrag";
private final WritableMap payload;
public OnDragEvent(int surfaceId, int viewId, WritableMap payload) {
super(surfaceId, viewId);
this.payload = payload;
}
@NonNull
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public WritableMap getEventData() {
return payload;
}
}

View File

@@ -0,0 +1,28 @@
package com.rnmaps.fabric.event;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
public class OnDragStartEvent extends Event<OnDragStartEvent> {
public static final String EVENT_NAME = "topDragStart";
private final WritableMap payload;
public OnDragStartEvent(int surfaceId, int viewId, WritableMap payload) {
super(surfaceId, viewId);
this.payload = payload;
}
@NonNull
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public WritableMap getEventData() {
return payload;
}
}

View File

@@ -0,0 +1,28 @@
package com.rnmaps.fabric.event;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
public class OnIndoorBuildingFocusedEvent extends Event<OnIndoorBuildingFocusedEvent> {
public static final String EVENT_NAME = "topIndoorBuildingFocused";
private final WritableMap payload;
public OnIndoorBuildingFocusedEvent(int surfaceId, int viewId, WritableMap payload) {
super(surfaceId, viewId);
this.payload = payload;
}
@NonNull
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public WritableMap getEventData() {
return payload;
}
}

View File

@@ -0,0 +1,28 @@
package com.rnmaps.fabric.event;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
public class OnIndoorLevelActivatedEvent extends Event<OnIndoorLevelActivatedEvent> {
public static final String EVENT_NAME = "topIndoorLevelActivated";
private final WritableMap payload;
public OnIndoorLevelActivatedEvent(int surfaceId, int viewId, WritableMap payload) {
super(surfaceId, viewId);
this.payload = payload;
}
@NonNull
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public WritableMap getEventData() {
return payload;
}
}

View File

@@ -0,0 +1,28 @@
package com.rnmaps.fabric.event;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
public class OnKmlReadyEvent extends Event<OnKmlReadyEvent> {
public static final String EVENT_NAME = "topKmlReady";
private final WritableMap payload;
public OnKmlReadyEvent(int surfaceId, int viewId, WritableMap payload) {
super(surfaceId, viewId);
this.payload = payload;
}
@NonNull
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public WritableMap getEventData() {
return payload;
}
}

View File

@@ -0,0 +1,28 @@
package com.rnmaps.fabric.event;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
public class OnLongPressEvent extends Event<OnLongPressEvent> {
public static final String EVENT_NAME = "topLongPress";
private final WritableMap payload;
public OnLongPressEvent(int surfaceId, int viewId, WritableMap payload) {
super(surfaceId, viewId);
this.payload = payload;
}
@NonNull
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public WritableMap getEventData() {
return payload;
}
}

View File

@@ -0,0 +1,29 @@
package com.rnmaps.fabric.event;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
public class OnMapLoadedEvent extends Event<OnMapLoadedEvent> {
public static final String EVENT_NAME = "topMapLoaded";
private final WritableMap payload;
public OnMapLoadedEvent(int surfaceId, int viewId, WritableMap payload) {
super(surfaceId, viewId);
this.payload = payload;
}
@NonNull
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public WritableMap getEventData() {
return payload;
}
}

View File

@@ -0,0 +1,29 @@
package com.rnmaps.fabric.event;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
public class OnMapReadyEvent extends Event<OnMapReadyEvent> {
public static final String EVENT_NAME = "topMapReady";
private final WritableMap payload;
public OnMapReadyEvent(int surfaceId, int viewId, WritableMap payload) {
super(surfaceId, viewId);
this.payload = payload;
}
@NonNull
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public WritableMap getEventData() {
return payload;
}
}

View File

@@ -0,0 +1,28 @@
package com.rnmaps.fabric.event;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
public class OnMarkerDeselectEvent extends Event<OnMarkerDeselectEvent> {
public static final String EVENT_NAME = "topMarkerDeselect";
private final WritableMap payload;
public OnMarkerDeselectEvent(int surfaceId, int viewId, WritableMap payload) {
super(surfaceId, viewId);
this.payload = payload;
}
@NonNull
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public WritableMap getEventData() {
return payload;
}
}

View File

@@ -0,0 +1,28 @@
package com.rnmaps.fabric.event;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
public class OnMarkerDragEndEvent extends Event<OnMarkerDragEndEvent> {
public static final String EVENT_NAME = "topMarkerDragEnd";
private final WritableMap payload;
public OnMarkerDragEndEvent(int surfaceId, int viewId, WritableMap payload) {
super(surfaceId, viewId);
this.payload = payload;
}
@NonNull
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public WritableMap getEventData() {
return payload;
}
}

View File

@@ -0,0 +1,28 @@
package com.rnmaps.fabric.event;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
public class OnMarkerDragEvent extends Event<OnMarkerDragEvent> {
public static final String EVENT_NAME = "topMarkerDrag";
private final WritableMap payload;
public OnMarkerDragEvent(int surfaceId, int viewId, WritableMap payload) {
super(surfaceId, viewId);
this.payload = payload;
}
@NonNull
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public WritableMap getEventData() {
return payload;
}
}

View File

@@ -0,0 +1,28 @@
package com.rnmaps.fabric.event;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
public class OnMarkerDragStartEvent extends Event<OnMarkerDragStartEvent> {
public static final String EVENT_NAME = "topMarkerDragStart";
private final WritableMap payload;
public OnMarkerDragStartEvent(int surfaceId, int viewId, WritableMap payload) {
super(surfaceId, viewId);
this.payload = payload;
}
@NonNull
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public WritableMap getEventData() {
return payload;
}
}

View File

@@ -0,0 +1,28 @@
package com.rnmaps.fabric.event;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
public class OnMarkerPressEvent extends Event<OnMarkerPressEvent> {
public static final String EVENT_NAME = "topMarkerPress";
private final WritableMap payload;
public OnMarkerPressEvent(int surfaceId, int viewId, WritableMap payload) {
super(surfaceId, viewId);
this.payload = payload;
}
@NonNull
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public WritableMap getEventData() {
return payload;
}
}

View File

@@ -0,0 +1,28 @@
package com.rnmaps.fabric.event;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
public class OnMarkerSelectEvent extends Event<OnMarkerSelectEvent> {
public static final String EVENT_NAME = "topMarkerSelect";
private final WritableMap payload;
public OnMarkerSelectEvent(int surfaceId, int viewId, WritableMap payload) {
super(surfaceId, viewId);
this.payload = payload;
}
@NonNull
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public WritableMap getEventData() {
return payload;
}
}

View File

@@ -0,0 +1,28 @@
package com.rnmaps.fabric.event;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
public class OnPanDragEvent extends Event<OnPanDragEvent> {
public static final String EVENT_NAME = "topPanDrag";
private final WritableMap payload;
public OnPanDragEvent(int surfaceId, int viewId, WritableMap payload) {
super(surfaceId, viewId);
this.payload = payload;
}
@NonNull
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public WritableMap getEventData() {
return payload;
}
}

View File

@@ -0,0 +1,27 @@
package com.rnmaps.fabric.event;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
public class OnPoiClickEvent extends Event<OnPoiClickEvent> {
public static final String EVENT_NAME = "topPoiClick";
private final WritableMap payload;
public OnPoiClickEvent(int surfaceId, int viewId, WritableMap payload) {
super(surfaceId, viewId);
this.payload = payload;
}
@NonNull
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public WritableMap getEventData() {
return payload;
}
}

View File

@@ -0,0 +1,27 @@
package com.rnmaps.fabric.event;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
public class OnPressEvent extends Event<OnPressEvent> {
public static final String EVENT_NAME = "topPress";
private final WritableMap payload;
public OnPressEvent(int surfaceId, int viewId, WritableMap payload) {
super(surfaceId, viewId);
this.payload = payload;
}
@NonNull
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public WritableMap getEventData() {
return payload;
}
}

View File

@@ -0,0 +1,30 @@
package com.rnmaps.fabric.event;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
public class OnRegionChangeCompleteEvent extends Event<OnRegionChangeCompleteEvent> {
public static final String EVENT_NAME = "topRegionChangeComplete";
private final WritableMap payload;
public OnRegionChangeCompleteEvent(int surfaceId, int viewId, WritableMap payload) {
super(surfaceId, viewId);
this.payload = payload;
}
@NonNull
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
protected WritableMap getEventData() {
return payload;
}
}

View File

@@ -0,0 +1,47 @@
package com.rnmaps.fabric.event;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeMap;
import com.facebook.react.uimanager.events.Event;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
public class OnRegionChangeEvent extends Event<OnRegionChangeEvent> {
public static final String EVENT_NAME = "topRegionChange";
private final WritableMap payload;
public OnRegionChangeEvent(int surfaceId, int viewId, WritableMap payload) {
super(surfaceId, viewId);
this.payload = payload;
}
public static WritableMap payLoadFor(LatLngBounds bounds, boolean isGesture) {
WritableMap event = new WritableNativeMap();
WritableMap region = new WritableNativeMap();
LatLng center = bounds.getCenter();
region.putDouble("latitude", center.latitude);
region.putDouble("longitude", center.longitude);
region.putDouble("latitudeDelta", bounds.northeast.latitude - bounds.southwest.latitude);
region.putDouble("longitudeDelta", bounds.northeast.longitude - bounds.southwest.longitude);
event.putMap("region", region);
event.putBoolean("isGesture", isGesture);
return event;
}
@NonNull
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
protected WritableMap getEventData() {
return payload;
}
}

View File

@@ -0,0 +1,33 @@
package com.rnmaps.fabric.event;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeMap;
import com.facebook.react.uimanager.events.Event;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
public class OnRegionChangeStartEvent extends Event<OnRegionChangeStartEvent> {
public static final String EVENT_NAME = "topRegionChangeStart";
private final WritableMap payload;
public OnRegionChangeStartEvent(int surfaceId, int viewId, WritableMap payload) {
super(surfaceId, viewId);
this.payload = payload;
}
@NonNull
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
protected WritableMap getEventData() {
return payload;
}
}

View File

@@ -0,0 +1,28 @@
package com.rnmaps.fabric.event;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
public class OnSelectEvent extends Event<OnSelectEvent> {
public static final String EVENT_NAME = "topSelect";
private final WritableMap payload;
public OnSelectEvent(int surfaceId, int viewId, WritableMap payload) {
super(surfaceId, viewId);
this.payload = payload;
}
@NonNull
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public WritableMap getEventData() {
return payload;
}
}

View File

@@ -0,0 +1,28 @@
package com.rnmaps.fabric.event;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
public class OnUserLocationChangeEvent extends Event<OnUserLocationChangeEvent> {
public static final String EVENT_NAME = "topUserLocationChange";
private final WritableMap payload;
public OnUserLocationChangeEvent(int surfaceId, int viewId, WritableMap payload) {
super(surfaceId, viewId);
this.payload = payload;
}
@NonNull
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public WritableMap getEventData() {
return payload;
}
}

View File

@@ -0,0 +1,74 @@
package com.rnmaps.maps;
import android.content.Context;
import android.net.Uri;
import android.os.AsyncTask;
import com.facebook.common.logging.FLog;
import com.facebook.react.common.ReactConstants;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
public class FileUtil extends AsyncTask<String, Void, InputStream> {
private Context context;
public FileUtil(Context context) {
super();
this.context = context;
}
protected InputStream doInBackground(String... urls) {
try {
Uri fileContentUri = Uri.parse(urls[0]);
if (fileContentUri.getScheme().startsWith("http")) {
return getDownloadFileInputStream(context, fileContentUri);
}
return context.getContentResolver().openInputStream(fileContentUri);
} catch (Exception e) {
FLog.e(
ReactConstants.TAG,
"Could not retrieve file for contentUri " + urls[0],
e);
return null;
}
}
private InputStream getDownloadFileInputStream(Context context, Uri uri)
throws IOException {
final File outputDir = context.getApplicationContext().getCacheDir();
String NAME = "FileUtil";
String TEMP_FILE_SUFFIX = "temp";
final File file = File.createTempFile(NAME, TEMP_FILE_SUFFIX, outputDir);
file.deleteOnExit();
final URL url = new URL(uri.toString());
final InputStream is = url.openStream();
try {
final ReadableByteChannel channel = Channels.newChannel(is);
try {
final FileOutputStream stream = new FileOutputStream(file);
try {
stream.getChannel().transferFrom(channel, 0, Long.MAX_VALUE);
return new FileInputStream(file);
} finally {
stream.close();
}
} finally {
channel.close();
}
} finally {
is.close();
}
}
}

View File

@@ -0,0 +1,75 @@
package com.rnmaps.maps;
import android.annotation.SuppressLint;
import android.content.Context;
import android.location.Location;
import android.os.Looper;
import com.google.android.gms.location.FusedLocationProviderClient;
import com.google.android.gms.location.LocationCallback;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationResult;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.location.Priority;
import com.google.android.gms.maps.LocationSource;
import com.google.android.gms.tasks.OnSuccessListener;
import java.lang.SecurityException;
public class FusedLocationSource implements LocationSource {
private final FusedLocationProviderClient fusedLocationClientProviderClient;
private final LocationRequest locationRequest;
private LocationCallback locationCallback;
public FusedLocationSource(Context context){
fusedLocationClientProviderClient =
LocationServices.getFusedLocationProviderClient(context);
locationRequest = LocationRequest.create();
locationRequest.setPriority(Priority.PRIORITY_HIGH_ACCURACY);
locationRequest.setInterval(5000);
}
public void setPriority(int priority){
locationRequest.setPriority(priority);
}
public void setInterval(int interval){
locationRequest.setInterval(interval);
}
public void setFastestInterval(int fastestInterval){
locationRequest.setFastestInterval(fastestInterval);
}
@SuppressLint("MissingPermission")
@Override
public void activate(final OnLocationChangedListener onLocationChangedListener) {
try {
fusedLocationClientProviderClient.getLastLocation().addOnSuccessListener(new OnSuccessListener<Location>() {
@Override
public void onSuccess(Location location) {
if (location != null) {
onLocationChangedListener.onLocationChanged(location);
}
}
});
locationCallback = new LocationCallback() {
@Override
public void onLocationResult(LocationResult locationResult) {
for (Location location : locationResult.getLocations()) {
onLocationChangedListener.onLocationChanged(location);
}
}
};
fusedLocationClientProviderClient.requestLocationUpdates(locationRequest, locationCallback, Looper.myLooper());
} catch (SecurityException e) {
e.printStackTrace();
}
}
@Override
public void deactivate() {
fusedLocationClientProviderClient.removeLocationUpdates(locationCallback);
}
}

View File

@@ -0,0 +1,15 @@
package com.rnmaps.maps;
import android.graphics.Bitmap;
import com.google.android.gms.maps.model.BitmapDescriptor;
public interface ImageReadable {
public void setIconBitmap(Bitmap bitmap);
public void setIconBitmapDescriptor(BitmapDescriptor bitmapDescriptor);
public void update();
}

View File

@@ -0,0 +1,127 @@
package com.rnmaps.maps;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.Animatable;
import android.net.Uri;
import androidx.annotation.Nullable;
import com.facebook.common.references.CloseableReference;
import com.facebook.datasource.DataSource;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.drawee.controller.BaseControllerListener;
import com.facebook.drawee.controller.ControllerListener;
import com.facebook.drawee.drawable.ScalingUtils;
import com.facebook.drawee.generic.GenericDraweeHierarchy;
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
import com.facebook.drawee.interfaces.DraweeController;
import com.facebook.drawee.view.DraweeHolder;
import com.facebook.imagepipeline.core.ImagePipeline;
import com.facebook.imagepipeline.image.CloseableImage;
import com.facebook.imagepipeline.image.CloseableStaticBitmap;
import com.facebook.imagepipeline.image.ImageInfo;
import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.imagepipeline.request.ImageRequestBuilder;
import com.google.android.gms.maps.model.BitmapDescriptor;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
public class ImageReader {
private final ImageReadable imp;
private final Context context;
private final Resources resources;
private final DraweeHolder<?> logoHolder;
private DataSource<CloseableReference<CloseableImage>> dataSource;
private final ControllerListener<ImageInfo> mLogoControllerListener =
new BaseControllerListener<ImageInfo>() {
@Override
public void onFinalImageSet(
String id,
@Nullable final ImageInfo imageInfo,
@Nullable Animatable animatable) {
CloseableReference<CloseableImage> imageReference = null;
try {
imageReference = dataSource.getResult();
if (imageReference != null) {
CloseableImage image = imageReference.get();
if (image instanceof CloseableStaticBitmap) {
CloseableStaticBitmap closeableStaticBitmap = (CloseableStaticBitmap) image;
Bitmap bitmap = closeableStaticBitmap.getUnderlyingBitmap();
if (bitmap != null) {
bitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true);
imp.setIconBitmap(bitmap);
imp.setIconBitmapDescriptor(BitmapDescriptorFactory.fromBitmap(bitmap));
}
}
}
} finally {
dataSource.close();
if (imageReference != null) {
CloseableReference.closeSafely(imageReference);
}
}
imp.update();
}
};
public ImageReader(Context context, Resources resources, ImageReadable imp) {
this.context = context;
this.resources = resources;
this.imp = imp;
logoHolder = DraweeHolder.create(createDraweeHeirarchy(resources), context);
logoHolder.onAttach();
}
private GenericDraweeHierarchy createDraweeHeirarchy(Resources resources){
return new GenericDraweeHierarchyBuilder(resources)
.setActualImageScaleType(ScalingUtils.ScaleType.FIT_CENTER)
.setFadeDuration(0)
.build();
}
public void setImage(String uri) {
if (uri == null) {
imp.setIconBitmapDescriptor(null);
imp.update();
} else if (uri.startsWith("http://") || uri.startsWith("https://") ||
uri.startsWith("file://") || uri.startsWith("asset://") || uri.startsWith("data:")) {
ImageRequest imageRequest = ImageRequestBuilder
.newBuilderWithSource(Uri.parse(uri))
.build();
ImagePipeline imagePipeline = Fresco.getImagePipeline();
dataSource = imagePipeline.fetchDecodedImage(imageRequest, this);
DraweeController controller = Fresco.newDraweeControllerBuilder()
.setImageRequest(imageRequest)
.setControllerListener(mLogoControllerListener)
.setOldController(logoHolder.getController())
.build();
logoHolder.setController(controller);
} else {
BitmapDescriptor iconBitmapDescriptor = getBitmapDescriptorByName(uri);
imp.setIconBitmapDescriptor(iconBitmapDescriptor);
imp.setIconBitmap(BitmapFactory.decodeResource(this.resources, getDrawableResourceByName
(uri)));
imp.update();
}
}
private int getDrawableResourceByName(String name) {
return this.resources.getIdentifier(
name,
"drawable",
this.context.getPackageName());
}
private BitmapDescriptor getBitmapDescriptorByName(String name) {
return BitmapDescriptorFactory.fromResource(getDrawableResourceByName(name));
}
}

View File

@@ -0,0 +1,26 @@
package com.rnmaps.maps;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Base64;
import java.io.ByteArrayOutputStream;
public class ImageUtil {
public static Bitmap convert(String base64Str) throws IllegalArgumentException {
byte[] decodedBytes = Base64.decode(
base64Str.substring(base64Str.indexOf(",") + 1),
Base64.DEFAULT
);
return BitmapFactory.decodeByteArray(decodedBytes, 0, decodedBytes.length);
}
public static String convert(Bitmap bitmap) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream);
return Base64.encodeToString(outputStream.toByteArray(), Base64.DEFAULT);
}
}

View File

@@ -0,0 +1,47 @@
package com.rnmaps.maps;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
public class LatLngBoundsUtils {
public static boolean BoundsAreDifferent(LatLngBounds a, LatLngBounds b) {
LatLng centerA = a.getCenter();
double latA = centerA.latitude;
double lngA = centerA.longitude;
double latDeltaA = a.northeast.latitude - a.southwest.latitude;
double lngDeltaA = a.northeast.longitude - a.southwest.longitude;
LatLng centerB = b.getCenter();
double latB = centerB.latitude;
double lngB = centerB.longitude;
double latDeltaB = b.northeast.latitude - b.southwest.latitude;
double lngDeltaB = b.northeast.longitude - b.southwest.longitude;
double latEps = LatitudeEpsilon(a, b);
double lngEps = LongitudeEpsilon(a, b);
return
different(latA, latB, latEps) ||
different(lngA, lngB, lngEps) ||
different(latDeltaA, latDeltaB, latEps) ||
different(lngDeltaA, lngDeltaB, lngEps);
}
private static boolean different(double a, double b, double epsilon) {
return Math.abs(a - b) > epsilon;
}
private static double LatitudeEpsilon(LatLngBounds a, LatLngBounds b) {
double sizeA = a.northeast.latitude - a.southwest.latitude; // something mod 180?
double sizeB = b.northeast.latitude - b.southwest.latitude; // something mod 180?
double size = Math.min(Math.abs(sizeA), Math.abs(sizeB));
return size / 2560;
}
private static double LongitudeEpsilon(LatLngBounds a, LatLngBounds b) {
double sizeA = a.northeast.longitude - a.southwest.longitude;
double sizeB = b.northeast.longitude - b.southwest.longitude;
double size = Math.min(Math.abs(sizeA), Math.abs(sizeB));
return size / 2560;
}
}

View File

@@ -0,0 +1,41 @@
package com.rnmaps.maps;
import android.content.Context;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.views.view.ReactViewGroup;
import com.rnmaps.fabric.event.OnPressEvent;
import java.util.Map;
public class MapCallout extends ReactViewGroup {
private boolean tooltip = false;
public int width;
public int height;
public MapCallout(Context context) {
super(context);
}
public void setTooltip(boolean tooltip) {
this.tooltip = tooltip;
}
public boolean getTooltip() {
return this.tooltip;
}
@Override
protected void onLayout(boolean changed,
int left, int top, int right, int bottom){
width = right - left;
height = bottom - top;
}
public static Map<String, Object> getExportedCustomBubblingEventTypeConstants() {
MapBuilder.Builder<String, Object> builder = MapBuilder.builder();
builder.put(OnPressEvent.EVENT_NAME, MapBuilder.of("registrationName", OnPressEvent.EVENT_NAME));
return builder.build();
}
}

View File

@@ -0,0 +1,56 @@
package com.rnmaps.maps;
import androidx.annotation.Nullable;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.uimanager.LayoutShadowNode;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.annotations.ReactProp;
import java.util.Map;
public class MapCalloutManager extends ViewGroupManager<MapCallout> {
@Override
public String getName() {
return "AIRMapCallout";
}
@Override
public MapCallout createViewInstance(ThemedReactContext context) {
return new MapCallout(context);
}
@ReactProp(name = "tooltip", defaultBoolean = false)
public void setTooltip(MapCallout view, boolean tooltip) {
view.setTooltip(tooltip);
}
@Override
@Nullable
public Map getExportedCustomDirectEventTypeConstants() {
return MapBuilder.of("onPress", MapBuilder.of("registrationName", "onPress"));
}
@Override
public LayoutShadowNode createShadowNodeInstance() {
// we use a custom shadow node that emits the width/height of the view
// after layout with the updateExtraData method. Without this, we can't generate
// a bitmap of the appropriate width/height of the rendered view.
return new SizeReportingShadowNode();
}
@Override
public void updateExtraData(MapCallout view, Object extraData) {
// This method is called from the shadow node with the width/height of the rendered
// marker view.
//noinspection unchecked
Map<String, Float> data = (Map<String, Float>) extraData;
float width = data.get("width");
float height = data.get("height");
view.width = (int) width;
view.height = (int) height;
}
}

View File

@@ -0,0 +1,126 @@
package com.rnmaps.maps;
import android.content.Context;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.common.MapBuilder;
import com.google.android.gms.maps.model.Circle;
import com.google.android.gms.maps.model.CircleOptions;
import com.google.android.gms.maps.model.LatLng;
import com.google.maps.android.collections.CircleManager;
import com.rnmaps.fabric.event.OnPressEvent;
import java.util.Map;
public class MapCircle extends MapFeature {
private CircleOptions circleOptions;
private Circle circle;
private LatLng center;
private double radius;
private int strokeColor;
private int fillColor;
private float strokeWidth;
private float zIndex;
private boolean tappable;
public MapCircle(Context context) {
super(context);
}
public void setCenter(LatLng center) {
this.center = center;
if (circle != null) {
circle.setCenter(this.center);
}
}
public static Map<String, Object> getExportedCustomBubblingEventTypeConstants() {
MapBuilder.Builder<String, Object> builder = MapBuilder.builder();
builder.put(OnPressEvent.EVENT_NAME, MapBuilder.of("registrationName", OnPressEvent.EVENT_NAME));
return builder.build();
}
public void setRadius(double radius) {
this.radius = radius;
if (circle != null) {
circle.setRadius(this.radius);
}
}
public void setFillColor(int color) {
this.fillColor = color;
if (circle != null) {
circle.setFillColor(color);
}
}
public void setStrokeColor(int color) {
this.strokeColor = color;
if (circle != null) {
circle.setStrokeColor(color);
}
}
public void setStrokeWidth(float width) {
this.strokeWidth = width;
if (circle != null) {
circle.setStrokeWidth(width);
}
}
public void setZIndex(float zIndex) {
this.zIndex = zIndex;
if (circle != null) {
circle.setZIndex(zIndex);
}
}
public void setTappable(boolean tappable) {
this.tappable = tappable;
if (circle != null) {
circle.setClickable(tappable);
}
}
public CircleOptions getCircleOptions() {
if (circleOptions == null) {
circleOptions = createCircleOptions();
}
return circleOptions;
}
private CircleOptions createCircleOptions() {
CircleOptions options = new CircleOptions();
options.center(center);
options.radius(radius);
options.fillColor(fillColor);
options.strokeColor(strokeColor);
options.strokeWidth(strokeWidth);
options.zIndex(zIndex);
return options;
}
@Override
public Object getFeature() {
return circle;
}
@Override
public void addToMap(Object collection) {
CircleManager.Collection circleCollection = (CircleManager.Collection) collection;
circle = circleCollection.addCircle(getCircleOptions());
}
@Override
public void removeFromMap(Object collection) {
CircleManager.Collection circleCollection = (CircleManager.Collection) collection;
circleCollection.remove(circle);
}
public void setCenter(ReadableMap center) {
setCenter(new LatLng(center.getDouble("latitude"), center.getDouble("longitude")));
}
}

View File

@@ -0,0 +1,67 @@
package com.rnmaps.maps;
import android.content.Context;
import android.graphics.Color;
import android.util.DisplayMetrics;
import android.view.WindowManager;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.google.android.gms.maps.model.LatLng;
public class MapCircleManager extends ViewGroupManager<MapCircle> {
private final DisplayMetrics metrics;
public MapCircleManager(ReactApplicationContext reactContext) {
super();
metrics = new DisplayMetrics();
((WindowManager) reactContext.getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay()
.getRealMetrics(metrics);
}
@Override
public String getName() {
return "AIRMapCircle";
}
@Override
public MapCircle createViewInstance(ThemedReactContext context) {
return new MapCircle(context);
}
@ReactProp(name = "center")
public void setCenter(MapCircle view, ReadableMap center) {
view.setCenter(new LatLng(center.getDouble("latitude"), center.getDouble("longitude")));
}
@ReactProp(name = "radius", defaultDouble = 0)
public void setRadius(MapCircle view, double radius) {
view.setRadius(radius);
}
@ReactProp(name = "strokeWidth", defaultFloat = 1f)
public void setStrokeWidth(MapCircle view, float widthInPoints) {
float widthInScreenPx = metrics.density * widthInPoints; // done for parity with iOS
view.setStrokeWidth(widthInScreenPx);
}
@ReactProp(name = "fillColor", defaultInt = Color.RED, customType = "Color")
public void setFillColor(MapCircle view, int color) {
view.setFillColor(color);
}
@ReactProp(name = "strokeColor", defaultInt = Color.RED, customType = "Color")
public void setStrokeColor(MapCircle view, int color) {
view.setStrokeColor(color);
}
@ReactProp(name = "zIndex", defaultFloat = 1.0f)
public void setZIndex(MapCircle view, float zIndex) {
view.setZIndex(zIndex);
}
}

View File

@@ -0,0 +1,17 @@
package com.rnmaps.maps;
import android.content.Context;
import com.facebook.react.views.view.ReactViewGroup;
public abstract class MapFeature extends ReactViewGroup {
public MapFeature(Context context) {
super(context);
}
public abstract void addToMap(Object mapOrCollection);
public abstract void removeFromMap(Object mapOrCollection);
public abstract Object getFeature();
}

View File

@@ -0,0 +1,343 @@
package com.rnmaps.maps;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Shader;
import android.util.Log;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.Tile;
import com.google.android.gms.maps.model.TileOverlay;
import com.google.android.gms.maps.model.TileOverlayOptions;
import com.google.android.gms.maps.model.TileProvider;
import com.google.maps.android.SphericalUtil;
import com.google.maps.android.geometry.Point;
import com.google.maps.android.projection.SphericalMercatorProjection;
import java.io.ByteArrayOutputStream;
import java.util.List;
/**
* Tile overlay used to display a colored polyline as a replacement for the
* non-existence of gradient polylines for google maps. Implementation borrowed
* from Dagothig/ColoredPolylineOverlay
* (https://gist.github.com/Dagothig/5f9cf0a4a7a42901a7b2)
*/
public class MapGradientPolyline extends MapFeature {
private List<LatLng> points;
private int[] colors;
private float zIndex;
private float width;
private GoogleMap map;
private TileOverlay tileOverlay;
protected final Context context;
public MapGradientPolyline(Context context) {
super(context);
this.context = context;
}
public void setCoordinates(List<LatLng> coordinates) {
this.points = coordinates;
if (tileOverlay != null) {
tileOverlay.remove();
}
if (map != null) {
tileOverlay = map.addTileOverlay(createTileOverlayOptions());
}
}
public void setStrokeColors(int[] colors) {
this.colors = colors;
if (tileOverlay != null) {
tileOverlay.remove();
}
if (map != null) {
tileOverlay = map.addTileOverlay(createTileOverlayOptions());
}
}
public void setZIndex(float zIndex) {
this.zIndex = zIndex;
if (tileOverlay != null) {
tileOverlay.setZIndex(zIndex);
}
}
public void setWidth(float width) {
this.width = width;
if (tileOverlay != null) {
tileOverlay.remove();
}
if (map != null) {
tileOverlay = map.addTileOverlay(createTileOverlayOptions());
}
}
private TileOverlayOptions createTileOverlayOptions() {
TileOverlayOptions options = new TileOverlayOptions();
options.zIndex(zIndex);
AirMapGradientPolylineProvider tileProvider = new AirMapGradientPolylineProvider(context, points, colors, width);
options.tileProvider(tileProvider);
return options;
}
public static int interpolateColor(int[] colors, float proportion) {
int rTotal = 0, gTotal = 0, bTotal = 0;
// We correct the ratio to colors.length - 1 so that
// for i == colors.length - 1 and p == 1, then the final ratio is 1 (see below)
float p = proportion * (colors.length - 1);
for (int i = 0; i < colors.length; i++) {
// The ratio mostly resides on the 1 - Math.abs(p - i) calculation :
// Since for p == i, then the ratio is 1 and for p == i + 1 or p == i -1, then the ratio is 0
// This calculation works BECAUSE p lies within [0, length - 1] and i lies within [0, length - 1] as well
float iRatio = Math.max(1 - Math.abs(p - i), 0.0f);
rTotal += (int) (Color.red(colors[i]) * iRatio);
gTotal += (int) (Color.green(colors[i]) * iRatio);
bTotal += (int) (Color.blue(colors[i]) * iRatio);
}
return Color.rgb(rTotal, gTotal, bTotal);
}
public class AirMapGradientPolylineProvider implements TileProvider {
public static final int BASE_TILE_SIZE = 256;
protected final List<LatLng> points;
protected final int[] colors;
protected final float width;
protected final float density;
protected final int tileDimension;
protected final SphericalMercatorProjection projection;
// Caching calculation-related stuff
protected LatLng[] trailLatLngs;
protected Point[] projectedPts;
protected Point[] projectedPtMids;
public AirMapGradientPolylineProvider(Context context, List<LatLng> points, int[] colors,
float width) {
super();
this.points = points;
this.colors = colors;
this.width = width;
density = context.getResources().getDisplayMetrics().density;
tileDimension = (int) (BASE_TILE_SIZE * density);
projection = new SphericalMercatorProjection(BASE_TILE_SIZE);
calculatePoints();
}
public void calculatePoints() {
trailLatLngs = new LatLng[points.size()];
projectedPts = new Point[points.size()];
projectedPtMids = new Point[Math.max(points.size() - 1, 0)];
for (int i = 0; i < points.size(); i++) {
LatLng latLng = points.get(i);
trailLatLngs[i] = latLng;
projectedPts[i] = projection.toPoint(latLng);
// Mids
if (i > 0) {
LatLng previousLatLng = points.get(i - 1);
LatLng latLngMid = SphericalUtil.interpolate(previousLatLng, latLng, 0.5);
projectedPtMids[i - 1] = projection.toPoint(latLngMid);
}
}
}
@Override
public Tile getTile(int x, int y, int zoom) {
// Because getTile can be called asynchronously by multiple threads, none of the info we keep in the class will be modified
// (getTile is essentially side-effect-less) :
// Instead, we create the bitmap, the canvas and the paints specifically for the call to getTile
Bitmap bitmap = Bitmap.createBitmap(tileDimension, tileDimension, Bitmap.Config.ARGB_8888);
// Normally, instead of the later calls for drawing being offset, we would offset them using scale() and translate() right here
// However, there seems to be funky issues related to float imprecisions that happen at large scales when using this method, so instead
// The points are offset properly when drawing
Canvas canvas = new Canvas(bitmap);
Matrix shaderMat = new Matrix();
Paint gradientPaint = new Paint();
gradientPaint.setStyle(Paint.Style.STROKE);
gradientPaint.setStrokeWidth(width);
gradientPaint.setStrokeCap(Paint.Cap.BUTT);
gradientPaint.setStrokeJoin(Paint.Join.ROUND);
gradientPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
gradientPaint.setShader(new LinearGradient(0, 0, 1, 0, colors, null,
Shader.TileMode.CLAMP));
gradientPaint.getShader().setLocalMatrix(shaderMat);
Paint colorPaint = new Paint();
colorPaint.setStyle(Paint.Style.STROKE);
colorPaint.setStrokeWidth(width);
colorPaint.setStrokeCap(Paint.Cap.BUTT);
colorPaint.setStrokeJoin(Paint.Join.ROUND);
colorPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
// See https://developers.google.com/maps/documentation/android/views#zoom for handy info regarding what zoom is
float scale = (float) (Math.pow(2, zoom) * density);
renderTrail(canvas, shaderMat, gradientPaint, colorPaint, scale, x, y);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos);
return new Tile(tileDimension, tileDimension, baos.toByteArray());
}
public void renderTrail(Canvas canvas, Matrix shaderMat, Paint gradientPaint, Paint colorPaint,
float scale, int x, int y) {
MutPoint pt1 = new MutPoint(), pt2 = new MutPoint(), pt3 = new MutPoint(), pt1mid2 =
new MutPoint(), pt2mid3 = new MutPoint();
if (points.size() == 1) {
pt1.set(projectedPts[0], scale, x, y, tileDimension);
colorPaint.setStyle(Paint.Style.FILL);
colorPaint.setColor(interpolateColor(colors, 1));
canvas
.drawCircle((float) pt1.x, (float) pt1.y, colorPaint.getStrokeWidth() / 2f, colorPaint);
colorPaint.setStyle(Paint.Style.STROKE);
return;
}
if (points.size() == 2) {
pt1.set(projectedPts[0], scale, x, y, tileDimension);
pt2.set(projectedPts[1], scale, x, y, tileDimension);
drawLine(canvas, colorPaint, pt1, pt2, 0);
return;
}
for (int i = 2; i < points.size(); i++) {
pt1.set(projectedPts[i - 2], scale, x, y, tileDimension);
pt2.set(projectedPts[i - 1], scale, x, y, tileDimension);
pt3.set(projectedPts[i], scale, x, y, tileDimension);
// Because we want to split the lines in two to ease over the corners, we need the middle points
pt1mid2.set(projectedPtMids[i - 2], scale, x, y, tileDimension);
pt2mid3.set(projectedPtMids[i - 1], scale, x, y, tileDimension);
float interp1 = ((float)i - 2) / points.size();
float interp2 = ((float)i - 1) / points.size();
float interp1to2 = (interp1 + interp2) / 2;
Log.d("AirMapGradientPolyline", String.valueOf(interp1to2));
// Circle for the corner (removes the weird empty corners that occur otherwise)
colorPaint.setStyle(Paint.Style.FILL);
colorPaint.setColor(interpolateColor(colors, interp1to2));
canvas
.drawCircle((float) pt2.x, (float) pt2.y, colorPaint.getStrokeWidth() / 2f, colorPaint);
colorPaint.setStyle(Paint.Style.STROKE);
// Corner
// Note that since for the very first point and the very last point we don't split it in two, we used them instead.
drawLine(canvas, shaderMat, gradientPaint, colorPaint, i - 2 == 0 ? pt1 : pt1mid2,
pt2, interp1, interp1to2);
drawLine(canvas, shaderMat, gradientPaint, colorPaint, pt2, i == points.size() - 1 ?
pt3 : pt2mid3, interp1to2, interp2);
}
}
/**
* Note: it is assumed the shader is 0, 0, 1, 0 (horizontal) so that it lines up with the rotation
* (rotations are usually setup so that the angle 0 points right)
*/
public void drawLine(Canvas canvas, Matrix shaderMat, Paint gradientPaint, Paint colorPaint,
MutPoint pt1, MutPoint pt2, float ratio1, float ratio2) {
// Degenerate case: both ratios are the same; we just handle it using the colorPaint (handling it using the shader is just messy and ineffective)
if (ratio1 == ratio2) {
drawLine(canvas, colorPaint, pt1, pt2, ratio1);
return;
}
shaderMat.reset();
// PS: don't ask me why this specfic orders for calls works but other orders will fuck up
// Since every call is pre, this is essentially ordered as (or my understanding is that it is):
// ratio translate -> ratio scale -> scale to pt length -> translate to pt start -> rotate
// (my initial intuition was to use only post calls and to order as above, but it resulted in odd corruptions)
// Setup based on points:
// We translate the shader so that it is based on the first point, rotated towards the second and since the length of the
// gradient is 1, then scaling to the length of the distance between the points makes it exactly as long as needed
shaderMat.preRotate((float) Math.toDegrees(Math.atan2(pt2.y - pt1.y, pt2.x - pt1.x)),
(float) pt1.x, (float) pt1.y);
shaderMat.preTranslate((float) pt1.x, (float) pt1.y);
float scale = (float) Math.sqrt(Math.pow(pt2.x - pt1.x, 2) + Math.pow(pt2.y - pt1.y, 2));
shaderMat.preScale(scale, scale);
// Setup based on ratio
// By basing the shader to the first ratio, we ensure that the start of the gradient corresponds to it
// The inverse scaling of the shader means that it takes the full length of the call to go to the second ratio
// For instance; if d(ratio1, ratio2) is 0.5, then the shader needs to be twice as long so that an entire call (1)
// Results in only half of the gradient being used
shaderMat.preScale(1f / (ratio2 - ratio1), 1f / (ratio2 - ratio1));
shaderMat.preTranslate(-ratio1, 0);
gradientPaint.getShader().setLocalMatrix(shaderMat);
canvas.drawLine(
(float) pt1.x,
(float) pt1.y,
(float) pt2.x,
(float) pt2.y,
gradientPaint
);
}
public void drawLine(Canvas canvas, Paint colorPaint, MutPoint pt1, MutPoint pt2, float ratio) {
colorPaint.setColor(interpolateColor(colors, ratio));
canvas.drawLine(
(float) pt1.x,
(float) pt1.y,
(float) pt2.x,
(float) pt2.y,
colorPaint
);
}
}
@Override
public Object getFeature() {
return tileOverlay;
}
@Override
public void addToMap(Object map) {
this.map = (GoogleMap) map;
this.tileOverlay = this.map.addTileOverlay(createTileOverlayOptions());
}
@Override
public void removeFromMap(Object map) {
tileOverlay.remove();
}
public static class MutPoint {
public double x, y;
public MutPoint set(Point point, float scale, int x, int y, int tileDimension) {
this.x = point.x * scale - x * tileDimension;
this.y = point.y * scale - y * tileDimension;
return this;
}
}
}

View File

@@ -0,0 +1,83 @@
package com.rnmaps.maps;
import android.content.Context;
import android.util.DisplayMetrics;
import android.view.WindowManager;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.google.android.gms.maps.model.LatLng;
import java.util.List;
import java.util.ArrayList;
public class MapGradientPolylineManager extends ViewGroupManager<MapGradientPolyline> {
private final DisplayMetrics metrics;
public MapGradientPolylineManager(ReactApplicationContext reactContext) {
super();
metrics = new DisplayMetrics();
((WindowManager) reactContext.getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay()
.getRealMetrics(metrics);
}
@Override
public String getName() {
return "AIRMapGradientPolyline";
}
@Override
public MapGradientPolyline createViewInstance(ThemedReactContext context) {
return new MapGradientPolyline(context);
}
@ReactProp(name = "coordinates")
public void setCoordinates(MapGradientPolyline view, ReadableArray coordinates) {
List<LatLng> p = new ArrayList<LatLng>();
for (int i = 0; i < coordinates.size(); i++) {
ReadableMap point = coordinates.getMap(i);
LatLng latLng = new LatLng(point.getDouble("latitude"), point.getDouble("longitude"));
p.add(latLng);
}
view.setCoordinates(p);
}
@ReactProp(name = "strokeColors", customType = "ColorArray")
public void setStrokeColors(MapGradientPolyline view, ReadableArray colors) {
if (colors != null) {
if (colors.size() == 0) {
int[] colorValues = {0,0};
view.setStrokeColors(colorValues);
} else if (colors.size() == 1) {
int[] colorValues = { colors.getInt(0), colors.getInt(0) };
view.setStrokeColors(colorValues);
} else {
int[] colorValues = new int[colors.size()];
for (int i = 0; i < colors.size(); i++) {
colorValues[i] = colors.getInt(i);
}
view.setStrokeColors(colorValues);
}
} else {
int[] colorValues = {0,0};
view.setStrokeColors(colorValues);
}
}
@ReactProp(name = "zIndex", defaultFloat = 1.0f)
public void setZIndex(MapGradientPolyline view, float zIndex) {
view.setZIndex(zIndex);
}
@ReactProp(name = "strokeWidth", defaultFloat = 1f)
public void setStrokeWidth(MapGradientPolyline view, float widthInPoints) {
float widthInScreenPx = metrics.density * widthInPoints; // done for parity with iOS
view.setWidth(widthInScreenPx);
}
}

View File

@@ -0,0 +1,113 @@
package com.rnmaps.maps;
import android.content.Context;
import android.util.Log;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.model.TileOverlay;
import com.google.android.gms.maps.model.TileOverlayOptions;
import com.google.maps.android.heatmaps.HeatmapTileProvider;
import com.google.maps.android.heatmaps.WeightedLatLng;
import com.google.maps.android.heatmaps.Gradient;
import java.util.Arrays;
import java.util.List;
public class MapHeatmap extends MapFeature {
private TileOverlayOptions heatmapOptions;
private TileOverlay heatmap;
private HeatmapTileProvider heatmapTileProvider;
private List<WeightedLatLng> points;
private Gradient gradient;
private Double opacity;
private Integer radius;
public MapHeatmap(Context context) {
super(context);
}
public void setPoints(WeightedLatLng[] points) {
this.points = Arrays.asList(points);
if (heatmapTileProvider != null) {
heatmapTileProvider.setWeightedData(this.points);
}
if (heatmap != null) {
heatmap.clearTileCache();
}
}
public void setGradient(Gradient gradient) {
this.gradient = gradient;
if (heatmapTileProvider != null) {
heatmapTileProvider.setGradient(gradient);
}
if (heatmap != null) {
heatmap.clearTileCache();
}
}
public void setOpacity(double opacity) {
this.opacity = opacity;
if (heatmapTileProvider != null) {
heatmapTileProvider.setOpacity(opacity);
}
if (heatmap != null) {
heatmap.clearTileCache();
}
}
public void setRadius(int radius) {
this.radius = radius;
if (heatmapTileProvider != null) {
heatmapTileProvider.setRadius(radius);
}
if (heatmap != null) {
heatmap.clearTileCache();
}
}
public TileOverlayOptions getHeatmapOptions() {
if (heatmapOptions == null) {
heatmapOptions = createHeatmapOptions();
}
return heatmapOptions;
}
private TileOverlayOptions createHeatmapOptions() {
TileOverlayOptions options = new TileOverlayOptions();
if (heatmapTileProvider == null) {
HeatmapTileProvider.Builder builder =
new HeatmapTileProvider.Builder().weightedData(this.points);
if (radius != null) {
builder.radius(radius);
}
if (opacity != null) {
builder.opacity(opacity);
}
if (gradient != null) {
builder.gradient(gradient);
}
heatmapTileProvider = builder.build();
}
options.tileProvider(heatmapTileProvider);
return options;
}
@Override
public Object getFeature() {
return heatmap;
}
@Override
public void addToMap(Object map) {
heatmap = ((GoogleMap) map).addTileOverlay(getHeatmapOptions());
}
@Override
public void removeFromMap(Object map) {
heatmap.remove();
}
}

View File

@@ -0,0 +1,74 @@
package com.rnmaps.maps;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
import com.google.android.gms.maps.model.LatLng;
import com.google.maps.android.heatmaps.WeightedLatLng;
import com.google.maps.android.heatmaps.Gradient;
public class MapHeatmapManager extends ViewGroupManager<MapHeatmap> {
@Override
public String getName() {
return "AIRMapHeatmap";
}
@Override
public MapHeatmap createViewInstance(ThemedReactContext context) {
return new MapHeatmap(context);
}
@ReactProp(name = "points")
public void setPoints(MapHeatmap view, ReadableArray points) {
WeightedLatLng[] p = new WeightedLatLng[points.size()];
for (int i = 0; i < points.size(); i++) {
ReadableMap point = points.getMap(i);
WeightedLatLng weightedLatLng;
LatLng latLng = new LatLng(point.getDouble("latitude"), point.getDouble("longitude"));
if (point.hasKey("weight")) {
weightedLatLng = new WeightedLatLng(latLng, point.getDouble("weight"));
} else {
weightedLatLng = new WeightedLatLng(latLng);
}
p[i] = weightedLatLng;
}
view.setPoints(p);
}
@ReactProp(name = "gradient")
public void setGradient(MapHeatmap view, ReadableMap gradient) {
ReadableArray srcColors = gradient.getArray("colors");
int[] colors = new int[srcColors.size()];
for (int i = 0; i < srcColors.size(); i++) {
colors[i] = srcColors.getInt(i);
}
ReadableArray srcStartPoints = gradient.getArray("startPoints");
float[] startPoints = new float[srcStartPoints.size()];
for (int i = 0; i < srcStartPoints.size(); i++) {
startPoints[i] = (float)srcStartPoints.getDouble(i);
}
if (gradient.hasKey("colorMapSize")) {
int colorMapSize = gradient.getInt("colorMapSize");
view.setGradient(new Gradient(colors, startPoints, colorMapSize));
} else {
view.setGradient(new Gradient(colors, startPoints));
}
}
@ReactProp(name = "opacity")
public void setOpacity(MapHeatmap view, double opacity) {
view.setOpacity(opacity);
}
@ReactProp(name = "radius")
public void setRadius(MapHeatmap view, int radius) {
view.setRadius(radius);
}
}

View File

@@ -0,0 +1,151 @@
package com.rnmaps.maps;
import android.content.Context;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.model.Tile;
import com.google.android.gms.maps.model.TileOverlay;
import com.google.android.gms.maps.model.TileOverlayOptions;
import com.google.android.gms.maps.model.TileProvider;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class MapLocalTile extends MapFeature {
class AIRMapLocalTileProvider implements TileProvider {
private static final int BUFFER_SIZE = 16 * 1024;
private int tileSize;
private String pathTemplate;
private final boolean useAssets;
public AIRMapLocalTileProvider(int tileSizet, String pathTemplate, boolean useAssets) {
this.tileSize = tileSizet;
this.pathTemplate = pathTemplate;
this.useAssets = useAssets;
}
@Override
public Tile getTile(int x, int y, int zoom) {
byte[] image = readTileImage(x, y, zoom);
return image == null ? TileProvider.NO_TILE : new Tile(this.tileSize, this.tileSize, image);
}
public void setPathTemplate(String pathTemplate) {
this.pathTemplate = pathTemplate;
}
public void setTileSize(int tileSize) {
this.tileSize = tileSize;
}
private byte[] readTileImage(int x, int y, int zoom) {
InputStream in = null;
ByteArrayOutputStream buffer = null;
String tileFilename = getTileFilename(x, y, zoom);
try {
in = useAssets ? getContext().getAssets().open(tileFilename) : new FileInputStream(tileFilename);
buffer = new ByteArrayOutputStream();
int nRead;
byte[] data = new byte[BUFFER_SIZE];
while ((nRead = in.read(data, 0, BUFFER_SIZE)) != -1) {
buffer.write(data, 0, nRead);
}
buffer.flush();
return buffer.toByteArray();
} catch (IOException | OutOfMemoryError e) {
e.printStackTrace();
return null;
} finally {
if (in != null) try { in.close(); } catch (Exception ignored) {}
if (buffer != null) try { buffer.close(); } catch (Exception ignored) {}
}
}
private String getTileFilename(int x, int y, int zoom) {
String s = this.pathTemplate
.replace("{x}", Integer.toString(x))
.replace("{y}", Integer.toString(y))
.replace("{z}", Integer.toString(zoom));
return s;
}
}
private TileOverlayOptions tileOverlayOptions;
private TileOverlay tileOverlay;
private MapLocalTile.AIRMapLocalTileProvider tileProvider;
private String pathTemplate;
private float tileSize;
private float zIndex;
private boolean useAssets;
public MapLocalTile(Context context) {
super(context);
}
public void setPathTemplate(String pathTemplate) {
this.pathTemplate = pathTemplate;
if (tileProvider != null) {
tileProvider.setPathTemplate(pathTemplate);
}
if (tileOverlay != null) {
tileOverlay.clearTileCache();
}
}
public void setZIndex(float zIndex) {
this.zIndex = zIndex;
if (tileOverlay != null) {
tileOverlay.setZIndex(zIndex);
}
}
public void setTileSize(float tileSize) {
this.tileSize = tileSize;
if (tileProvider != null) {
tileProvider.setTileSize((int)tileSize);
}
}
public void setUseAssets(boolean useAssets) {
this.useAssets = useAssets;
}
public TileOverlayOptions getTileOverlayOptions() {
if (tileOverlayOptions == null) {
tileOverlayOptions = createTileOverlayOptions();
}
return tileOverlayOptions;
}
private TileOverlayOptions createTileOverlayOptions() {
TileOverlayOptions options = new TileOverlayOptions();
options.zIndex(zIndex);
this.tileProvider = new MapLocalTile.AIRMapLocalTileProvider((int)this.tileSize, this.pathTemplate, this.useAssets);
options.tileProvider(this.tileProvider);
return options;
}
@Override
public Object getFeature() {
return tileOverlay;
}
@Override
public void addToMap(Object map) {
this.tileOverlay = ((GoogleMap) map).addTileOverlay(getTileOverlayOptions());
}
@Override
public void removeFromMap(Object map) {
tileOverlay.remove();
}
}

View File

@@ -0,0 +1,54 @@
package com.rnmaps.maps;
import android.content.Context;
import android.util.DisplayMetrics;
import android.view.WindowManager;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.annotations.ReactProp;
/**
* Created by zavadpe on 30/11/2017.
*/
public class MapLocalTileManager extends ViewGroupManager<MapLocalTile> {
public MapLocalTileManager(ReactApplicationContext reactContext) {
super();
DisplayMetrics metrics = new DisplayMetrics();
((WindowManager) reactContext.getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay()
.getRealMetrics(metrics);
}
@Override
public String getName() {
return "AIRMapLocalTile";
}
@Override
public MapLocalTile createViewInstance(ThemedReactContext context) {
return new MapLocalTile(context);
}
@ReactProp(name = "pathTemplate")
public void setPathTemplate(MapLocalTile view, String pathTemplate) {
view.setPathTemplate(pathTemplate);
}
@ReactProp(name = "tileSize", defaultFloat = 256f)
public void setTileSize(MapLocalTile view, float tileSize) {
view.setTileSize(tileSize);
}
@ReactProp(name = "zIndex", defaultFloat = -1.0f)
public void setZIndex(MapLocalTile view, float zIndex) {
view.setZIndex(zIndex);
}
@ReactProp(name = "useAssets", defaultBoolean = false)
public void setUseAssets(MapLocalTile view, boolean useAssets) {
view.setUseAssets(useAssets);
}
}

View File

@@ -0,0 +1,504 @@
package com.rnmaps.maps;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.facebook.react.R;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.uimanager.LayoutShadowNode;
import com.facebook.react.uimanager.ReactStylesDiffMap;
import com.facebook.react.uimanager.StateWrapper;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.uimanager.events.RCTEventEmitter;
import com.google.android.gms.location.Priority;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.GoogleMapOptions;
import com.google.android.gms.maps.MapsInitializer;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
import java.util.Map;
public class MapManager extends ViewGroupManager<MapView> {
private static final String REACT_CLASS = "AIRMap";
private final Map<String, Integer> MAP_TYPES = MapBuilder.of(
"standard", GoogleMap.MAP_TYPE_NORMAL,
"satellite", GoogleMap.MAP_TYPE_SATELLITE,
"hybrid", GoogleMap.MAP_TYPE_HYBRID,
"terrain", GoogleMap.MAP_TYPE_TERRAIN,
"none", GoogleMap.MAP_TYPE_NONE
);
public static final Map<String, Integer> MY_LOCATION_PRIORITY = MapBuilder.of(
"balanced", Priority.PRIORITY_BALANCED_POWER_ACCURACY,
"high", Priority.PRIORITY_HIGH_ACCURACY,
"low", Priority.PRIORITY_LOW_POWER,
"passive", Priority.PRIORITY_PASSIVE
);
private final ReactApplicationContext appContext;
private MapMarkerManager markerManager;
protected GoogleMapOptions googleMapOptions;
protected MapsInitializer.Renderer renderer;
public MapManager(ReactApplicationContext context) {
this.appContext = context;
}
public MapMarkerManager getMarkerManager() {
return this.markerManager;
}
public void setMarkerManager(MapMarkerManager markerManager) {
this.markerManager = markerManager;
}
@Override
public String getName() {
return REACT_CLASS;
}
@Override
protected MapView createViewInstance(@NonNull ThemedReactContext context) {
return new MapView(context, this.appContext, this, googleMapOptions);
}
@Override
protected MapView createViewInstance(int reactTag, @NonNull ThemedReactContext reactContext, @Nullable ReactStylesDiffMap initialProps, @Nullable StateWrapper stateWrapper) {
this.googleMapOptions = new GoogleMapOptions();
if (initialProps != null) {
if (initialProps.getString("googleMapId") != null) {
googleMapOptions.mapId(initialProps.getString("googleMapId"));
}
if (initialProps.hasKey("liteMode")) {
googleMapOptions.liteMode(initialProps.getBoolean("liteMode", false));
}
if (initialProps.hasKey("initialCamera")) {
CameraPosition position = MapView.cameraPositionFromMap(initialProps.getMap("initialCamera"));
if (position != null) {
googleMapOptions.camera(position);
}
} else if (initialProps.hasKey("camera")) {
CameraPosition position = MapView.cameraPositionFromMap(initialProps.getMap("camera"));
if (position != null) {
googleMapOptions.camera(position);
}
}
if (initialProps.hasKey("googleRenderer") && "LEGACY".equals(initialProps.getString("googleRenderer"))) {
renderer = MapsInitializer.Renderer.LEGACY;
} else {
renderer = MapsInitializer.Renderer.LATEST;
}
}
return super.createViewInstance(reactTag, reactContext, initialProps, stateWrapper);
}
private void emitMapError(ThemedReactContext context, String message, String type) {
WritableMap error = Arguments.createMap();
error.putString("message", message);
error.putString("type", type);
context
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit("onError", error);
}
@ReactProp(name = "region")
public void setRegion(MapView view, ReadableMap region) {
view.setRegion(region);
}
@ReactProp(name = "googleRenderer")
public void setGoogleRenderer(MapView view, @Nullable String googleRenderer) {
// do nothing, passed as part of the InitialProps
}
@ReactProp(name = "liteMode", defaultBoolean = false)
public void setLiteMode(MapView view, boolean liteMode) {
googleMapOptions.liteMode(liteMode);
}
@ReactProp(name = "googleMapId")
public void setGoogleMapId(MapView view, @Nullable String googleMapId) {
if (googleMapId != null) {
googleMapOptions.mapId(googleMapId);
}
}
@ReactProp(name = "initialRegion")
public void setInitialRegion(MapView view, ReadableMap initialRegion) {
view.setInitialRegion(initialRegion);
}
@ReactProp(name = "camera")
public void setCamera(MapView view, ReadableMap camera) {
view.setCamera(camera);
}
@ReactProp(name = "initialCamera")
public void setInitialCamera(MapView view, ReadableMap initialCamera) {
view.setInitialCamera(initialCamera);
}
@ReactProp(name = "mapType")
public void setMapType(MapView view, @Nullable String mapType) {
int typeId = MAP_TYPES.get(mapType);
view.map.setMapType(typeId);
}
@ReactProp(name = "customMapStyleString")
public void setMapStyle(MapView view, @Nullable String customMapStyleString) {
view.setMapStyle(customMapStyleString);
}
@ReactProp(name = "mapPadding")
public void setMapPadding(MapView view, @Nullable ReadableMap padding) {
int left = 0;
int top = 0;
int right = 0;
int bottom = 0;
double density = (double) view.getResources().getDisplayMetrics().density;
if (padding != null) {
if (padding.hasKey("left")) {
left = (int) (padding.getDouble("left") * density);
}
if (padding.hasKey("top")) {
top = (int) (padding.getDouble("top") * density);
}
if (padding.hasKey("right")) {
right = (int) (padding.getDouble("right") * density);
}
if (padding.hasKey("bottom")) {
bottom = (int) (padding.getDouble("bottom") * density);
}
}
view.applyBaseMapPadding(left, top, right, bottom);
view.map.setPadding(left, top, right, bottom);
}
@ReactProp(name = "showsUserLocation", defaultBoolean = false)
public void setShowsUserLocation(MapView view, boolean showUserLocation) {
view.setShowsUserLocation(showUserLocation);
}
@ReactProp(name = "userLocationPriority")
public void setUserLocationPriority(MapView view, @Nullable String accuracy) {
view.setUserLocationPriority(MY_LOCATION_PRIORITY.get(accuracy));
}
@ReactProp(name = "userLocationUpdateInterval", defaultInt = 5000)
public void setUserLocationUpdateInterval(MapView view, int updateInterval) {
view.setUserLocationUpdateInterval(updateInterval);
}
@ReactProp(name = "userLocationFastestInterval", defaultInt = 5000)
public void setUserLocationFastestInterval(MapView view, int fastestInterval) {
view.setUserLocationFastestInterval(fastestInterval);
}
@ReactProp(name = "showsMyLocationButton", defaultBoolean = true)
public void setShowsMyLocationButton(MapView view, boolean showMyLocationButton) {
view.setShowsMyLocationButton(showMyLocationButton);
}
@ReactProp(name = "toolbarEnabled", defaultBoolean = true)
public void setToolbarEnabled(MapView view, boolean toolbarEnabled) {
view.setToolbarEnabled(toolbarEnabled);
}
// This is a private prop to improve performance of panDrag by disabling it when the callback
// is not set
@ReactProp(name = "handlePanDrag", defaultBoolean = false)
public void setHandlePanDrag(MapView view, boolean handlePanDrag) {
view.setHandlePanDrag(handlePanDrag);
}
@ReactProp(name = "showsTraffic", defaultBoolean = false)
public void setShowTraffic(MapView view, boolean showTraffic) {
view.map.setTrafficEnabled(showTraffic);
}
@ReactProp(name = "showsBuildings", defaultBoolean = false)
public void setShowBuildings(MapView view, boolean showBuildings) {
view.setShowBuildings(showBuildings);
}
@ReactProp(name = "showsIndoors", defaultBoolean = false)
public void setShowIndoors(MapView view, boolean showIndoors) {
view.setShowIndoors(showIndoors);
}
@ReactProp(name = "showsIndoorLevelPicker", defaultBoolean = false)
public void setShowsIndoorLevelPicker(MapView view, boolean showsIndoorLevelPicker) {
view.setShowsIndoorLevelPicker(showsIndoorLevelPicker);
}
@ReactProp(name = "showsCompass", defaultBoolean = false)
public void setShowsCompass(MapView view, boolean showsCompass) {
view.setShowsCompass(showsCompass);
}
@ReactProp(name = "scrollEnabled", defaultBoolean = false)
public void setScrollEnabled(MapView view, boolean scrollEnabled) {
view.setScrollEnabled(scrollEnabled);
}
@ReactProp(name = "zoomEnabled", defaultBoolean = false)
public void setZoomEnabled(MapView view, boolean zoomEnabled) {
view.setZoomEnabled(zoomEnabled);
}
@ReactProp(name = "zoomControlEnabled", defaultBoolean = true)
public void setZoomControlEnabled(MapView view, boolean zoomControlEnabled) {
view.setZoomControlEnabled(zoomControlEnabled);
}
@ReactProp(name = "rotateEnabled", defaultBoolean = false)
public void setRotateEnabled(MapView view, boolean rotateEnabled) {
view.setRotateEnabled(rotateEnabled);
}
@ReactProp(name = "scrollDuringRotateOrZoomEnabled", defaultBoolean = true)
public void setScrollDuringRotateOrZoomEnabled(MapView view, boolean scrollDuringRotateOrZoomEnabled) {
view.setScrollDuringRotateOrZoomEnabled(scrollDuringRotateOrZoomEnabled);
}
@ReactProp(name = "cacheEnabled", defaultBoolean = false)
public void setCacheEnabled(MapView view, boolean cacheEnabled) {
view.setCacheEnabled(cacheEnabled);
}
@ReactProp(name = "poiClickEnabled", defaultBoolean = true)
public void setPoiClickEnabled(MapView view, boolean poiClickEnabled) {
view.setPoiClickEnabled(poiClickEnabled);
}
@ReactProp(name = "loadingEnabled", defaultBoolean = false)
public void setLoadingEnabled(MapView view, boolean loadingEnabled) {
view.setLoadingEnabled(loadingEnabled);
}
@ReactProp(name = "moveOnMarkerPress", defaultBoolean = true)
public void setMoveOnMarkerPress(MapView view, boolean moveOnPress) {
view.setMoveOnMarkerPress(moveOnPress);
}
@ReactProp(name = "loadingBackgroundColor", customType = "Color")
public void setLoadingBackgroundColor(MapView view, @Nullable Integer loadingBackgroundColor) {
view.setLoadingBackgroundColor(loadingBackgroundColor);
}
@ReactProp(name = "loadingIndicatorColor", customType = "Color")
public void setLoadingIndicatorColor(MapView view, @Nullable Integer loadingIndicatorColor) {
view.setLoadingIndicatorColor(loadingIndicatorColor);
}
@ReactProp(name = "pitchEnabled", defaultBoolean = false)
public void setPitchEnabled(MapView view, boolean pitchEnabled) {
view.setPitchEnabled(pitchEnabled);
}
@ReactProp(name = "minZoomLevel")
public void setMinZoomLevel(MapView view, float minZoomLevel) {
view.setMinZoomLevel(minZoomLevel);
}
@ReactProp(name = "maxZoomLevel")
public void setMaxZoomLevel(MapView view, float maxZoomLevel) {
view.setMaxZoomLevel(maxZoomLevel);
}
@ReactProp(name = "kmlSrc")
public void setKmlSrc(MapView view, String kmlUrl) {
if (kmlUrl != null) {
view.setKmlSrc(kmlUrl);
}
}
@ReactProp(name = "accessibilityLabel")
public void setAccessibilityLabel(MapView view, @Nullable String accessibilityLabel) {
view.setTag(R.id.accessibility_label, accessibilityLabel);
}
@Override
public void receiveCommand(@NonNull MapView view, String commandId, @Nullable ReadableArray args) {
int duration;
double lat;
double lng;
double lngDelta;
double latDelta;
ReadableMap region;
ReadableMap camera;
switch (commandId) {
case "setCamera":
if (args == null) {
break;
}
camera = args.getMap(0);
view.animateToCamera(camera, 0);
break;
case "animateCamera":
if (args == null) {
break;
}
camera = args.getMap(0);
duration = args.getInt(1);
view.animateToCamera(camera, duration);
break;
case "animateToRegion":
if (args == null) {
break;
}
region = args.getMap(0);
duration = args.getInt(1);
lng = region.getDouble("longitude");
lat = region.getDouble("latitude");
lngDelta = region.getDouble("longitudeDelta");
latDelta = region.getDouble("latitudeDelta");
LatLngBounds bounds = new LatLngBounds(
new LatLng(lat - latDelta / 2, lng - lngDelta / 2), // southwest
new LatLng(lat + latDelta / 2, lng + lngDelta / 2) // northeast
);
view.animateToRegion(bounds, duration);
break;
case "fitToElements":
if (args == null) {
break;
}
view.fitToElements(args.getMap(0), args.getBoolean(1));
break;
case "fitToSuppliedMarkers":
if (args == null) {
break;
}
view.fitToSuppliedMarkers(args.getArray(0), args.getMap(1), args.getBoolean(2));
break;
case "fitToCoordinates":
if (args == null) {
break;
}
view.fitToCoordinates(args.getArray(0), args.getMap(1), args.getBoolean(2));
break;
case "setMapBoundaries":
if (args == null) {
break;
}
view.setMapBoundaries(args.getMap(0), args.getMap(1));
break;
case "setIndoorActiveLevelIndex":
if (args == null) {
break;
}
view.setIndoorActiveLevelIndex(args.getInt(0));
break;
}
}
@Override
@Nullable
public Map getExportedCustomDirectEventTypeConstants() {
Map<String, Map<String, String>> map = MapBuilder.of(
"onMapReady", MapBuilder.of("registrationName", "onMapReady"),
"onPress", MapBuilder.of("registrationName", "onPress"),
"onLongPress", MapBuilder.of("registrationName", "onLongPress"),
"onMarkerPress", MapBuilder.of("registrationName", "onMarkerPress"),
"onCalloutPress", MapBuilder.of("registrationName", "onCalloutPress")
);
map.putAll(MapBuilder.of(
"onUserLocationChange", MapBuilder.of("registrationName", "onUserLocationChange"),
"onMarkerDragStart", MapBuilder.of("registrationName", "onMarkerDragStart"),
"onMarkerDrag", MapBuilder.of("registrationName", "onMarkerDrag"),
"onMarkerDragEnd", MapBuilder.of("registrationName", "onMarkerDragEnd"),
"onPanDrag", MapBuilder.of("registrationName", "onPanDrag"),
"onKmlReady", MapBuilder.of("registrationName", "onKmlReady"),
"onPoiClick", MapBuilder.of("registrationName", "onPoiClick")
));
map.putAll(MapBuilder.of(
"onIndoorLevelActivated", MapBuilder.of("registrationName", "onIndoorLevelActivated"),
"onIndoorBuildingFocused", MapBuilder.of("registrationName", "onIndoorBuildingFocused"),
"onDoublePress", MapBuilder.of("registrationName", "onDoublePress"),
"onMapLoaded", MapBuilder.of("registrationName", "onMapLoaded"),
"onMarkerSelect", MapBuilder.of("registrationName", "onMarkerSelect"),
"onMarkerDeselect", MapBuilder.of("registrationName", "onMarkerDeselect"),
"onRegionChangeStart", MapBuilder.of("registrationName", "onRegionChangeStart")
));
return map;
}
@Override
public LayoutShadowNode createShadowNodeInstance() {
// A custom shadow node is needed in order to pass back the width/height of the map to the
// view manager so that it can start applying camera moves with bounds.
return new SizeReportingShadowNode();
}
@Override
public void addView(MapView parent, View child, int index) {
parent.addFeature(child, index);
}
@Override
public int getChildCount(MapView view) {
return view.getFeatureCount();
}
@Override
public View getChildAt(MapView view, int index) {
return view.getFeatureAt(index);
}
@Override
public void removeViewAt(MapView parent, int index) {
parent.removeFeatureAt(index);
}
@Override
public void updateExtraData(MapView view, Object extraData) {
view.updateExtraData(extraData);
}
void pushEvent(ThemedReactContext context, View view, String name, WritableMap data) {
context
.getReactApplicationContext()
.getJSModule(RCTEventEmitter.class)
.receiveEvent(view.getId(), name, data);
}
@Override
public void onDropViewInstance(MapView view) {
view.doDestroy();
super.onDropViewInstance(view);
}
}

View File

@@ -0,0 +1,827 @@
package com.rnmaps.maps;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.view.View;
import android.widget.LinearLayout;
import android.animation.ObjectAnimator;
import android.util.Property;
import android.animation.TypeEvaluator;
import android.os.Handler;
import android.os.Looper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.facebook.common.references.CloseableReference;
import com.facebook.datasource.DataSource;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.drawee.controller.AbstractDraweeController;
import com.facebook.drawee.controller.BaseControllerListener;
import com.facebook.drawee.controller.ControllerListener;
import com.facebook.drawee.drawable.ScalingUtils;
import com.facebook.drawee.generic.GenericDraweeHierarchy;
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
import com.facebook.drawee.interfaces.DraweeController;
import com.facebook.drawee.view.DraweeHolder;
import com.facebook.drawee.view.DraweeView;
import com.facebook.fresco.ui.common.ControllerListener2;
import com.facebook.imagepipeline.core.ImagePipeline;
import com.facebook.imagepipeline.image.CloseableImage;
import com.facebook.imagepipeline.image.CloseableStaticBitmap;
import com.facebook.imagepipeline.image.ImageInfo;
import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.imagepipeline.request.ImageRequestBuilder;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.uimanager.UIManagerHelper;
import com.facebook.react.uimanager.events.Event;
import com.facebook.react.uimanager.events.EventDispatcher;
import com.google.android.gms.common.images.ImageManager;
import com.google.android.gms.maps.model.BitmapDescriptor;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;
import com.google.maps.android.collections.MarkerManager;
import com.rnmaps.fabric.event.OnDeselectEvent;
import com.rnmaps.fabric.event.OnDragEndEvent;
import com.rnmaps.fabric.event.OnDragEvent;
import com.rnmaps.fabric.event.OnDragStartEvent;
import com.rnmaps.fabric.event.OnPressEvent;
import com.rnmaps.fabric.event.OnSelectEvent;
import java.lang.ref.SoftReference;
import java.lang.reflect.Method;
import java.util.Map;
public class MapMarker extends MapFeature {
private MarkerOptions markerOptions;
private Marker marker;
private int width;
private int height;
private String identifier;
private LatLng position;
private String title;
private String snippet;
private boolean anchorIsSet;
private float anchorX;
private float anchorY;
private MapCallout calloutView;
private View wrappedCalloutView;
private final Context context;
private float markerHue = 0.0f; // should be between 0 and 360
private BitmapDescriptor iconBitmapDescriptor;
private Bitmap iconBitmap;
private float rotation = 0.0f;
private boolean flat = false;
private boolean draggable = false;
private int zIndex = 0;
private float opacity = 1.0f;
private float calloutAnchorX;
private float calloutAnchorY;
private boolean calloutAnchorIsSet;
private int updated = 0;
private boolean tracksViewChanges = true;
private boolean tracksViewChangesActive = false;
private boolean hasCustomMarkerView = false;
private final MapMarkerManager markerManager;
private String imageUri;
private boolean loadingImage;
private SoftReference<MarkerManager.Collection> markerCollectionRef;
private final DraweeHolder<?> logoHolder;
private ImageManager.OnImageLoadedListener imageLoadedListener;
private DataSource<CloseableReference<CloseableImage>> dataSource;
private final ControllerListener<ImageInfo> mLogoControllerListener =
new BaseControllerListener<ImageInfo>() {
@Override
public void onSubmit(String id, Object callerContext) {
loadingImage = true;
}
@Override
public void onFinalImageSet(
String id,
@Nullable final ImageInfo imageInfo,
@Nullable Animatable animatable) {
CloseableReference<CloseableImage> imageReference = null;
try {
imageReference = dataSource.getResult();
if (imageReference != null) {
CloseableImage image = imageReference.get();
if (image instanceof CloseableStaticBitmap) {
CloseableStaticBitmap closeableStaticBitmap = (CloseableStaticBitmap) image;
Bitmap bitmap = closeableStaticBitmap.getUnderlyingBitmap();
if (bitmap != null) {
bitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true);
iconBitmap = bitmap;
iconBitmapDescriptor = BitmapDescriptorFactory.fromBitmap(bitmap);
}
}
}
} finally {
dataSource.close();
if (imageReference != null) {
CloseableReference.closeSafely(imageReference);
}
}
if (MapMarker.this.markerManager != null && MapMarker.this.imageUri != null) {
MapMarker.this.markerManager.getSharedIcon(MapMarker.this.imageUri)
.updateIcon(iconBitmapDescriptor, iconBitmap);
}
update(true);
loadingImage = false;
if (imageLoadedListener != null) {
imageLoadedListener.onImageLoaded(null, null, false);
// fire and forget
imageLoadedListener = null;
}
}
};
public MapMarker(Context context, MapMarkerManager markerManager) {
super(context);
this.context = context;
this.markerManager = markerManager;
logoHolder = DraweeHolder.create(createDraweeHierarchy(), context);
logoHolder.onAttach();
}
public MapMarker(Context context, MarkerOptions options, MapMarkerManager markerManager) {
super(context);
this.context = context;
this.markerManager = markerManager;
logoHolder = DraweeHolder.create(createDraweeHierarchy(), context);
logoHolder.onAttach();
position = options.getPosition();
setAnchor(options.getAnchorU(), options.getAnchorV());
setCalloutAnchor(options.getInfoWindowAnchorU(), options.getInfoWindowAnchorV());
setTitle(options.getTitle());
setSnippet(options.getSnippet());
setRotation(options.getRotation());
setFlat(options.isFlat());
setDraggable(options.isDraggable());
setZIndex(Math.round(options.getZIndex()));
setOpacity(options.getAlpha());
iconBitmapDescriptor = options.getIcon();
}
private GenericDraweeHierarchy createDraweeHierarchy() {
return new GenericDraweeHierarchyBuilder(getResources())
.setActualImageScaleType(ScalingUtils.ScaleType.FIT_CENTER)
.setFadeDuration(0)
.build();
}
public void setCoordinate(ReadableMap coordinate) {
setCoordinate(new LatLng(coordinate.getDouble("latitude"), coordinate.getDouble("longitude")));
}
public void setCoordinate(LatLng position) {
this.position = position;
if (marker != null) {
marker.setPosition(position);
}
update(false);
}
public void setIdentifier(String identifier) {
this.identifier = identifier;
update(false);
}
public void doDestroy() {
MarkerManager.Collection collection = markerCollectionRef != null
? markerCollectionRef.get()
: null;
if (collection != null) {
this.removeFromMap(collection);
}
markerCollectionRef = null;
}
public String getIdentifier() {
return this.identifier;
}
public void setTitle(String title) {
this.title = title;
if (marker != null) {
marker.setTitle(title);
}
update(false);
}
public void setSnippet(String snippet) {
this.snippet = snippet;
if (marker != null) {
marker.setSnippet(snippet);
}
update(false);
}
public void setRotation(float rotation) {
this.rotation = rotation;
if (marker != null) {
marker.setRotation(rotation);
}
update(false);
}
public void setFlat(boolean flat) {
this.flat = flat;
if (marker != null) {
marker.setFlat(flat);
}
update(false);
}
public void setDraggable(boolean draggable) {
this.draggable = draggable;
if (marker != null) {
marker.setDraggable(draggable);
}
update(false);
}
public void setZIndex(int zIndex) {
this.zIndex = zIndex;
if (marker != null) {
marker.setZIndex(zIndex);
}
update(false);
}
public void setOpacity(float opacity) {
this.opacity = opacity;
if (marker != null) {
marker.setAlpha(opacity);
}
update(false);
}
public void setMarkerHue(float markerHue) {
this.markerHue = markerHue;
update(true);
}
public void setAnchor(double x, double y) {
anchorIsSet = true;
anchorX = (float) x;
anchorY = (float) y;
if (marker != null) {
marker.setAnchor(anchorX, anchorY);
}
update(false);
}
public void setCalloutAnchor(double x, double y) {
calloutAnchorIsSet = true;
calloutAnchorX = (float) x;
calloutAnchorY = (float) y;
if (marker != null) {
marker.setInfoWindowAnchor(calloutAnchorX, calloutAnchorY);
}
update(false);
}
public void setTracksViewChanges(boolean tracksViewChanges) {
this.tracksViewChanges = tracksViewChanges;
updateTracksViewChanges();
}
private void updateTracksViewChanges() {
boolean shouldTrack = tracksViewChanges && hasCustomMarkerView && marker != null;
if (shouldTrack == tracksViewChangesActive) return;
tracksViewChangesActive = shouldTrack;
if (shouldTrack) {
ViewChangesTracker.getInstance().addMarker(this);
} else {
ViewChangesTracker.getInstance().removeMarker(this);
// Let it render one more time to avoid race conditions.
// i.e. Image onLoad ->
// ViewChangesTracker may not get a chance to render ->
// setState({ tracksViewChanges: false }) ->
// image loaded but not rendered.
updateMarkerIcon();
}
}
public LatLng getPosition() {
return position;
}
public boolean updateCustomForTracking() {
if (!tracksViewChangesActive || updated == 0) {
tracksViewChangesActive = false;
return false;
}
updateMarkerIcon();
if (updated > 0) {
updated--;
}
return true;
}
public void updateMarkerIcon() {
if (marker == null) return;
marker.setIcon(getIcon());
}
public LatLng interpolate(float fraction, LatLng a, LatLng b) {
double lat = (b.latitude - a.latitude) * fraction + a.latitude;
double lng = (b.longitude - a.longitude) * fraction + a.longitude;
return new LatLng(lat, lng);
}
public void animateToCoodinate(LatLng finalPosition, Integer duration) {
TypeEvaluator<LatLng> typeEvaluator = new TypeEvaluator<LatLng>() {
@Override
public LatLng evaluate(float fraction, LatLng startValue, LatLng endValue) {
return interpolate(fraction, startValue, endValue);
}
};
Property<Marker, LatLng> property = Property.of(Marker.class, LatLng.class, "position");
ObjectAnimator animator = ObjectAnimator.ofObject(
marker,
property,
typeEvaluator,
finalPosition);
animator.setDuration(duration);
animator.start();
}
public void setImage(String uri) {
boolean shouldLoadImage = true;
if (this.markerManager != null) {
// remove marker from previous shared icon if needed, to avoid future updates from it.
// remove the shared icon completely if no markers on it as well.
// this is to avoid memory leak due to orphan bitmaps.
//
// However in case where client want to update all markers from icon A to icon B
// and after some time to update back from icon B to icon A
// it may be better to keep it though. We assume that is rare.
if (this.imageUri != null) {
this.markerManager.getSharedIcon(this.imageUri).removeMarker(this);
this.markerManager.removeSharedIconIfEmpty(this.imageUri);
}
if (uri != null) {
// listening for marker bitmap descriptor update, as well as check whether to load the image.
MapMarkerManager.AirMapMarkerSharedIcon sharedIcon = this.markerManager.getSharedIcon(uri);
sharedIcon.addMarker(this);
shouldLoadImage = sharedIcon.shouldLoadImage();
}
}
this.imageUri = uri;
if (!shouldLoadImage) {
return;
}
if (uri == null) {
iconBitmapDescriptor = null;
update(true);
} else if (uri.startsWith("http://") || uri.startsWith("https://") ||
uri.startsWith("file://") || uri.startsWith("asset://") || uri.startsWith("data:")) {
ImageRequest imageRequest = ImageRequestBuilder
.newBuilderWithSource(Uri.parse(uri))
.build();
ImagePipeline imagePipeline = Fresco.getImagePipeline();
dataSource = imagePipeline.fetchDecodedImage(imageRequest, this);
DraweeController controller = Fresco.newDraweeControllerBuilder()
.setImageRequest(imageRequest)
.setControllerListener(mLogoControllerListener)
.setOldController(logoHolder.getController())
.build();
logoHolder.setController(controller);
} else {
iconBitmapDescriptor = getBitmapDescriptorByName(uri);
int drawableId = getDrawableResourceByName(uri);
iconBitmap = BitmapFactory.decodeResource(getResources(), drawableId);
if (iconBitmap == null) { // VectorDrawable or similar
Drawable drawable = getResources().getDrawable(drawableId);
iconBitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
Canvas canvas = new Canvas(iconBitmap);
drawable.draw(canvas);
}
if (this.markerManager != null) {
this.markerManager.getSharedIcon(uri).updateIcon(iconBitmapDescriptor, iconBitmap);
}
update(true);
}
}
public void setIconBitmapDescriptor(BitmapDescriptor bitmapDescriptor, Bitmap bitmap) {
this.iconBitmapDescriptor = bitmapDescriptor;
this.iconBitmap = bitmap;
this.update(true);
}
public void setIconBitmap(Bitmap bitmap) {
this.iconBitmap = bitmap;
}
public MarkerOptions getMarkerOptions() {
if (markerOptions == null) {
markerOptions = new MarkerOptions();
}
fillMarkerOptions(markerOptions);
return markerOptions;
}
@Override
public void addView(View child, int index) {
super.addView(child, index);
// if children are added, it means we are rendering a custom marker
if (!(child instanceof MapCallout)) {
hasCustomMarkerView = true;
updateTracksViewChanges();
hackToHandleDraweeLifecycle(child);
}
update(true);
}
private void hackToHandleDraweeLifecycle(View child){
if (child instanceof DraweeView<?>) {
try {
DraweeView draweeView = (DraweeView) child;
Method onAttachMethod = DraweeView.class.getDeclaredMethod("onAttachedToWindow");
onAttachMethod.setAccessible(true);
onAttachMethod.invoke(child);
Handler mainHandler = new Handler(Looper.getMainLooper());
if (draweeView.getController() instanceof AbstractDraweeController<?,?>){
AbstractDraweeController abstractController = (AbstractDraweeController) draweeView.getController();
abstractController.addControllerListener2(new ControllerListener2() {
@Override
public void onSubmit(@NonNull String s, @Nullable Object o, @Nullable Extras extras) {
}
@Override
public void onFinalImageSet(@NonNull String s, @Nullable Object o, @Nullable Extras extras) {
mainHandler.postDelayed(() -> update(true), ((GenericDraweeHierarchy) draweeView.getHierarchy()).getFadeDuration());
}
@Override
public void onIntermediateImageSet(@NonNull String s, @Nullable Object o) {
mainHandler.post(() -> update(true));
}
@Override
public void onIntermediateImageFailed(@NonNull String s) {
}
@Override
public void onFailure(@NonNull String s, @Nullable Throwable throwable, @Nullable Extras extras) {
}
@Override
public void onRelease(@NonNull String s, @Nullable Extras extras) {
}
@Override
public void onEmptyEvent(@Nullable Object o) {
}
});
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
public void requestLayout() {
super.requestLayout();
if (getChildCount() == 0) {
if (hasCustomMarkerView) {
hasCustomMarkerView = false;
clearDrawableCache();
updateTracksViewChanges();
update(true);
}
} else {
// custom subview
if (!(getChildAt(0) instanceof MapCallout)) {
if (updated == 0) {
updated = 1;
updateTracksViewChanges();
}
}
}
}
@Override
public Object getFeature() {
return marker;
}
@Override
public void addToMap(Object collection) {
MarkerManager.Collection markerCollection = (MarkerManager.Collection) collection;
marker = markerCollection.addMarker(getMarkerOptions());
this.markerCollectionRef = new SoftReference<>(markerCollection);
updateTracksViewChanges();
}
@Override
public void removeFromMap(Object collection) {
if (marker == null) {
return;
}
MarkerManager.Collection markerCollection = (MarkerManager.Collection) collection;
markerCollection.remove(marker);
marker = null;
updateTracksViewChanges();
}
private BitmapDescriptor getIcon() {
if (hasCustomMarkerView) {
// creating a bitmap from an arbitrary view
if (iconBitmapDescriptor != null) {
Bitmap viewBitmap = createDrawable();
int width = Math.max(iconBitmap.getWidth(), viewBitmap.getWidth());
int height = Math.max(iconBitmap.getHeight(), viewBitmap.getHeight());
Bitmap combinedBitmap = Bitmap.createBitmap(width, height, iconBitmap.getConfig());
Canvas canvas = new Canvas(combinedBitmap);
canvas.drawBitmap(iconBitmap, 0, 0, null);
canvas.drawBitmap(viewBitmap, 0, 0, null);
return BitmapDescriptorFactory.fromBitmap(combinedBitmap);
} else {
return BitmapDescriptorFactory.fromBitmap(createDrawable());
}
} else if (iconBitmapDescriptor != null) {
// use local image as a marker
return iconBitmapDescriptor;
} else {
// render the default marker pin
return BitmapDescriptorFactory.defaultMarker(this.markerHue);
}
}
private MarkerOptions fillMarkerOptions(MarkerOptions options) {
options.position(position);
if (anchorIsSet) options.anchor(anchorX, anchorY);
if (calloutAnchorIsSet) options.infoWindowAnchor(calloutAnchorX, calloutAnchorY);
options.title(title);
options.snippet(snippet);
options.rotation(rotation);
options.flat(flat);
options.draggable(draggable);
options.zIndex(zIndex);
options.alpha(opacity);
options.icon(getIcon());
return options;
}
public void update(boolean updateIcon) {
if (marker == null) {
return;
}
if (updateIcon)
updateMarkerIcon();
if (anchorIsSet) {
marker.setAnchor(anchorX, anchorY);
} else {
marker.setAnchor(0.5f, 1.0f);
}
if (calloutAnchorIsSet) {
marker.setInfoWindowAnchor(calloutAnchorX, calloutAnchorY);
} else {
marker.setInfoWindowAnchor(0.5f, 0);
}
updated += 1;
}
public void update(int width, int height) {
this.width = width;
this.height = height;
updated += 1;
updateTracksViewChanges();
clearDrawableCache();
update(true);
}
public void redraw() {
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
updateMarkerIcon();
}
});
}
private Bitmap mLastBitmapCreated = null;
private void clearDrawableCache() {
mLastBitmapCreated = null;
}
private Bitmap createDrawable() {
int width = this.width <= 0 ? 100 : this.width;
int height = this.height <= 0 ? 100 : this.height;
// Do not create the doublebuffer-bitmap each time. reuse it to save memory.
Bitmap bitmap = mLastBitmapCreated;
if (bitmap == null ||
bitmap.isRecycled() ||
bitmap.getWidth() != width ||
bitmap.getHeight() != height) {
bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
mLastBitmapCreated = bitmap;
} else {
bitmap.eraseColor(Color.TRANSPARENT);
}
Canvas canvas = new Canvas(bitmap);
this.draw(canvas);
return bitmap;
}
public void setCalloutView(MapCallout view) {
this.calloutView = view;
}
public MapCallout getCalloutView() {
return this.calloutView;
}
public View getCallout() {
if (this.calloutView == null) return null;
if (this.wrappedCalloutView == null) {
this.wrapCalloutView();
}
if (this.calloutView.getTooltip()) {
return this.wrappedCalloutView;
} else {
return null;
}
}
public View getInfoContents() {
if (this.calloutView == null) return null;
if (this.wrappedCalloutView == null) {
this.wrapCalloutView();
}
if (this.calloutView.getTooltip()) {
return null;
} else {
return this.wrappedCalloutView;
}
}
private void wrapCalloutView() {
// some hackery is needed to get the arbitrary infowindow view to render centered, and
// with only the width/height that it needs.
if (this.calloutView == null || this.calloutView.getChildCount() == 0) {
return;
}
LinearLayout LL = new LinearLayout(context);
LL.setOrientation(LinearLayout.VERTICAL);
LL.setLayoutParams(new LinearLayout.LayoutParams(
this.calloutView.width,
this.calloutView.height,
0f
));
LinearLayout LL2 = new LinearLayout(context);
LL2.setOrientation(LinearLayout.HORIZONTAL);
LL2.setLayoutParams(new LinearLayout.LayoutParams(
this.calloutView.width,
this.calloutView.height,
0f
));
LL.addView(LL2);
LL2.addView(this.calloutView);
this.wrappedCalloutView = LL;
}
private int getDrawableResourceByName(String name) {
return getResources().getIdentifier(
name,
"drawable",
getContext().getPackageName());
}
public boolean isLoadingImage() {
return loadingImage;
}
public ImageManager.OnImageLoadedListener getImageLoadedListener() {
return imageLoadedListener;
}
public void setImageLoadedListener(ImageManager.OnImageLoadedListener imageLoadedListener) {
this.imageLoadedListener = imageLoadedListener;
}
public void setUpdated(boolean updated) {
if (updated) {
this.updated += 1;
} else {
this.updated = 0;
}
}
@FunctionalInterface
public interface EventCreator<T extends Event> {
T create(int surfaceId, int viewId, WritableMap payload);
}
public <T extends Event> void dispatchEvent(WritableMap payload, MapView.EventCreator<T> creator) {
// Cast context to ReactContext
ReactContext reactContext = (ReactContext) context;
// Get the event dispatcher
EventDispatcher eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, getId());
// If there is a dispatcher, create and dispatch the event
if (eventDispatcher != null) {
int surfaceId = UIManagerHelper.getSurfaceId(reactContext);
T event = creator.create(surfaceId, getId(), payload);
eventDispatcher.dispatchEvent(event);
}
}
private BitmapDescriptor getBitmapDescriptorByName(String name) {
return BitmapDescriptorFactory.fromResource(getDrawableResourceByName(name));
}
public static Map<String, Object> getExportedCustomBubblingEventTypeConstants() {
MapBuilder.Builder<String, Object> builder = MapBuilder.builder();
builder.put(OnPressEvent.EVENT_NAME, MapBuilder.of("registrationName", OnPressEvent.EVENT_NAME));
return builder.build();
}
public static Map<String, Object> getExportedCustomDirectEventTypeConstants() {
return MapBuilder.of(
OnSelectEvent.EVENT_NAME, MapBuilder.of("registrationName", OnSelectEvent.EVENT_NAME),
OnDeselectEvent.EVENT_NAME, MapBuilder.of("registrationName", OnDeselectEvent.EVENT_NAME),
OnDragEvent.EVENT_NAME, MapBuilder.of("registrationName", OnDragEvent.EVENT_NAME),
OnDragStartEvent.EVENT_NAME, MapBuilder.of("registrationName", OnDragStartEvent.EVENT_NAME),
OnDragEndEvent.EVENT_NAME, MapBuilder.of("registrationName", OnDragEndEvent.EVENT_NAME)
);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
this.height = b - t;
this.width = r - l;
}
}

View File

@@ -0,0 +1,368 @@
package com.rnmaps.maps;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.facebook.react.R;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.uimanager.LayoutShadowNode;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.BitmapDescriptor;
import com.google.android.gms.maps.model.LatLng;
import java.util.HashMap;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
public class MapMarkerManager extends ViewGroupManager<MapMarker> {
public static class AirMapMarkerSharedIcon {
private BitmapDescriptor iconBitmapDescriptor;
private Bitmap bitmap;
private final Map<MapMarker, Boolean> markers;
private boolean loadImageStarted;
public AirMapMarkerSharedIcon() {
this.markers = new WeakHashMap<>();
this.loadImageStarted = false;
}
/**
* check whether the load image process started.
* caller AirMapMarker will only need to load it when this returns true.
*
* @return true if it is not started, false otherwise.
*/
public synchronized boolean shouldLoadImage() {
if (!this.loadImageStarted) {
this.loadImageStarted = true;
return true;
}
return false;
}
/**
* subscribe icon update for given marker.
* <p>
* The marker is wrapped in weakReference, so no need to remove it explicitly.
*
* @param marker
*/
public synchronized void addMarker(MapMarker marker) {
this.markers.put(marker, true);
if (this.iconBitmapDescriptor != null) {
marker.setIconBitmapDescriptor(this.iconBitmapDescriptor, this.bitmap);
}
}
/**
* Remove marker from this shared icon.
* <p>
* Marker will only need to call it when the marker receives a different marker image uri.
*
* @param marker
*/
public synchronized void removeMarker(MapMarker marker) {
this.markers.remove(marker);
}
/**
* check if there is markers still listening on this icon.
* when there are not markers listen on it, we can remove it.
*
* @return true if there is, false otherwise
*/
public synchronized boolean hasMarker() {
return this.markers.isEmpty();
}
/**
* Update the bitmap descriptor and bitmap for the image uri.
* And notify all subscribers about the update.
*
* @param bitmapDescriptor
* @param bitmap
*/
public synchronized void updateIcon(BitmapDescriptor bitmapDescriptor, Bitmap bitmap) {
this.iconBitmapDescriptor = bitmapDescriptor;
this.bitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true);
if (this.markers.isEmpty()) {
return;
}
for (Map.Entry<MapMarker, Boolean> markerEntry : markers.entrySet()) {
if (markerEntry.getKey() != null) {
markerEntry.getKey().setIconBitmapDescriptor(bitmapDescriptor, bitmap);
}
}
}
}
private final Map<String, AirMapMarkerSharedIcon> sharedIcons = new ConcurrentHashMap<>();
/**
* get the shared icon object, if not existed, create a new one and store it.
*
* @param uri
* @return the icon object for the given uri.
*/
public AirMapMarkerSharedIcon getSharedIcon(String uri) {
AirMapMarkerSharedIcon icon = this.sharedIcons.get(uri);
if (icon == null) {
synchronized (this) {
if ((icon = this.sharedIcons.get(uri)) == null) {
icon = new AirMapMarkerSharedIcon();
this.sharedIcons.put(uri, icon);
}
}
}
return icon;
}
/**
* Remove the share icon object from our sharedIcons map when no markers are listening for it.
*
* @param uri
*/
public void removeSharedIconIfEmpty(String uri) {
AirMapMarkerSharedIcon icon = this.sharedIcons.get(uri);
if (icon == null) {
return;
}
if (!icon.hasMarker()) {
synchronized (this) {
if ((icon = this.sharedIcons.get(uri)) != null && !icon.hasMarker()) {
this.sharedIcons.remove(uri);
}
}
}
}
public MapMarkerManager() {
}
@Override
public String getName() {
return "AIRMapMarker";
}
@Override
public MapMarker createViewInstance(ThemedReactContext context) {
return new MapMarker(context, this);
}
@ReactProp(name = "coordinate")
public void setCoordinate(MapMarker view, ReadableMap map) {
view.setCoordinate(map);
}
@ReactProp(name = "title")
public void setTitle(MapMarker view, String title) {
view.setTitle(title);
}
@ReactProp(name = "identifier")
public void setIdentifier(MapMarker view, String identifier) {
view.setIdentifier(identifier);
}
@ReactProp(name = "description")
public void setDescription(MapMarker view, String description) {
view.setSnippet(description);
}
// NOTE(lmr):
// android uses normalized coordinate systems for this, and is provided through the
// `anchor` property and `calloutAnchor` instead. Perhaps some work could be done
// to normalize iOS and android to use just one of the systems.
// @ReactProp(name = "centerOffset")
// public void setCenterOffset(AirMapMarker view, ReadableMap map) {
//
// }
//
// @ReactProp(name = "calloutOffset")
// public void setCalloutOffset(AirMapMarker view, ReadableMap map) {
//
// }
@ReactProp(name = "anchor")
public void setAnchor(MapMarker view, ReadableMap map) {
// should default to (0.5, 1) (bottom middle)
double x = map != null && map.hasKey("x") ? map.getDouble("x") : 0.5;
double y = map != null && map.hasKey("y") ? map.getDouble("y") : 1.0;
view.setAnchor(x, y);
}
@ReactProp(name = "calloutAnchor")
public void setCalloutAnchor(MapMarker view, ReadableMap map) {
// should default to (0.5, 0) (top middle)
double x = map != null && map.hasKey("x") ? map.getDouble("x") : 0.5;
double y = map != null && map.hasKey("y") ? map.getDouble("y") : 0.0;
view.setCalloutAnchor(x, y);
}
@ReactProp(name = "image")
public void setImage(MapMarker view, @Nullable String source) {
view.setImage(source);
}
// public void setImage(AirMapMarker view, ReadableMap image) {
// view.setImage(image);
// }
@ReactProp(name = "icon")
public void setIcon(MapMarker view, @Nullable String source) {
view.setImage(source);
}
@ReactProp(name = "pinColor", defaultInt = Color.RED, customType = "Color")
public void setPinColor(MapMarker view, int pinColor) {
float[] hsv = new float[3];
Color.colorToHSV(pinColor, hsv);
// NOTE: android only supports a hue
view.setMarkerHue(hsv[0]);
}
@ReactProp(name = "rotation", defaultFloat = 0.0f)
public void setMarkerRotation(MapMarker view, float rotation) {
view.setRotation(rotation);
}
@ReactProp(name = "flat", defaultBoolean = false)
public void setFlat(MapMarker view, boolean flat) {
view.setFlat(flat);
}
@ReactProp(name = "draggable", defaultBoolean = false)
public void setDraggable(MapMarker view, boolean draggable) {
view.setDraggable(draggable);
}
@Override
@ReactProp(name = "zIndex", defaultFloat = 0.0f)
public void setZIndex(MapMarker view, float zIndex) {
super.setZIndex(view, zIndex);
int integerZIndex = Math.round(zIndex);
view.setZIndex(integerZIndex);
}
@Override
@ReactProp(name = "opacity", defaultFloat = 1.0f)
public void setOpacity(MapMarker view, float opacity) {
super.setOpacity(view, opacity);
view.setOpacity(opacity);
}
@ReactProp(name = "tracksViewChanges", defaultBoolean = true)
public void setTracksViewChanges(MapMarker view, boolean tracksViewChanges) {
view.setTracksViewChanges(tracksViewChanges);
}
@ReactProp(name = "accessibilityLabel")
public void setAccessibilityLabel(MapMarker view, @Nullable String accessibilityLabel) {
view.setTag(R.id.accessibility_label, accessibilityLabel);
}
@Override
public void addView(MapMarker parent, View child, int index) {
// if an <Callout /> component is a child, then it is a callout view, NOT part of the
// marker.
if (child instanceof MapCallout) {
parent.setCalloutView((MapCallout) child);
} else {
super.addView(parent, child, index);
parent.update(true);
}
}
@Override
public void removeViewAt(MapMarker parent, int index) {
super.removeViewAt(parent, index);
parent.update(true);
}
@Override
public void receiveCommand(@NonNull MapMarker view, String commandId, @Nullable ReadableArray args) {
int duration;
double lat;
double lng;
ReadableMap region;
switch (commandId) {
case "showCallout":
((Marker) view.getFeature()).showInfoWindow();
break;
case "hideCallout":
((Marker) view.getFeature()).hideInfoWindow();
break;
case "animateMarkerToCoordinate":
if (args == null) {
break;
}
region = args.getMap(0);
duration = args.getInt(1);
lng = region.getDouble("longitude");
lat = region.getDouble("latitude");
view.animateToCoodinate(new LatLng(lat, lng), duration);
break;
case "redraw":
view.updateMarkerIcon();
break;
}
}
@Override
@Nullable
public Map getExportedCustomDirectEventTypeConstants() {
return MapBuilder.<String, Map<String, String>>builder()
.put("onCalloutPress", MapBuilder.of("registrationName", "onCalloutPress"))
.put("onDragStart", MapBuilder.of("registrationName", "onDragStart"))
.put("onDrag", MapBuilder.of("registrationName", "onDrag"))
.put("onDragEnd", MapBuilder.of("registrationName", "onDragEnd"))
.put("onSelect", MapBuilder.of("registrationName", "onSelect"))
.put("onDeselect", MapBuilder.of("registrationName", "onDeselect"))
.build();
}
@Override
@Nullable
public Map getExportedCustomBubblingEventTypeConstants() {
return MapBuilder.<String, Map<String, Object>>builder()
.put("onPress", MapBuilder.of("phasedRegistrationNames", MapBuilder.of("bubbled", "onPress")))
.build();
}
@Override
public LayoutShadowNode createShadowNodeInstance() {
// we use a custom shadow node that emits the width/height of the view
// after layout with the updateExtraData method. Without this, we can't generate
// a bitmap of the appropriate width/height of the rendered view.
return new SizeReportingShadowNode();
}
@Override
public void updateExtraData(MapMarker view, Object extraData) {
// This method is called from the shadow node with the width/height of the rendered
// marker view.
HashMap<String, Float> data = (HashMap<String, Float>) extraData;
float width = data.get("width");
float height = data.get("height");
view.update((int) width, (int) height);
}
}

View File

@@ -0,0 +1,283 @@
package com.rnmaps.maps;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.location.Address;
import android.location.Geocoder;
import android.net.Uri;
import android.util.Base64;
import android.util.DisplayMetrics;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeMap;
import com.facebook.react.module.annotations.ReactModule;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.LatLng;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ReactModule(name = MapModule.NAME)
public class MapModule extends ReactContextBaseJavaModule {
public static final String NAME = "AirMapModule";
public static final String SNAPSHOT_RESULT_FILE = "file";
public static final String SNAPSHOT_RESULT_BASE64 = "base64";
public static final String SNAPSHOT_FORMAT_PNG = "png";
public static final String SNAPSHOT_FORMAT_JPG = "jpg";
public MapModule(ReactApplicationContext reactContext) {
super(reactContext);
}
@Override
public String getName() {
return NAME;
}
@Override
public Map<String, Object> getConstants() {
final Map<String, Object> constants = new HashMap<>();
constants.put("legalNotice", "This license information is displayed in Settings > Google > Open Source on any device running Google Play services.");
return constants;
}
public Activity getActivity() {
return getCurrentActivity();
}
public static void closeQuietly(Closeable closeable) {
if (closeable == null) return;
try {
closeable.close();
} catch (IOException ignored) {
}
}
@ReactMethod
public void takeSnapshot(final int tag, final ReadableMap options, final Promise promise) {
// Parse and verity options
final ReactApplicationContext context = getReactApplicationContext();
final String format = options.hasKey("format") ? options.getString("format") : "png";
final Bitmap.CompressFormat compressFormat =
format.equals(SNAPSHOT_FORMAT_PNG) ? Bitmap.CompressFormat.PNG :
format.equals(SNAPSHOT_FORMAT_JPG) ? Bitmap.CompressFormat.JPEG : null;
final double quality = options.hasKey("quality") ? options.getDouble("quality") : 1.0;
final DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
final Integer width =
options.hasKey("width") ? (int) (displayMetrics.density * options.getDouble("width")) : 0;
final Integer height =
options.hasKey("height") ? (int) (displayMetrics.density * options.getDouble("height")) : 0;
final String result = options.hasKey("result") ? options.getString("result") : "file";
MapUIBlock uiBlock = new MapUIBlock(tag, promise, context, view -> {
view.map.snapshot(new GoogleMap.SnapshotReadyCallback() {
public void onSnapshotReady(@Nullable Bitmap snapshot) {
// Convert image to requested width/height if necessary
if (snapshot == null) {
promise.reject("Failed to generate bitmap, snapshot = null");
return;
}
if ((width != 0) && (height != 0) &&
(width != snapshot.getWidth() || height != snapshot.getHeight())) {
snapshot = Bitmap.createScaledBitmap(snapshot, width, height, true);
}
// Save the snapshot to disk
if (result.equals(SNAPSHOT_RESULT_FILE)) {
File tempFile;
FileOutputStream outputStream;
try {
tempFile =
File.createTempFile("AirMapSnapshot", "." + format, context.getCacheDir());
outputStream = new FileOutputStream(tempFile);
} catch (Exception e) {
promise.reject(e);
return;
}
snapshot.compress(compressFormat, (int) (100.0 * quality), outputStream);
closeQuietly(outputStream);
String uri = Uri.fromFile(tempFile).toString();
promise.resolve(uri);
} else if (result.equals(SNAPSHOT_RESULT_BASE64)) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
snapshot.compress(compressFormat, (int) (100.0 * quality), outputStream);
closeQuietly(outputStream);
byte[] bytes = outputStream.toByteArray();
String data = Base64.encodeToString(bytes, Base64.NO_WRAP);
promise.resolve(data);
}
}
});
return null;
});
// Add UI-block so we can get a valid reference to the map-view
uiBlock.addToUIManager();
}
@ReactMethod
public void getCamera(final int tag, final Promise promise) {
final ReactApplicationContext context = getReactApplicationContext();
MapUIBlock uiBlock = new MapUIBlock(tag, promise, context, view -> {
CameraPosition position = view.map.getCameraPosition();
WritableMap centerJson = new WritableNativeMap();
centerJson.putDouble("latitude", position.target.latitude);
centerJson.putDouble("longitude", position.target.longitude);
WritableMap cameraJson = new WritableNativeMap();
cameraJson.putMap("center", centerJson);
cameraJson.putDouble("heading", (double) position.bearing);
cameraJson.putDouble("zoom", (double) position.zoom);
cameraJson.putDouble("pitch", (double) position.tilt);
promise.resolve(cameraJson);
return null;
});
uiBlock.addToUIManager();
}
@ReactMethod
public void getAddressFromCoordinates(final int tag, final ReadableMap coordinate, final Promise promise) {
final ReactApplicationContext context = getReactApplicationContext();
MapUIBlock uiBlock = new MapUIBlock(tag, promise, context, mapView -> {
if (coordinate == null ||
!coordinate.hasKey("latitude") ||
!coordinate.hasKey("longitude")) {
promise.reject("Invalid coordinate format");
return null;
}
Geocoder geocoder = new Geocoder(context);
try {
List<Address> list =
geocoder.getFromLocation(coordinate.getDouble("latitude"), coordinate.getDouble("longitude"), 1);
if (list.isEmpty()) {
promise.reject("Can not get address location");
return null;
}
Address address = list.get(0);
WritableMap addressJson = new WritableNativeMap();
addressJson.putString("name", address.getFeatureName());
addressJson.putString("locality", address.getLocality());
addressJson.putString("thoroughfare", address.getThoroughfare());
addressJson.putString("subThoroughfare", address.getSubThoroughfare());
addressJson.putString("subLocality", address.getSubLocality());
addressJson.putString("administrativeArea", address.getAdminArea());
addressJson.putString("subAdministrativeArea", address.getSubAdminArea());
addressJson.putString("postalCode", address.getPostalCode());
addressJson.putString("countryCode", address.getCountryCode());
addressJson.putString("country", address.getCountryName());
promise.resolve(addressJson);
} catch (IOException e) {
promise.reject("Can not get address location");
}
return null;
});
uiBlock.addToUIManager();
}
@ReactMethod
public void pointForCoordinate(final int tag, ReadableMap coordinate, final Promise promise) {
final ReactApplicationContext context = getReactApplicationContext();
final double density = (double) context.getResources().getDisplayMetrics().density;
final LatLng coord = new LatLng(
coordinate.hasKey("latitude") ? coordinate.getDouble("latitude") : 0.0,
coordinate.hasKey("longitude") ? coordinate.getDouble("longitude") : 0.0
);
MapUIBlock uiBlock = new MapUIBlock(tag, promise, context, view -> {
Point pt = view.map.getProjection().toScreenLocation(coord);
WritableMap ptJson = new WritableNativeMap();
ptJson.putDouble("x", (double) pt.x / density);
ptJson.putDouble("y", (double) pt.y / density);
promise.resolve(ptJson);
return null;
});
uiBlock.addToUIManager();
}
@ReactMethod
public void coordinateForPoint(final int tag, ReadableMap point, final Promise promise) {
final ReactApplicationContext context = getReactApplicationContext();
final double density = (double) context.getResources().getDisplayMetrics().density;
final Point pt = new Point(
point.hasKey("x") ? (int) (point.getDouble("x") * density) : 0,
point.hasKey("y") ? (int) (point.getDouble("y") * density) : 0
);
MapUIBlock uiBlock = new MapUIBlock(tag, promise, context, view -> {
LatLng coord = view.map.getProjection().fromScreenLocation(pt);
WritableMap coordJson = new WritableNativeMap();
coordJson.putDouble("latitude", coord.latitude);
coordJson.putDouble("longitude", coord.longitude);
promise.resolve(coordJson);
return null;
});
uiBlock.addToUIManager();
}
@ReactMethod
public void getMapBoundaries(final int tag, final Promise promise) {
final ReactApplicationContext context = getReactApplicationContext();
MapUIBlock uiBlock = new MapUIBlock(tag, promise, context, view -> {
double[][] boundaries = view.getMapBoundaries();
WritableMap coordinates = new WritableNativeMap();
WritableMap northEastHash = new WritableNativeMap();
WritableMap southWestHash = new WritableNativeMap();
northEastHash.putDouble("longitude", boundaries[0][0]);
northEastHash.putDouble("latitude", boundaries[0][1]);
southWestHash.putDouble("longitude", boundaries[1][0]);
southWestHash.putDouble("latitude", boundaries[1][1]);
coordinates.putMap("northEast", northEastHash);
coordinates.putMap("southWest", southWestHash);
promise.resolve(coordinates);
return null;
});
uiBlock.addToUIManager();
}
}

View File

@@ -0,0 +1,262 @@
package com.rnmaps.maps;
import android.content.Context;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import com.facebook.common.references.CloseableReference;
import com.facebook.datasource.BaseDataSubscriber;
import com.facebook.datasource.DataSource;
import com.facebook.datasource.DataSubscriber;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.drawee.drawable.ScalingUtils;
import com.facebook.drawee.generic.GenericDraweeHierarchy;
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
import com.facebook.imagepipeline.core.ImagePipeline;
import com.facebook.imagepipeline.image.CloseableBitmap;
import com.facebook.imagepipeline.image.CloseableImage;
import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.imagepipeline.request.ImageRequestBuilder;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.common.MapBuilder;
import com.google.android.gms.common.images.ImageManager;
import com.google.android.gms.maps.model.BitmapDescriptor;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.GroundOverlay;
import com.google.android.gms.maps.model.GroundOverlayOptions;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
import com.google.maps.android.collections.GroundOverlayManager;
import com.rnmaps.fabric.event.OnPressEvent;
import java.util.Map;
import java.util.concurrent.Executors;
public class MapOverlay extends MapFeature {
private String imageUri;
private GroundOverlayOptions groundOverlayOptions;
private GroundOverlay groundOverlay;
private LatLngBounds bounds;
private float bearing;
private BitmapDescriptor bitmapDescriptor;
private boolean tappable;
private float zIndex;
private float transparency;
private DataSource<CloseableReference<CloseableImage>> dataSource;
private GroundOverlayManager.Collection groundOverlayCollection;
private ImageManager.OnImageLoadedListener imageLoadedListener;
public MapOverlay(Context context) {
super(context);
transparency = 1;
}
private GenericDraweeHierarchy createDraweeHierarchy() {
return new GenericDraweeHierarchyBuilder(getResources())
.setActualImageScaleType(ScalingUtils.ScaleType.FIT_CENTER)
.setFadeDuration(0)
.build();
}
public void setBounds(LatLngBounds bounds) {
this.bounds = bounds;
if (this.groundOverlay != null) {
this.groundOverlay.setPositionFromBounds(this.bounds);
}
}
public void setBearing(float bearing) {
this.bearing = bearing;
if (this.groundOverlay != null) {
this.groundOverlay.setBearing(bearing);
}
}
public void setZIndex(float zIndex) {
this.zIndex = zIndex;
if (this.groundOverlay != null) {
this.groundOverlay.setZIndex(zIndex);
}
}
public void setTransparency(float transparency) {
this.transparency = transparency;
if (groundOverlay != null) {
groundOverlay.setTransparency(transparency);
}
}
public void setImage(String uri) {
boolean shouldLoadImage = true;
this.imageUri = uri;
if (!shouldLoadImage) {
return;
}
if (uri == null) {
bitmapDescriptor = null;
} else if (uri.startsWith("http://") || uri.startsWith("https://") ||
uri.startsWith("file://") || uri.startsWith("asset://") || uri.startsWith("data:")) {
ImageRequest imageRequest = ImageRequestBuilder
.newBuilderWithSource(Uri.parse(uri))
.build();
ImagePipeline imagePipeline = Fresco.getImagePipeline();
dataSource = imagePipeline.fetchDecodedImage(imageRequest, this);
DataSubscriber<CloseableReference<CloseableImage>> subscriber = new BaseDataSubscriber<CloseableReference<CloseableImage>>() {
@Override
protected void onNewResultImpl(DataSource<CloseableReference<CloseableImage>> dataSource) {
if (!dataSource.isFinished()) {
return;
}
CloseableReference<CloseableImage> imageReference = dataSource.getResult();
if (imageReference != null) {
try {
CloseableImage closeableImage = imageReference.get();
if (closeableImage instanceof CloseableBitmap) {
Bitmap bitmap = ((CloseableBitmap) closeableImage).getUnderlyingBitmap();
if (bitmap != null) {
bitmapDescriptor = BitmapDescriptorFactory.fromBitmap(bitmap);
}
}
} finally {
CloseableReference.closeSafely(imageReference);
}
new Handler(Looper.getMainLooper()).post(() -> update());
}
}
@Override
protected void onFailureImpl(DataSource<CloseableReference<CloseableImage>> dataSource) {
// Handle failure
}
};
dataSource.subscribe(subscriber, Executors.newSingleThreadExecutor());
} else {
bitmapDescriptor = getBitmapDescriptorByName(uri);
}
}
private BitmapDescriptor getBitmapDescriptorByName(String name) {
return BitmapDescriptorFactory.fromResource(getDrawableResourceByName(name));
}
private int getDrawableResourceByName(String name) {
return getResources().getIdentifier(
name,
"drawable",
getContext().getPackageName());
}
public void setTappable(boolean tapabble) {
this.tappable = tapabble;
if (groundOverlay != null) {
groundOverlay.setClickable(tappable);
}
}
public static Map<String, Object> getExportedCustomBubblingEventTypeConstants() {
MapBuilder.Builder<String, Object> builder = MapBuilder.builder();
builder.put(OnPressEvent.EVENT_NAME, MapBuilder.of("registrationName", OnPressEvent.EVENT_NAME));
return builder.build();
}
public GroundOverlayOptions getGroundOverlayOptions() {
if (this.groundOverlayOptions == null) {
this.groundOverlayOptions = createGroundOverlayOptions();
}
return this.groundOverlayOptions;
}
private GroundOverlayOptions createGroundOverlayOptions() {
if (this.groundOverlayOptions != null) {
return this.groundOverlayOptions;
}
GroundOverlayOptions options = new GroundOverlayOptions();
if (this.bitmapDescriptor != null) {
options.image(bitmapDescriptor);
} else {
// add stub image to be able to instantiate the overlay
// and store a reference to it in MapView
options.image(BitmapDescriptorFactory.defaultMarker());
// hide overlay until real image gets added
options.visible(false);
}
options.positionFromBounds(bounds);
options.zIndex(zIndex);
options.bearing(bearing);
// options.transparency(transparency);
return options;
}
@Override
public Object getFeature() {
return groundOverlay;
}
@Override
public void addToMap(Object collection) {
GroundOverlayManager.Collection groundOverlayCollection = (GroundOverlayManager.Collection) collection;
GroundOverlayOptions groundOverlayOptions = getGroundOverlayOptions();
if (groundOverlayOptions != null) {
groundOverlay = groundOverlayCollection.addGroundOverlay(groundOverlayOptions);
groundOverlay.setClickable(this.tappable);
} else {
this.groundOverlayCollection = groundOverlayCollection;
}
}
@Override
public void removeFromMap(Object collection) {
if (groundOverlay != null) {
GroundOverlayManager.Collection groundOverlayCollection = (GroundOverlayManager.Collection) collection;
groundOverlayCollection.remove(groundOverlay);
groundOverlay = null;
groundOverlayOptions = null;
}
groundOverlayCollection = null;
}
public void update() {
this.groundOverlay = getGroundOverlay();
if (this.groundOverlay != null) {
this.groundOverlay.setVisible(true);
this.groundOverlay.setImage(this.bitmapDescriptor);
// this.groundOverlay.setTransparency(this.transparency);
this.groundOverlay.setClickable(this.tappable);
}
}
private GroundOverlay getGroundOverlay() {
if (this.groundOverlay != null) {
return this.groundOverlay;
}
if (this.groundOverlayCollection == null) {
return null;
}
GroundOverlayOptions groundOverlayOptions = getGroundOverlayOptions();
if (groundOverlayOptions != null) {
return this.groundOverlayCollection.addGroundOverlay(groundOverlayOptions);
}
return null;
}
}

View File

@@ -0,0 +1,97 @@
package com.rnmaps.maps;
import android.content.Context;
import android.util.DisplayMetrics;
import android.view.WindowManager;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
import java.util.Map;
public class MapOverlayManager extends ViewGroupManager<MapOverlay> {
public MapOverlayManager(ReactApplicationContext reactContext) {
super();
DisplayMetrics metrics = new DisplayMetrics();
((WindowManager) reactContext.getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay()
.getRealMetrics(metrics);
}
@Override
public String getName() {
return "AIRMapOverlay";
}
@Override
public MapOverlay createViewInstance(ThemedReactContext context) {
return new MapOverlay(context);
}
@ReactProp(name = "bounds")
public void setBounds(MapOverlay view, ReadableArray bounds) {
view.setBounds(fixBoundsIfNecessary(bounds));
}
// seems like apple users north west, south east
// google uses north east, south west
private static LatLngBounds fixBoundsIfNecessary(ReadableArray bounds) {
double lat1 = bounds.getArray(0).getDouble(0);
double lon1 = bounds.getArray(0).getDouble(1);
double lat2 = bounds.getArray(1).getDouble(0);
double lon2 = bounds.getArray(1).getDouble(1);
// Ensure lat1/lon1 is the SW corner and lat2/lon2 is the NE corner
double southLat = Math.min(lat1, lat2);
double northLat = Math.max(lat1, lat2);
double westLon = Math.min(lon1, lon2);
double eastLon = Math.max(lon1, lon2);
// Create corrected LatLngs
LatLng sw = new LatLng(southLat, westLon);
LatLng ne = new LatLng(northLat, eastLon);
return new LatLngBounds(sw, ne);
}
@ReactProp(name = "bearing")
public void setBearing(MapOverlay view, float bearing){
view.setBearing(bearing);
}
@ReactProp(name = "zIndex", defaultFloat = 1.0f)
public void setZIndex(MapOverlay view, float zIndex) {
view.setZIndex(zIndex);
}
@ReactProp(name = "opacity", defaultFloat = 1.0f)
public void setOpacity(MapOverlay view, float opacity) {
view.setTransparency(1 - opacity);
}
@ReactProp(name = "image")
public void setImage(MapOverlay view, @Nullable String source) {
view.setImage(source);
}
@ReactProp(name = "tappable", defaultBoolean = false)
public void setTappable(MapOverlay view, boolean tapabble) {
view.setTappable(tapabble);
}
@Override
@Nullable
public Map getExportedCustomDirectEventTypeConstants() {
return MapBuilder.of(
"onPress", MapBuilder.of("registrationName", "onPress")
);
}
}

View File

@@ -0,0 +1,224 @@
package com.rnmaps.maps;
import android.content.Context;
import android.graphics.Color;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.uimanager.UIManagerHelper;
import com.facebook.react.uimanager.events.Event;
import com.facebook.react.uimanager.events.EventDispatcher;
import com.google.android.gms.maps.model.Dash;
import com.google.android.gms.maps.model.Gap;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.PatternItem;
import com.google.android.gms.maps.model.Polygon;
import com.google.android.gms.maps.model.PolygonOptions;
import com.google.maps.android.collections.PolygonManager;
import com.rnmaps.fabric.event.OnPressEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class MapPolygon extends MapFeature {
private PolygonOptions polygonOptions;
private Polygon polygon;
private List<LatLng> coordinates;
private List<List<LatLng>> holes;
private int strokeColor;
private int fillColor;
private float strokeWidth;
private boolean geodesic;
private boolean tappable;
private float zIndex;
private ReadableArray patternValues;
private List<PatternItem> pattern;
public MapPolygon(Context context) {
super(context);
strokeWidth = 10;
}
public static Map<String, Object> getExportedCustomBubblingEventTypeConstants() {
MapBuilder.Builder<String, Object> builder = MapBuilder.builder();
builder.put(OnPressEvent.EVENT_NAME, MapBuilder.of("registrationName", OnPressEvent.EVENT_NAME));
return builder.build();
}
public <T extends Event> void dispatchEvent(WritableMap payload, MapView.EventCreator<T> creator, ReactContext reactContext) {
// Get the event dispatcher
EventDispatcher eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, getId());
// If there is a dispatcher, create and dispatch the event
if (eventDispatcher != null) {
int surfaceId = UIManagerHelper.getSurfaceId(reactContext);
T event = creator.create(surfaceId, getId(), payload);
eventDispatcher.dispatchEvent(event);
}
}
public void setCoordinates(ReadableArray coordinates) {
// it's kind of a bummer that we can't run map() or anything on the ReadableArray
this.coordinates = new ArrayList<>(coordinates.size());
for (int i = 0; i < coordinates.size(); i++) {
ReadableMap coordinate = coordinates.getMap(i);
this.coordinates.add(i,
new LatLng(coordinate.getDouble("latitude"), coordinate.getDouble("longitude")));
}
if (polygon != null) {
polygon.setPoints(this.coordinates);
}
}
public void setHoles(ReadableArray holes) {
if (holes == null) { return; }
this.holes = new ArrayList<>(holes.size());
for (int i = 0; i < holes.size(); i++) {
ReadableArray hole = holes.getArray(i);
if (hole.size() < 3) { continue; }
List<LatLng> coordinates = new ArrayList<>();
for (int j = 0; j < hole.size(); j++) {
ReadableMap coordinate = hole.getMap(j);
coordinates.add(new LatLng(
coordinate.getDouble("latitude"),
coordinate.getDouble("longitude")));
}
// If hole is triangle
if (coordinates.size() == 3) {
coordinates.add(coordinates.get(0));
}
this.holes.add(coordinates);
}
if (polygon != null) {
polygon.setHoles(this.holes);
}
}
public void setFillColor(int color) {
this.fillColor = color;
if (polygon != null) {
polygon.setFillColor(color);
}
}
public void setStrokeColor(int color) {
this.strokeColor = color;
if (polygon != null) {
polygon.setStrokeColor(color);
}
}
public void setStrokeWidth(float width) {
this.strokeWidth = width;
if (polygon != null) {
polygon.setStrokeWidth(width);
}
}
public void setTappable(boolean tapabble) {
this.tappable = tapabble;
if (polygon != null) {
polygon.setClickable(tappable);
}
}
public void setGeodesic(boolean geodesic) {
this.geodesic = geodesic;
if (polygon != null) {
polygon.setGeodesic(geodesic);
}
}
public void setZIndex(float zIndex) {
this.zIndex = zIndex;
if (polygon != null) {
polygon.setZIndex(zIndex);
}
}
public void setLineDashPattern(ReadableArray patternValues) {
this.patternValues = patternValues;
this.applyPattern();
}
private void applyPattern() {
if(patternValues == null) {
return;
}
this.pattern = new ArrayList<>(patternValues.size());
for (int i = 0; i < patternValues.size(); i++) {
float patternValue = (float) patternValues.getDouble(i);
boolean isGap = i % 2 != 0;
if(isGap) {
this.pattern.add(new Gap(patternValue));
}else {
PatternItem patternItem;
patternItem = new Dash(patternValue);
this.pattern.add(patternItem);
}
}
if(polygon != null) {
polygon.setStrokePattern(this.pattern);
}
}
public PolygonOptions getPolygonOptions() {
if (polygonOptions == null) {
polygonOptions = createPolygonOptions();
}
return polygonOptions;
}
private PolygonOptions createPolygonOptions() {
PolygonOptions options = new PolygonOptions();
options.addAll(coordinates);
options.fillColor(fillColor);
options.strokeColor(strokeColor);
options.strokeWidth(strokeWidth);
options.geodesic(geodesic);
options.zIndex(zIndex);
options.strokePattern(this.pattern);
options.clickable(this.tappable);
if (this.holes != null) {
for (int i = 0; i < holes.size(); i++) {
options.addHole(holes.get(i));
}
}
return options;
}
@Override
public Object getFeature() {
return polygon;
}
@Override
public void addToMap(Object collection) {
PolygonManager.Collection polygonCollection = (PolygonManager.Collection) collection;
polygon = polygonCollection.addPolygon(getPolygonOptions());
}
@Override
public void removeFromMap(Object collection) {
PolygonManager.Collection polygonCollection = (PolygonManager.Collection) collection;
polygonCollection.remove(polygon);
}
}

View File

@@ -0,0 +1,93 @@
package com.rnmaps.maps;
import android.content.Context;
import android.graphics.Color;
import android.util.DisplayMetrics;
import android.view.WindowManager;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.annotations.ReactProp;
import java.util.Map;
public class MapPolygonManager extends ViewGroupManager<MapPolygon> {
private final DisplayMetrics metrics;
public MapPolygonManager(ReactApplicationContext reactContext) {
super();
metrics = new DisplayMetrics();
((WindowManager) reactContext.getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay()
.getRealMetrics(metrics);
}
@Override
public String getName() {
return "AIRMapPolygon";
}
@Override
public MapPolygon createViewInstance(ThemedReactContext context) {
return new MapPolygon(context);
}
@ReactProp(name = "coordinates")
public void setCoordinate(MapPolygon view, ReadableArray coordinates) {
view.setCoordinates(coordinates);
}
@ReactProp(name = "holes")
public void setHoles(MapPolygon view, ReadableArray holes) {
view.setHoles(holes);
}
@ReactProp(name = "strokeWidth", defaultFloat = 1f)
public void setStrokeWidth(MapPolygon view, float widthInPoints) {
float widthInScreenPx = metrics.density * widthInPoints; // done for parity with iOS
view.setStrokeWidth(widthInScreenPx);
}
@ReactProp(name = "fillColor", defaultInt = Color.RED, customType = "Color")
public void setFillColor(MapPolygon view, int color) {
view.setFillColor(color);
}
@ReactProp(name = "strokeColor", defaultInt = Color.RED, customType = "Color")
public void setStrokeColor(MapPolygon view, int color) {
view.setStrokeColor(color);
}
@ReactProp(name = "tappable", defaultBoolean = false)
public void setTappable(MapPolygon view, boolean tapabble) {
view.setTappable(tapabble);
}
@ReactProp(name = "geodesic", defaultBoolean = false)
public void setGeodesic(MapPolygon view, boolean geodesic) {
view.setGeodesic(geodesic);
}
@ReactProp(name = "zIndex", defaultFloat = 1.0f)
public void setZIndex(MapPolygon view, float zIndex) {
view.setZIndex(zIndex);
}
@ReactProp(name = "lineDashPattern")
public void setLineDashPattern(MapPolygon view, ReadableArray patternValues) {
view.setLineDashPattern(patternValues);
}
@Override
@Nullable
public Map getExportedCustomDirectEventTypeConstants() {
return MapBuilder.of(
"onPress", MapBuilder.of("registrationName", "onPress")
);
}
}

View File

@@ -0,0 +1,237 @@
package com.rnmaps.maps;
import android.content.Context;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.common.MapBuilder;
import com.google.android.gms.maps.model.ButtCap;
import com.google.android.gms.maps.model.Cap;
import com.google.android.gms.maps.model.Dash;
import com.google.android.gms.maps.model.Dot;
import com.google.android.gms.maps.model.Gap;
import com.google.android.gms.maps.model.JointType;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.PatternItem;
import com.google.android.gms.maps.model.Polyline;
import com.google.android.gms.maps.model.PolylineOptions;
import com.google.android.gms.maps.model.RoundCap;
import com.google.android.gms.maps.model.SquareCap;
import com.google.android.gms.maps.model.StrokeStyle;
import com.google.android.gms.maps.model.StyleSpan;
import com.google.maps.android.collections.PolylineManager;
import com.rnmaps.fabric.event.OnPressEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class MapPolyline extends MapFeature {
private PolylineOptions polylineOptions;
private Polyline polyline;
private List<LatLng> coordinates;
private int color;
private float width;
private boolean tappable;
private boolean geodesic;
private float zIndex;
private Cap lineCap = new RoundCap();
private ReadableArray patternValues;
private List<PatternItem> pattern;
private List<StyleSpan> spans;
public MapPolyline(Context context) {
super(context);
}
public void setCoordinates(ReadableArray coordinates) {
this.coordinates = new ArrayList<>(coordinates.size());
for (int i = 0; i < coordinates.size(); i++) {
ReadableMap coordinate = coordinates.getMap(i);
this.coordinates.add(i,
new LatLng(coordinate.getDouble("latitude"), coordinate.getDouble("longitude")));
}
if (polyline != null) {
polyline.setPoints(this.coordinates);
}
}
public void setColor(int color) {
this.color = color;
if (polyline != null) {
polyline.setColor(color);
}
}
public void setStrokeColors(ReadableArray strokeColors) {
List<StyleSpan> spans = new ArrayList<>();
for (int i = 0; i < strokeColors.size(); i++) {
StrokeStyle stroke;
if (i == 0) {
stroke = StrokeStyle.colorBuilder(strokeColors.getInt(i)).build();
} else {
stroke = StrokeStyle.gradientBuilder(strokeColors.getInt(i - 1), strokeColors.getInt(i)).build();
}
spans.add(new StyleSpan(stroke));
}
this.spans = spans;
if (polyline != null){
polyline.setSpans(spans);
}
}
public void setWidth(float width) {
this.width = width;
if (polyline != null) {
polyline.setWidth(width);
}
}
public void setZIndex(float zIndex) {
this.zIndex = zIndex;
if (polyline != null) {
polyline.setZIndex(zIndex);
}
}
public void setTappable(boolean tapabble) {
this.tappable = tapabble;
if (polyline != null) {
polyline.setClickable(tappable);
}
}
public void setGeodesic(boolean geodesic) {
this.geodesic = geodesic;
if (polyline != null) {
polyline.setGeodesic(geodesic);
}
}
public void setLineCap(Cap cap) {
this.lineCap = cap;
if (polyline != null) {
polyline.setStartCap(cap);
polyline.setEndCap(cap);
}
this.applyPattern();
}
public void setLineDashPattern(ReadableArray patternValues) {
this.patternValues = patternValues;
this.applyPattern();
}
private void applyPattern() {
if (patternValues == null) {
return;
}
this.pattern = new ArrayList<>(patternValues.size());
for (int i = 0; i < patternValues.size(); i++) {
float patternValue = (float) patternValues.getDouble(i);
boolean isGap = i % 2 != 0;
if (isGap) {
this.pattern.add(new Gap(patternValue));
} else {
PatternItem patternItem;
boolean isLineCapRound = this.lineCap instanceof RoundCap;
if (isLineCapRound) {
patternItem = new Dot();
} else {
patternItem = new Dash(patternValue);
}
this.pattern.add(patternItem);
}
}
if (polyline != null) {
polyline.setPattern(this.pattern);
}
}
public PolylineOptions getPolylineOptions() {
if (polylineOptions == null) {
polylineOptions = createPolylineOptions();
}
return polylineOptions;
}
private PolylineOptions createPolylineOptions() {
PolylineOptions options = new PolylineOptions();
options.addAll(coordinates);
options.color(color);
options.width(width);
options.geodesic(geodesic);
options.zIndex(zIndex);
options.startCap(lineCap);
options.endCap(lineCap);
options.pattern(this.pattern);
return options;
}
@Override
public Object getFeature() {
return polyline;
}
@Override
public void addToMap(Object collection) {
PolylineManager.Collection polylineCollection = (PolylineManager.Collection) collection;
polyline = polylineCollection.addPolyline(getPolylineOptions());
polyline.setClickable(this.tappable);
if (spans != null){
polyline.setSpans(spans);
}
}
@Override
public void removeFromMap(Object collection) {
PolylineManager.Collection polylineCollection = (PolylineManager.Collection) collection;
polylineCollection.remove(polyline);
}
public void setLineCap(String lineCap) {
Cap cap;
switch (lineCap) {
case "round":
cap = new RoundCap();
break;
case "square":
cap = new SquareCap();
break;
case "butt":
default:
cap = new ButtCap();
break;
}
setLineCap(cap);
}
public static Map<String, Object> getExportedCustomBubblingEventTypeConstants() {
MapBuilder.Builder<String, Object> builder = MapBuilder.builder();
builder.put(OnPressEvent.EVENT_NAME, MapBuilder.of("registrationName", OnPressEvent.EVENT_NAME));
return builder.build();
}
public void setLineJoin(String lineJoin) {
int type;
switch (lineJoin) {
case "round":
type = JointType.ROUND;
break;
case "bevel":
type = JointType.BEVEL;
break;
case "miter":
default:
type = JointType.DEFAULT;
break;
}
if (polyline != null) {
polyline.setJointType(type);
}
}
}

View File

@@ -0,0 +1,92 @@
package com.rnmaps.maps;
import android.content.Context;
import android.graphics.Color;
import android.util.DisplayMetrics;
import android.view.WindowManager;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.google.android.gms.maps.model.ButtCap;
import com.google.android.gms.maps.model.Cap;
import com.google.android.gms.maps.model.RoundCap;
import com.google.android.gms.maps.model.SquareCap;
import java.util.Map;
public class MapPolylineManager extends ViewGroupManager<MapPolyline> {
private final DisplayMetrics metrics;
public MapPolylineManager(ReactApplicationContext reactContext) {
super();
metrics = new DisplayMetrics();
((WindowManager) reactContext.getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay()
.getRealMetrics(metrics);
}
@Override
public String getName() {
return "AIRMapPolyline";
}
@Override
public MapPolyline createViewInstance(ThemedReactContext context) {
return new MapPolyline(context);
}
@ReactProp(name = "coordinates")
public void setCoordinate(MapPolyline view, ReadableArray coordinates) {
view.setCoordinates(coordinates);
}
@ReactProp(name = "strokeWidth", defaultFloat = 1f)
public void setStrokeWidth(MapPolyline view, float widthInPoints) {
float widthInScreenPx = metrics.density * widthInPoints; // done for parity with iOS
view.setWidth(widthInScreenPx);
}
@ReactProp(name = "strokeColor", defaultInt = Color.RED, customType = "Color")
public void setStrokeColor(MapPolyline view, int color) {
view.setColor(color);
}
@ReactProp(name = "tappable", defaultBoolean = false)
public void setTappable(MapPolyline view, boolean tapabble) {
view.setTappable(tapabble);
}
@ReactProp(name = "geodesic", defaultBoolean = false)
public void setGeodesic(MapPolyline view, boolean geodesic) {
view.setGeodesic(geodesic);
}
@ReactProp(name = "zIndex", defaultFloat = 1.0f)
public void setZIndex(MapPolyline view, float zIndex) {
view.setZIndex(zIndex);
}
@ReactProp(name = "lineCap")
public void setlineCap(MapPolyline view, String lineCap) {
view.setLineCap(lineCap);
}
@ReactProp(name = "lineDashPattern")
public void setLineDashPattern(MapPolyline view, ReadableArray patternValues) {
view.setLineDashPattern(patternValues);
}
@Override
@Nullable
public Map getExportedCustomDirectEventTypeConstants() {
return MapBuilder.of(
"onPress", MapBuilder.of("registrationName", "onPress")
);
}
}

View File

@@ -0,0 +1,494 @@
package com.rnmaps.maps;
import android.content.Context;
import android.util.Log;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.Future;
import java.util.List;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import androidx.work.OneTimeWorkRequest;
import androidx.work.WorkManager;
import androidx.work.Data;
import androidx.work.Constraints;
import androidx.work.NetworkType;
import androidx.work.ExistingWorkPolicy;
import androidx.work.Operation;
import androidx.work.WorkInfo;
import com.google.android.gms.maps.model.Tile;
import com.google.android.gms.maps.model.TileProvider;
import com.google.android.gms.maps.model.UrlTileProvider;
import java.lang.System;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
public class MapTileProvider implements TileProvider {
class AIRMapUrlTileProvider extends UrlTileProvider {
private String urlTemplate;
public AIRMapUrlTileProvider(int width, int height, String urlTemplate) {
super(width, height);
this.urlTemplate = urlTemplate;
}
@Override
public URL getTileUrl(int x, int y, int zoom) {
if (MapTileProvider.this.flipY) {
y = (1 << zoom) - y - 1;
}
String s = this.urlTemplate
.replace("{x}", Integer.toString(x))
.replace("{y}", Integer.toString(y))
.replace("{z}", Integer.toString(zoom));
URL url;
if(MapTileProvider.this.maximumZ > 0 && zoom > MapTileProvider.this.maximumZ) {
return null;
}
if(MapTileProvider.this.minimumZ > 0 && zoom < MapTileProvider.this.minimumZ) {
return null;
}
try {
url = new URL(s);
} catch (MalformedURLException e) {
throw new AssertionError(e);
}
return url;
}
public void setUrlTemplate(String urlTemplate) {
this.urlTemplate = urlTemplate;
}
}
protected static final int BUFFER_SIZE = 16 * 1024;
protected static final int TARGET_TILE_SIZE = 512;
protected UrlTileProvider tileProvider;
protected String urlTemplate;
protected int tileSize;
protected boolean doubleTileSize;
protected int maximumZ;
protected int maximumNativeZ;
protected int minimumZ;
protected boolean flipY;
protected String tileCachePath;
protected int tileCacheMaxAge;
protected boolean offlineMode;
protected Context context;
protected boolean customMode;
public MapTileProvider(int tileSizet, boolean doubleTileSize, String urlTemplate,
int maximumZ, int maximumNativeZ, int minimumZ, boolean flipY, String tileCachePath,
int tileCacheMaxAge, boolean offlineMode, Context context, boolean customMode) {
this.tileProvider = new AIRMapUrlTileProvider(tileSizet, tileSizet, urlTemplate);
this.tileSize = tileSizet;
this.doubleTileSize = doubleTileSize;
this.urlTemplate = urlTemplate;
this.maximumZ = maximumZ;
this.maximumNativeZ = maximumNativeZ;
this.minimumZ = minimumZ;
this.flipY = flipY;
this.tileCachePath = tileCachePath;
this.tileCacheMaxAge = tileCacheMaxAge;
this.offlineMode = offlineMode;
this.context = context;
this.customMode = customMode;
}
@Override
public Tile getTile(int x, int y, int zoom) {
if (!this.customMode) return this.tileProvider.getTile(x, y, zoom);
byte[] image = null;
int maximumZ = this.maximumZ > 0 ? this.maximumZ : Integer.MAX_VALUE;
if (this.tileSize == 256 && this.doubleTileSize && zoom + 1 <= this.maximumNativeZ && zoom + 1 <= maximumZ) {
Log.d("urlTile", "pullTilesFromHigherZoom");
image = pullTilesFromHigherZoom(x, y, zoom);
}
if (zoom > this.maximumNativeZ) {
Log.d("urlTile", "scaleLowerZoomTile");
image = scaleLowerZoomTile(x, y, zoom, this.maximumNativeZ);
}
if (image == null && zoom <= maximumZ) {
Log.d("urlTile", "getTileImage");
image = getTileImage(x, y, zoom);
}
if (image == null && this.tileCachePath != null && this.offlineMode) {
Log.d("urlTile", "findLowerZoomTileForScaling");
int zoomLevelToStart = (zoom > this.maximumNativeZ) ? this.maximumNativeZ - 1 : zoom - 1;
int minimumZoomToSearch = Math.max(this.minimumZ, zoom - 3);
for (int tryZoom = zoomLevelToStart; tryZoom >= minimumZoomToSearch; tryZoom--) {
image = scaleLowerZoomTile(x, y, zoom, tryZoom);
if (image != null) {
break;
}
}
}
return image == null ? null : new Tile(this.tileSize, this.tileSize, image);
}
byte[] getTileImage(int x, int y, int zoom) {
byte[] image = null;
if (this.tileCachePath != null) {
image = readTileImage(x, y, zoom);
if (image != null) {
Log.d("urlTile", "tile cache HIT for " + zoom +
"/" + x + "/" + y);
} else {
Log.d("urlTile", "tile cache MISS for " + zoom +
"/" + x + "/" + y);
}
if (image != null && !this.offlineMode) {
checkForRefresh(x, y, zoom);
}
}
if (image == null && !this.offlineMode && this.tileCachePath != null) {
String fileName = getTileFilename(x, y, zoom);
Constraints constraints = new Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build();
OneTimeWorkRequest tileRefreshWorkRequest = new OneTimeWorkRequest.Builder(MapTileWorker.class)
.setConstraints(constraints)
.addTag(fileName)
.setInputData(
new Data.Builder()
.putString("url", getTileUrl(x, y, zoom).toString())
.putString("filename", fileName)
.putInt("maxAge", -1)
.build()
)
.build();
WorkManager workManager = WorkManager.getInstance(this.context.getApplicationContext());
Operation fetchOperation = workManager
.enqueueUniqueWork(fileName, ExistingWorkPolicy.KEEP, tileRefreshWorkRequest);
Future<Operation.State.SUCCESS> operationFuture = fetchOperation.getResult();
try {
operationFuture.get(1L, TimeUnit.SECONDS);
Thread.sleep(500);
Future<List<WorkInfo>> fetchFuture = workManager.getWorkInfosByTag(fileName);
List<WorkInfo> workInfo = fetchFuture.get(1L, TimeUnit.SECONDS);
Log.d("urlTile: ", workInfo.get(0).toString());
if (this.tileCachePath != null) {
image = readTileImage(x, y, zoom);
if (image != null) {
Log.d("urlTile","tile cache fetch HIT for " + zoom +
"/" + x + "/" + y);
} else {
Log.d("urlTile","tile cache fetch MISS for " + zoom +
"/" + x + "/" + y);
}
}
} catch (Exception e) {
e.printStackTrace();
}
} else if (image == null && !this.offlineMode) {
Log.d("urlTile", "Normal fetch");
image = fetchTile(x, y, zoom);
if (image == null) {
Log.d("urlTile", "tile fetch TIMEOUT / FAIL for " + zoom +
"/" + x + "/" + y);
}
}
return image;
}
byte[] pullTilesFromHigherZoom(int x, int y, int zoom) {
byte[] data;
Bitmap image = getNewBitmap();
Canvas canvas = new Canvas(image);
Paint paint = new Paint();
x = x * 2;
y = y * 2;
byte[] leftTop = getTileImage(x, y, zoom + 1);
byte[] leftBottom = getTileImage(x, y + 1, zoom + 1);
byte[] rightTop = getTileImage(x + 1, y, zoom + 1);
byte[] rightBottom = getTileImage(x + 1, y + 1, zoom + 1);
if (leftTop == null || leftBottom == null || rightTop == null || rightBottom == null) {
return null;
}
Bitmap bitmap;
bitmap = BitmapFactory.decodeByteArray(leftTop, 0, leftTop.length);
canvas.drawBitmap(bitmap, 0, 0, paint);
bitmap.recycle();
bitmap = BitmapFactory.decodeByteArray(leftBottom, 0, leftBottom.length);
canvas.drawBitmap(bitmap, 0, 256, paint);
bitmap.recycle();
bitmap = BitmapFactory.decodeByteArray(rightTop, 0, rightTop.length);
canvas.drawBitmap(bitmap, 256, 0, paint);
bitmap.recycle();
bitmap = BitmapFactory.decodeByteArray(rightBottom, 0, rightBottom.length);
canvas.drawBitmap(bitmap, 256, 256, paint);
bitmap.recycle();
data = bitmapToByteArray(image);
image.recycle();
return data;
}
Bitmap getNewBitmap() {
Bitmap image = Bitmap.createBitmap(TARGET_TILE_SIZE, TARGET_TILE_SIZE, Bitmap.Config.ARGB_8888);
image.eraseColor(Color.TRANSPARENT);
return image;
}
byte[] bitmapToByteArray(Bitmap bm) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
bm.compress(Bitmap.CompressFormat.PNG, 100, bos);
byte[] data = bos.toByteArray();
try {
bos.close();
} catch (Exception e) {
e.printStackTrace();
}
return data;
}
byte[] scaleLowerZoomTile(int x, int y, int zoom, int maximumZoom) {
int overZoomLevel = zoom - maximumZoom;
int zoomFactor = 1 << overZoomLevel;
int xParent = x >> overZoomLevel;
int yParent = y >> overZoomLevel;
int zoomParent = zoom - overZoomLevel;
int xOffset = x % zoomFactor;
int yOffset = y % zoomFactor;
byte[] data;
Bitmap image = getNewBitmap();
Canvas canvas = new Canvas(image);
Paint paint = new Paint();
data = getTileImage(xParent, yParent, zoomParent);
if (data == null) return null;
Bitmap sourceImage;
sourceImage = BitmapFactory.decodeByteArray(data, 0, data.length);
int subTileSize = this.tileSize / zoomFactor;
Rect sourceRect = new Rect(xOffset * subTileSize, yOffset * subTileSize, xOffset * subTileSize + subTileSize , yOffset * subTileSize + subTileSize);
Rect targetRect = new Rect(0,0,TARGET_TILE_SIZE, TARGET_TILE_SIZE);
canvas.drawBitmap(sourceImage, sourceRect, targetRect, paint);
sourceImage.recycle();
data = bitmapToByteArray(image);
image.recycle();
return data;
}
void checkForRefresh(int x, int y, int zoom) {
String fileName = getTileFilename(x, y, zoom);
File file = new File(fileName);
long lastModified = file.lastModified();
long now = System.currentTimeMillis();
if ((now - lastModified) / 1000 > this.tileCacheMaxAge) {
Log.d("urlTile", "Refreshing");
Constraints constraints = new Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build();
OneTimeWorkRequest tileRefreshWorkRequest = new OneTimeWorkRequest.Builder(MapTileWorker.class)
.setConstraints(constraints)
.addTag(fileName)
.setInputData(
new Data.Builder()
.putString("url", getTileUrl(x, y, zoom).toString())
.putString("filename", fileName)
.putInt("maxAge", this.tileCacheMaxAge)
.build()
)
.build();
WorkManager.getInstance(this.context.getApplicationContext())
.enqueueUniqueWork(fileName, ExistingWorkPolicy.KEEP, tileRefreshWorkRequest);
}
}
byte[] fetchTile(int x, int y, int zoom) {
URL url = getTileUrl(x, y, zoom);
ByteArrayOutputStream buffer = null;
InputStream in = null;
try {
URLConnection conn = url.openConnection();
in = conn.getInputStream();
buffer = new ByteArrayOutputStream();
int nRead;
byte[] data = new byte[BUFFER_SIZE];
while ((nRead = in.read(data, 0, BUFFER_SIZE)) != -1) {
buffer.write(data, 0, nRead);
}
buffer.flush();
return buffer.toByteArray();
} catch (IOException | OutOfMemoryError e) {
e.printStackTrace();
return null;
} finally {
if (in != null) try { in.close(); } catch (Exception ignored) {}
if (buffer != null) try { buffer.close(); } catch (Exception ignored) {}
}
}
byte[] readTileImage(int x, int y, int zoom) {
InputStream in = null;
ByteArrayOutputStream buffer = null;
String fileName = getTileFilename(x, y, zoom);
if (fileName == null) {
return null;
}
File file = new File(fileName);
try {
in = new FileInputStream(file);
buffer = new ByteArrayOutputStream();
int nRead;
byte[] data = new byte[BUFFER_SIZE];
while ((nRead = in.read(data, 0, BUFFER_SIZE)) != -1) {
buffer.write(data, 0, nRead);
}
buffer.flush();
if (this.tileCacheMaxAge == 0) {
file.setLastModified(System.currentTimeMillis());
}
return buffer.toByteArray();
} catch (IOException | OutOfMemoryError e) {
e.printStackTrace();
return null;
} finally {
if (in != null) try { in.close(); } catch (Exception ignored) {}
if (buffer != null) try { buffer.close(); } catch (Exception ignored) {}
}
}
boolean writeTileImage(byte[] image, int x, int y, int zoom) {
OutputStream out = null;
String fileName = getTileFilename(x, y, zoom);
if (fileName == null) {
return false;
}
try {
File file = new File(fileName);
file.getParentFile().mkdirs();
out = new FileOutputStream(file);
out.write(image);
return true;
} catch (IOException | OutOfMemoryError e) {
e.printStackTrace();
return false;
} finally {
if (out != null) try { out.close(); } catch (Exception ignored) {}
}
}
String getTileFilename(int x, int y, int zoom) {
if (this.tileCachePath == null) {
return null;
}
return this.tileCachePath + '/' + zoom +
"/" + x + "/" + y;
}
protected URL getTileUrl(int x, int y, int zoom) {
return this.tileProvider.getTileUrl(x, y, zoom);
}
public void setUrlTemplate(String urlTemplate) {
if (this.urlTemplate != urlTemplate) {
this.tileProvider = new AIRMapUrlTileProvider(tileSize, tileSize, urlTemplate);
}
this.urlTemplate = urlTemplate;
}
public void setTileSize(int tileSize) {
if (this.tileSize != tileSize) {
this.tileProvider = new AIRMapUrlTileProvider(tileSize, tileSize, urlTemplate);
}
this.tileSize = tileSize;
}
public void setDoubleTileSize(boolean doubleTileSize) {
this.doubleTileSize = doubleTileSize;
}
public void setMaximumZ(int maximumZ) {
this.maximumZ = maximumZ;
}
public void setMaximumNativeZ(int maximumNativeZ) {
this.maximumNativeZ = maximumNativeZ;
}
public void setMinimumZ(int minimumZ) {
this.minimumZ = minimumZ;
}
public void setFlipY(boolean flipY) {
this.flipY = flipY;
}
public void setTileCachePath(String tileCachePath) {
this.tileCachePath = tileCachePath;
}
public void setTileCacheMaxAge(int tileCacheMaxAge) {
this.tileCacheMaxAge = tileCacheMaxAge;
}
public void setOfflineMode(boolean offlineMode) {
this.offlineMode = offlineMode;
}
public void setCustomMode() {
}
}

View File

@@ -0,0 +1,115 @@
package com.rnmaps.maps;
import android.content.Context;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
public class MapTileWorker extends Worker {
private static final int BUFFER_SIZE = 16 * 1024;
public MapTileWorker(
@NonNull Context context,
@NonNull WorkerParameters params) {
super(context, params);
}
@Override
public Result doWork() {
byte[] image;
URL url;
String fileName = getInputData().getString("filename");
try {
int tileCacheMaxAge = getInputData().getInt("maxAge", 0);
if (tileCacheMaxAge >= 0) {
File file = new File(fileName);
long lastModified = file.lastModified();
long now = System.currentTimeMillis();
if ((now - lastModified) / 1000 < tileCacheMaxAge) return Result.failure();
}
} catch (Error e) {
return Result.failure();
}
try {
url = new URL(getInputData().getString("url"));
} catch (MalformedURLException e) {
throw new AssertionError(e);
}
image = fetchTile(url);
if (image != null) {
boolean success = writeTileImage(image, fileName);
if (!success) {
return Result.failure();
}
} else {
return Result.retry();
}
// Indicate whether the work finished successfully with the Result
Log.d("urlTile", "Worker fetched " + fileName);
return Result.success();
}
private byte[] fetchTile(URL url) {
ByteArrayOutputStream buffer = null;
InputStream in = null;
try {
in = url.openStream();
buffer = new ByteArrayOutputStream();
int nRead;
byte[] data = new byte[BUFFER_SIZE];
while ((nRead = in.read(data, 0, BUFFER_SIZE)) != -1) {
buffer.write(data, 0, nRead);
}
buffer.flush();
return buffer.toByteArray();
} catch (IOException | OutOfMemoryError e) {
e.printStackTrace();
return null;
} finally {
if (in != null) try { in.close(); } catch (Exception ignored) {}
if (buffer != null) try { buffer.close(); } catch (Exception ignored) {}
}
}
private boolean writeTileImage(byte[] image, String fileName) {
OutputStream out = null;
if (fileName == null) {
return false;
}
try {
File file = new File(fileName);
file.getParentFile().mkdirs();
out = new FileOutputStream(file);
out.write(image);
return true;
} catch (IOException | OutOfMemoryError e) {
e.printStackTrace();
return false;
} finally {
if (out != null) try { out.close(); } catch (Exception ignored) {}
}
}
}

Some files were not shown because too many files have changed in this diff Show More