From 0c9db775302d15483385f0621611583e3a2407cd Mon Sep 17 00:00:00 2001 From: Alexander NeonXP Kiryukhin Date: Tue, 14 May 2019 01:05:07 +0300 Subject: Initial commit --- src/Components/Login.tsx | 71 ++++++++++++++++ src/Components/Map.tsx | 180 +++++++++++++++++++++++++++++++++++++++++ src/Components/MapObjects.tsx | 90 +++++++++++++++++++++ src/Components/MapOverlay.tsx | 91 +++++++++++++++++++++ src/Components/PortalPanel.tsx | 70 ++++++++++++++++ 5 files changed, 502 insertions(+) create mode 100644 src/Components/Login.tsx create mode 100644 src/Components/Map.tsx create mode 100644 src/Components/MapObjects.tsx create mode 100644 src/Components/MapOverlay.tsx create mode 100644 src/Components/PortalPanel.tsx (limited to 'src/Components') diff --git a/src/Components/Login.tsx b/src/Components/Login.tsx new file mode 100644 index 0000000..6fe7957 --- /dev/null +++ b/src/Components/Login.tsx @@ -0,0 +1,71 @@ +import React, { Component } from 'react'; +import { StyleSheet, Text, View, WebView } from 'react-native'; + +import { connect } from 'redux-su'; +import { getBottomSpace, getStatusBarHeight } from '../helper'; +import actions from '../Actions/actions'; + +const LOGIN_URL = "https://accounts.google.com/ServiceLogin?service=ah&passive=true&continue=https://appengine.google.com/_ah/conflogin%3Fcontinue%3Dhttps://intel.ingress.com/"; +const HOME_URL = "https://intel.ingress.com/"; + +var styles = StyleSheet.create({ + container: { + flexGrow: 1, + } +}); + +type Props = { + login(csrf: string, v: string): void + actions: any + auth: any +} + +type State = { + v: string + onIngress: boolean +} + +class Login extends Component { + webview!: WebView; + constructor(props: Props) { + super(props); + this.state = { v: "", onIngress: false } + } + + onNavigationStateChange(navState: WebViewNavigation) { + if (navState.url == HOME_URL) { + this.setState({ onIngress: true }) + this.props.actions.login() + } + } + + renderLogin() { + if (this.state.onIngress) { + return (Пожалуйста, подождите...); + } + return ( + <> + r && (this.webview = r)} + automaticallyAdjustContentInsets={false} + thirdPartyCookiesEnabled + useWebKit + style={styles.container} + source={{ uri: LOGIN_URL }} + javaScriptEnabled={true} + onNavigationStateChange={this.onNavigationStateChange.bind(this)} + startInLoadingState={true} + /> + + ); + } + + render() { + console.log(this.props) + return ( + {this.renderLogin()} + ); + } +} + +export default connect({ 'auth': 'auth' }, actions)(Login) \ No newline at end of file diff --git a/src/Components/Map.tsx b/src/Components/Map.tsx new file mode 100644 index 0000000..3e878f8 --- /dev/null +++ b/src/Components/Map.tsx @@ -0,0 +1,180 @@ +import React, { Component } from 'react'; +import { StyleSheet, View, Dimensions, ActivityIndicator } from 'react-native'; +import MapView, { Marker, Region, UrlTile } from 'react-native-maps'; +import { connect } from 'redux-su'; +import { NavigationActions } from 'react-navigation'; + +import MapOverlay from './MapOverlay'; +import MapObjects from './MapObjects'; +import PortalPanel from './PortalPanel'; +import { getBottomSpace } from '../helper'; +import actions from '../Actions/actions'; +import { LatLng } from '../Api/interfaces'; + +const { width, height } = Dimensions.get("screen") +const draggableRange = { + top: height / 1.75, + bottom: 120 + getBottomSpace() +} + +type Props = any +type State = any +class Map extends Component { + refresh: number | undefined + map!: MapView; + constructor(props: Props) { + super(props) + this.state = { + user: undefined, + followUser: true, + zoom: 15, + region: null, + loading: false, + objects: { links: {}, fields: {}, portals: {}, loaded: false }, + } + } + componentWillMount() { + navigator.geolocation.getCurrentPosition( + position => { + this.setState({ + followUser: true, + }); + }, + error => alert(error.message), + { enableHighAccuracy: true, timeout: 20000, maximumAge: 1000 } + ); + navigator.geolocation.watchPosition( + position => { + this.setState({ + user: { + latitude: position.coords.latitude, + longitude: position.coords.longitude + }, + }); + }, + error => alert(error.message), + { enableHighAccuracy: true, timeout: 20000, maximumAge: 1000 } + ) + } + onRegionChange = (region: Region) => { + const zoom = this.getZoomByRegion(width, region) + this.setState({ region, zoom }) + setImmediate(() => this.load(false)) + } + getZoomByRegion(width: number, region: Region): number { + return Math.ceil(Math.log2(360 * ((width / 256) / region.longitudeDelta))) + 1 + } + load = async (refresh: boolean) => { + if (this.state.region != null && this.props.entities.loading == 0) { + this.props.actions.update(this.state.region, width, refresh) + } + return null + } + + refreshByTime = () => { + setTimeout(() => { + this.load(true).then(this.refreshByTime) + }, 30000) + } + + componentDidMount() { + this.refreshByTime() + } + + onPortalClick = (guid: string, coords: LatLng) => { + this.setState({ selectedPortal: { guid, coords } }) + } + + onOpenPortal = (guid: string, coords: LatLng) => { + const navigateAction = NavigationActions.navigate({ + routeName: 'Portal', + params: { guid, coords }, + }); + this.props.navigation.dispatch(navigateAction); + } + + render() { + if (!this.state.user) { + return + } + const camera = { + center: this.state.user, + altitude: 1000, + heading: 0, + pitch: 30, + zoom: 15, + } + const goToMe = () => { + this.map.animateToCoordinate(this.state.user) + } + const refresh = () => { + setImmediate(() => this.load(true)) + } + return ( + <> + (r != null) ? this.map = r : null} + style={styles.container} + initialRegion={{ ...this.state.user, latitudeDelta: 0.002, longitudeDelta: 0.002 }} + onRegionChangeComplete={this.onRegionChange} + showsCompass={false} + showsScale + showsUserLocation + showsMyLocationButton + loadingEnabled + type={'hybrid'} + > + + {this.state.selectedPortal && } + + + + ); + } +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, + spinnerContainer: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, + panel: { + flex: 1, + backgroundColor: '#fff', + position: 'relative', + borderTopLeftRadius: 20, + borderTopRightRadius: 20, + //ios + shadowOpacity: 0.3, + shadowRadius: 3, + shadowOffset: { + height: 0, + width: 0 + }, + //android + elevation: 1 + }, + panelHeader: { + height: 40, + alignItems: 'center', + justifyContent: 'center', + }, + dash: { + backgroundColor: 'rgba(200,200,200,0.9)', + height: 6, + width: 30, + borderRadius: 3 + } +}); + +export default connect({ 'entities': 'entities' }, actions)(Map) \ No newline at end of file diff --git a/src/Components/MapObjects.tsx b/src/Components/MapObjects.tsx new file mode 100644 index 0000000..1d2fb12 --- /dev/null +++ b/src/Components/MapObjects.tsx @@ -0,0 +1,90 @@ +import React, { ReactChild } from 'react'; +import { View, StyleSheet, Text } from 'react-native'; +import { Polyline, Polygon, Marker, Region } from 'react-native-maps'; +import Icon from 'react-native-vector-icons/FontAwesome'; +import { Link, Portal, Field } from '../Api/types'; +import { connect } from 'react-redux'; +import { COLORS, COLORS_LVL } from '../colors'; + +const fillPortalColor = { "R": "rgba(2, 140, 227, 0.5)", "E": "rgba(38, 205, 30, 0.5)", "N": "rgba(139, 0, 255, 0.5)" } +// const fillPortalColor = { "R": COLORS[1], "E": COLORS[2], "N": COLORS[0] } +const fractColor = { "R": "#028ce3", "E": "#26cd1e", "N": "#8b00ff" } +// const fractColor = { "R": COLORS[1], "E": COLORS[2], "N": COLORS[0] } +const fieldColor = { "R": "rgba(2, 140, 227, 0.1)", "E": "rgba(38, 205, 30, 0.1)", "N": "rgba(139, 0, 255, 0.1)" } + +type Props = { + portals: { [guid: string]: Portal } + links: { [guid: string]: Link } + fields: { [guid: string]: Field } + onPortalClick: Function + zoom: number +} + +const MapObjects = (props: Props) => { + const renderPortal = Object.keys(props.portals).map(guid => drawPortal(guid, props.portals[guid], props.zoom, props.onPortalClick)) + const renderField = Object.keys(props.fields).map(guid => drawField(guid, props.fields[guid])) + const renderLink = Object.keys(props.links).map(guid => drawLink(guid, props.links[guid])) + + if (props.zoom <= 14) { + return [...renderField, ...renderLink] + } else { + return [...renderField, ...renderLink, ...renderPortal] + } +} +const drawPortal = (guid: string, entity: Portal, zoom: number, onPortalClick: Function) => { + const size = (zoom) * 1.5 + return ( onPortalClick(guid, entity.coords)} + > + {/* */} + + {entity.level} + + ); +} +const drawField = (guid: string, entity: Field) => { + return +} +const drawLink = (guid: string, entity: Link) => { + return +} + + +const styles = StyleSheet.create({ + +}); + +export default connect((store) => { + const display = store.entities.display + const portals = {} + const fields = {} + const links = {} + display.portals.forEach(guid => { portals[guid] = store.entities.portals[guid] }) + display.fields.forEach(guid => { fields[guid] = store.entities.fields[guid] }) + display.links.forEach(guid => { links[guid] = store.entities.links[guid] }) + return { + portals, fields, links + } +}, {})(MapObjects) \ No newline at end of file diff --git a/src/Components/MapOverlay.tsx b/src/Components/MapOverlay.tsx new file mode 100644 index 0000000..30160c9 --- /dev/null +++ b/src/Components/MapOverlay.tsx @@ -0,0 +1,91 @@ +import React from 'react'; +import { StyleSheet, View, Text, GestureResponderEvent, ActivityIndicator, Linking } from 'react-native'; +import { FontAwesome } from '@expo/vector-icons'; +import { getStatusBarHeight } from '../helper'; +import { LatLng } from '../Api/interfaces'; + +type Props = { + goToMe: (e: GestureResponderEvent) => void; + refresh: (e: GestureResponderEvent) => void; + onOpenPortal: (guid: string, coords: LatLng) => void; + loading: Number; + selectedPortal: any; +} + +export default (props: Props) => ( + + + + + + + + {props.selectedPortal ? ( + <> + + { + props.onOpenPortal(props.selectedPortal.guid, props.selectedPortal.coords) + }} + iconStyle={styles.icon} + color={"#000"} + size={24} + backgroundColor={"#e3e3e3"} + /> + + + Linking.openURL(`geo:${props.selectedPortal.coords.latitude},${props.selectedPortal.coords.longitude}`)} + iconStyle={styles.icon} + color={"#000"} + size={24} + backgroundColor={"#e3e3e3"} + /> + + + ) : null} + + + {props.loading} + + {props.loading > 0 ? ( + + + + ) : null} + +); + +const styles = StyleSheet.create({ + overlay: { + position: 'absolute', + top: 8 + getStatusBarHeight(true), + right: 8, + }, + button: { + margin: 8, + }, + icon: { + marginRight: 0, + }, + loader: { + margin: 8, + paddingHorizontal: 8, + } +}); \ No newline at end of file diff --git a/src/Components/PortalPanel.tsx b/src/Components/PortalPanel.tsx new file mode 100644 index 0000000..6cf19c2 --- /dev/null +++ b/src/Components/PortalPanel.tsx @@ -0,0 +1,70 @@ +import React, { Component, PureComponent } from 'react'; +import { StyleSheet, View, Text, GestureResponderEvent, ActivityIndicator } from 'react-native'; +// import { Button } from 'react-native-vector-icons/FontAwesome'; +import Reactotron from 'reactotron-react-native' +import { getStatusBarHeight } from '../helper'; +import { connect } from 'react-redux'; +import actions from '../Actions/actions'; +import { Portal } from '../Api/types'; +import { bindActionCreators } from 'redux'; + +type Props = { + guid: string + portal?: Portal +} + +class PortalPanel extends PureComponent { + static navigationOptions = ({ navigation }) => { + return { + title: navigation.getParam('title', 'Загрузка...'), + }; + }; + componentWillMount() { + this.props.navigation.setParams({title: this.props.portal.name}) + } + componentDidMount() { + this.props.getPortalDetails(this.props.guid) + } + componentWillReceiveProps(next: Props) { + if (next.guid != this.props.guid) { + this.props.navigation.setParams({title: this.props.portal.name}) + this.props.getPortalDetails(next.guid) + } + } + render() { + const { portal } = this.props + if (!portal) { + return + } + return ( + + {portal.name} + Уровeнь: {portal.level}, здоровье: {portal.power} + {JSON.stringify(this.props)} + + ); + } +} + +export default connect((store, props: Props) => { + const guid = props.navigation.getParam('guid') + return { + portal: store.entities.portals[guid], + guid + } +}, (dispatch) => bindActionCreators(actions, dispatch))(PortalPanel) + +const styles = StyleSheet.create({ + overlay: { + flex: 1, + padding: 8, + }, + title: { + fontWeight: 'bold', + fontSize: 22, + }, + subtitle: { + fontWeight: 'normal', + fontSize: 18, + } +}); \ No newline at end of file -- cgit v1.2.3