1import * as Maps from 'expo-maps';
2import React, { useContext, useRef, useState } from 'react';
3import {
4  StyleSheet,
5  View,
6  Text,
7  Platform,
8  TextInput,
9  TouchableOpacity,
10  ScrollView,
11  KeyboardAvoidingView,
12} from 'react-native';
13import { Snackbar } from 'react-native-paper';
14
15import ProviderContext from '../context/ProviderContext';
16
17export default function MapMoveExample() {
18  const provider = useContext(ProviderContext);
19  const ref = useRef<Maps.ExpoMap>(null);
20
21  const [snackbarText, setSnackbarText] = useState<string | undefined>();
22  const [animateCamera, setAnimateCamera] = useState<boolean>(true);
23  const [animationDuration, setAnimationDuration] = useState<number>(1000);
24  const [zoom, setZoom] = useState<number>(4);
25  const [bearing, setBearing] = useState<number>(0);
26  const [tilt, setTilt] = useState<number>(0);
27  return (
28    <KeyboardAvoidingView
29      behavior="position"
30      contentContainerStyle={{ flex: 1 }}
31      style={{ flex: 1 }}
32      keyboardVerticalOffset={92}
33      enabled={Platform.OS === 'ios'}>
34      <View style={styles.container}>
35        <Maps.ExpoMap
36          style={{
37            flex: 1,
38            width: '100%',
39            overflow: 'hidden',
40          }}
41          ref={ref}
42          provider={provider}
43          onMapPress={async (event) => {
44            await ref.current
45              ?.moveCamera({
46                target: {
47                  latitude:
48                    event.nativeEvent.latitude ??
49                    // @ts-ignore
50                    // .payload won't be necessary when the Record conversion for iOS gets implemented
51                    event.nativeEvent.payload.latitude,
52                  longitude:
53                    event.nativeEvent.longitude ??
54                    // @ts-ignore
55                    event.nativeEvent.payload.longitude,
56                },
57                bearing,
58                tilt,
59                zoom,
60                duration: animationDuration,
61                animate: animateCamera,
62              })
63              .then((result) => setSnackbarText('Move ended at:' + JSON.stringify(result)));
64          }}
65        />
66        <Snackbar
67          visible={snackbarText !== undefined}
68          onDismiss={() => setSnackbarText(undefined)}
69          style={{ backgroundColor: 'white' }}
70          wrapperStyle={styles.snackbar}
71          duration={2000}>
72          <Text style={{ color: 'black' }}>{snackbarText}</Text>
73        </Snackbar>
74        <ScrollView
75          style={{ maxHeight: 200, width: '100%' }}
76          contentContainerStyle={{ flexGrow: 1, margin: 0, padding: 0 }}>
77          <View style={[styles.controls]}>
78            <Text style={styles.title}>Press on the map to move the camera!</Text>
79
80            <TouchableOpacity onPress={() => setAnimateCamera(!animateCamera)}>
81              <View
82                style={[
83                  styles.button,
84                  styles.shadow,
85                  { backgroundColor: animateCamera ? 'lightgreen' : '#FF7F7F' },
86                ]}>
87                <Text>Animate Camera:{animateCamera ? 'On' : 'Off'}</Text>
88              </View>
89            </TouchableOpacity>
90
91            {animateCamera && (
92              <Input
93                valueText="Animation Duration: "
94                initialValue="1000"
95                onValueChange={(value) => setAnimationDuration(parseInt(value, 10))}
96              />
97            )}
98            <View style={{ flexDirection: 'row' }}>
99              <Input
100                valueText="Zoom: "
101                initialValue="4"
102                onValueChange={(value) => setZoom(parseFloat(value))}
103              />
104              <Input
105                valueText="Bearing: "
106                initialValue="0"
107                onValueChange={(value) => setBearing(parseFloat(value))}
108              />
109            </View>
110            <Input
111              valueText="Tilt: "
112              initialValue="0"
113              onValueChange={(value) => setTilt(parseFloat(value))}
114            />
115          </View>
116        </ScrollView>
117      </View>
118    </KeyboardAvoidingView>
119  );
120}
121
122const styles = StyleSheet.create({
123  container: {
124    flex: 1,
125    backgroundColor: 'white',
126  },
127  controls: {
128    flex: 1,
129    backgroundColor: 'white',
130    alignItems: 'center',
131  },
132  snackbar: {
133    top: 0,
134  },
135  eventsList: {
136    padding: 20,
137  },
138  title: {
139    fontSize: 18,
140    fontWeight: '400',
141    paddingVertical: 20,
142  },
143  button: {
144    paddingVertical: 12,
145    paddingHorizontal: 20,
146    borderRadius: 30,
147    alignItems: 'center',
148    marginBottom: 10,
149  },
150  inputContainer: {
151    alignItems: 'center',
152    paddingVertical: 12,
153    paddingHorizontal: 20,
154    borderRadius: 30,
155  },
156  input: {
157    borderColor: 'lightgray',
158    borderWidth: 1,
159    borderRadius: 5,
160    paddingVertical: 5,
161    paddingHorizontal: 10,
162    textAlign: 'center',
163  },
164  shadow: {
165    shadowOpacity: 0.1,
166    shadowRadius: 5,
167    elevation: 10,
168    shadowColor: 'rgba(0,0,0,0.56)',
169  },
170});
171
172const Input = (props: {
173  valueText: string;
174  initialValue: string;
175  onValueChange: (value: string) => void;
176}) => (
177  <View style={styles.inputContainer}>
178    <View style={{ flexDirection: 'row', alignItems: 'center' }}>
179      <Text>{props.valueText}</Text>
180      <TextInput
181        style={styles.input}
182        onChangeText={(text) => props.onValueChange(text)}
183        defaultValue={props.initialValue}
184        keyboardType="decimal-pad"
185      />
186    </View>
187  </View>
188);
189