commit feebeffcd9b3017075e41356047dbddfefe3f60e Author: Philip Cheung Date: Tue Feb 27 16:19:55 2024 +0800 edited diff --git a/.babelrc b/.babelrc new file mode 100755 index 0000000..9bf53ab --- /dev/null +++ b/.babelrc @@ -0,0 +1,7 @@ +{ + + "presets": ["react-native","react-native-stage-0"], + + "plugins": ["transform-decorators-legacy"] + +} diff --git a/.buckconfig b/.buckconfig new file mode 100755 index 0000000..934256c --- /dev/null +++ b/.buckconfig @@ -0,0 +1,6 @@ + +[android] + target = Google Inc.:Google APIs:23 + +[maven_repositories] + central = https://repo1.maven.org/maven2 diff --git a/.expo/packager-info.json b/.expo/packager-info.json new file mode 100755 index 0000000..4b6cfa2 --- /dev/null +++ b/.expo/packager-info.json @@ -0,0 +1,3 @@ +{ + "expoServerPort": null +} \ No newline at end of file diff --git a/.expo/settings.json b/.expo/settings.json new file mode 100755 index 0000000..4867041 --- /dev/null +++ b/.expo/settings.json @@ -0,0 +1,7 @@ +{ + "hostType": "tunnel", + "lanType": "ip", + "dev": true, + "minify": false, + "urlRandomness": null +} \ No newline at end of file diff --git a/.flowconfig b/.flowconfig new file mode 100755 index 0000000..3c0adb5 --- /dev/null +++ b/.flowconfig @@ -0,0 +1,67 @@ +[ignore] +; We fork some components by platform +.*/*[.]android.js + +; Ignore "BUCK" generated dirs +/\.buckd/ + +; Ignore unexpected extra "@providesModule" +.*/node_modules/.*/node_modules/fbjs/.* + +; Ignore duplicate module providers +; For RN Apps installed via npm, "Libraries" folder is inside +; "node_modules/react-native" but in the source repo it is in the root +.*/Libraries/react-native/React.js + +; Ignore polyfills +.*/Libraries/polyfills/.* + +; Ignore metro +.*/node_modules/metro/.* + +[include] + +[libs] +node_modules/react-native/Libraries/react-native/react-native-interface.js +node_modules/react-native/flow/ +node_modules/react-native/flow-github/ + +[options] +emoji=true + +module.system=haste +module.system.haste.use_name_reducers=true +# get basename +module.system.haste.name_reducers='^.*/\([a-zA-Z0-9$_.-]+\.js\(\.flow\)?\)$' -> '\1' +# strip .js or .js.flow suffix +module.system.haste.name_reducers='^\(.*\)\.js\(\.flow\)?$' -> '\1' +# strip .ios suffix +module.system.haste.name_reducers='^\(.*\)\.ios$' -> '\1' +module.system.haste.name_reducers='^\(.*\)\.android$' -> '\1' +module.system.haste.name_reducers='^\(.*\)\.native$' -> '\1' +module.system.haste.paths.blacklist=.*/__tests__/.* +module.system.haste.paths.blacklist=.*/__mocks__/.* +module.system.haste.paths.blacklist=/node_modules/react-native/Libraries/Animated/src/polyfills/.* +module.system.haste.paths.whitelist=/node_modules/react-native/Libraries/.* + +munge_underscores=true + +module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub' + +module.file_ext=.js +module.file_ext=.jsx +module.file_ext=.json +module.file_ext=.native.js + +suppress_type=$FlowIssue +suppress_type=$FlowFixMe +suppress_type=$FlowFixMeProps +suppress_type=$FlowFixMeState + +suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) +suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+ +suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy +suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError + +[version] +^0.75.0 diff --git a/.gitattributes b/.gitattributes new file mode 100755 index 0000000..d42ff18 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.pbxproj -text diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..5d64756 --- /dev/null +++ b/.gitignore @@ -0,0 +1,56 @@ +# OSX +# +.DS_Store + +# Xcode +# +build/ +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata +*.xccheckout +*.moved-aside +DerivedData +*.hmap +*.ipa +*.xcuserstate +project.xcworkspace + +# Android/IntelliJ +# +build/ +.idea +.gradle +local.properties +*.iml + +# node.js +# +node_modules/ +npm-debug.log +yarn-error.log + +# BUCK +buck-out/ +\.buckd/ +*.keystore + +# fastlane +# +# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the +# screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/ + +*/fastlane/report.xml +*/fastlane/Preview.html +*/fastlane/screenshots + +# Bundle artifact +*.jsbundle diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100755 index 0000000..7a73a41 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,2 @@ +{ +} \ No newline at end of file diff --git a/.watchmanconfig b/.watchmanconfig new file mode 100755 index 0000000..9e26dfe --- /dev/null +++ b/.watchmanconfig @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/App.js b/App.js new file mode 100755 index 0000000..82f6036 --- /dev/null +++ b/App.js @@ -0,0 +1,51 @@ +/** + * Sample React Native App + * https://github.com/facebook/react-native + * + * @format + * @flow + */ + +import React, {Component} from 'react'; +import {Platform, StyleSheet, Text, View} from 'react-native'; +import {observable} from 'mobx' +import {observer} from 'mobx-react/native' +const instructions = Platform.select({ + ios: 'Press Cmd+R to reload,\n' + 'Cmd+D or shake for dev menu', + android: + 'Double tap R on your keyboard to reload,\n' + + 'Shake or press menu button for dev menu', +}); + +@observer +export default class App extends Component { + render() { + return ( + + Welcome to React Native! + To get started, edit App.js + {instructions} + + ); + } +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + backgroundColor: '#F5FCFF', + }, + welcome: { + fontSize: 20, + textAlign: 'center', + margin: 10, + }, + instructions: { + textAlign: 'center', + color: '#333333', + marginBottom: 5, + }, +}); + diff --git a/README.md b/README.md new file mode 100644 index 0000000..3929684 --- /dev/null +++ b/README.md @@ -0,0 +1,91 @@ +## Hangry + +## Description +Hangry is a mobile application for food delivery, where customers can place orders, make payments, and it is integrated with the Stripe third-party payment system. Additionally, Google Maps will display the pickup location for the food delivery. + +### Screenshot + + + + + + +This is a new [**React Native**](https://reactnative.dev) project, bootstrapped using [`@react-native-community/cli`](https://github.com/react-native-community/cli). + +# Getting Started + +>**Note**: Make sure you have completed the [React Native - Environment Setup](https://reactnative.dev/docs/environment-setup) instructions till "Creating a new application" step, before proceeding. + +## Step 1: Start the Metro Server + +First, you will need to start **Metro**, the JavaScript _bundler_ that ships _with_ React Native. + +To start Metro, run the following command from the _root_ of your React Native project: + +```bash +# using npm +npm start + +# OR using Yarn +yarn start +``` + +## Step 2: Start your Application + +Let Metro Bundler run in its _own_ terminal. Open a _new_ terminal from the _root_ of your React Native project. Run the following command to start your _Android_ or _iOS_ app: + +### For Android + +```bash +# using npm +npm run android + +# OR using Yarn +yarn android +``` + +### For iOS + +```bash +# using npm +npm run ios + +# OR using Yarn +yarn ios +``` + +If everything is set up _correctly_, you should see your new app running in your _Android Emulator_ or _iOS Simulator_ shortly provided you have set up your emulator/simulator correctly. + +This is one way to run your app — you can also run it directly from within Android Studio and Xcode respectively. + +## Step 3: Modifying your App + +Now that you have successfully run the app, let's modify it. + +1. Open `App.tsx` in your text editor of choice and edit some lines. +2. For **Android**: Press the R key twice or select **"Reload"** from the **Developer Menu** (Ctrl + M (on Window and Linux) or Cmd ⌘ + M (on macOS)) to see your changes! + + For **iOS**: Hit Cmd ⌘ + R in your iOS Simulator to reload the app and see your changes! + +## Congratulations! :tada: + +You've successfully run and modified your React Native App. :partying_face: + +### Now what? + +- If you want to add this new React Native code to an existing application, check out the [Integration guide](https://reactnative.dev/docs/integration-with-existing-apps). +- If you're curious to learn more about React Native, check out the [Introduction to React Native](https://reactnative.dev/docs/getting-started). + +# Troubleshooting + +If you can't get this to work, see the [Troubleshooting](https://reactnative.dev/docs/troubleshooting) page. + +# Learn More + +To learn more about React Native, take a look at the following resources: + +- [React Native Website](https://reactnative.dev) - learn more about React Native. +- [Getting Started](https://reactnative.dev/docs/environment-setup) - an **overview** of React Native and how setup your environment. +- [Learn the Basics](https://reactnative.dev/docs/getting-started) - a **guided tour** of the React Native **basics**. +- [Blog](https://reactnative.dev/blog) - read the latest official React Native **Blog** posts. +- [`@facebook/react-native`](https://github.com/facebook/react-native) - the Open Source; GitHub **repository** for React Native. diff --git a/app.json b/app.json new file mode 100755 index 0000000..768d8fb --- /dev/null +++ b/app.json @@ -0,0 +1,4 @@ +{ + "name": "foodDelivery_v1", + "displayName": "foodDelivery_v1" +} \ No newline at end of file diff --git a/app/Global.js b/app/Global.js new file mode 100755 index 0000000..da372f3 --- /dev/null +++ b/app/Global.js @@ -0,0 +1,3 @@ +export default { +login: false +}; \ No newline at end of file diff --git a/app/components/Menu/googleAutoComplete.js b/app/components/Menu/googleAutoComplete.js new file mode 100755 index 0000000..d2e5c8c --- /dev/null +++ b/app/components/Menu/googleAutoComplete.js @@ -0,0 +1,189 @@ +import React, { Component } from "react"; +import { + Platform, + StyleSheet, + View, + Alert, + NetInfo, + Geolocation, + TouchableOpacity, + SafeAreaView, + StatusBar, + Image +} from "react-native"; +import Header from "../../components/Public/header"; +import Text from "react-native-text"; +import { SearchBar } from "react-native-elements"; +import { observer, inject } from "mobx-react/native"; +import { observable, transaction } from "mobx"; +import { scale, verticalScale, moderateScale } from "react-native-size-matters"; +import { width, height } from "../../config/screen"; +import MapView, { Polyline } from "react-native-maps"; +import MapViewDirections from "react-native-maps-directions"; +import AsyncStorageHelper from "../../config/asyncStorageHelper"; +import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view"; +import theme from "../../config/colors"; +import Size from "../../config/size"; +import { Fonts } from "../../config/fonts"; +import MyStatusBar from "../../components/Public/statusBar"; +const asyncStorageHelper = new AsyncStorageHelper(); +import { GooglePlacesAutocomplete } from "react-native-google-places-autocomplete"; +const origin = { latitude: 22.320508, longitude: 114.170222 }; +const destination = { latitude: 22.320568, longitude: 114.171273 }; +const GOOGLE_MAPS_APIKEY = "AIzaSyBM8eEWcSWBuZcM0lGH_JSoDjgImlqHwPs"; +const size = new Size(); +@observer +@inject(["menuStore"], ["userStore"]) +export default class GoogleAutoComplete extends Component { + constructor(props) { + super(props); + this.store = this.props.menuStore; + this.userStore = this.props.userStore; + } + + blur() { + // this.secondTextInput.onBlur(); + } + getIndex(id) { + var index = this.userStore.pickupPointLabel.findIndex(function(item, i) { + return item.id == parseInt(id); + }); + + return index; + } + onTextChange(id) { + this.locationRef._onBlur(); + this.locationRef.setAddressText( + this.userStore.pickupPointLabel[this.getIndex(id)].value + ); + } + + componentWillUnmount() { + if (this.props.onRef) { + this.props.onRef(undefined); + } + } + + getIndex(id){ + var index = this.userStore.pickupPointLabel.findIndex(function(item, i){ + + return item.id == parseInt(id) + }); + + return index + + } + + componentDidMount() { + if (this.props.onRef) { + this.props.onRef(this); + } + // this.secondTextInput.blur(); + } + + render() { + console.log("pickupPointId " + this.userStore.pickupPointId); + return ( + + { + this.locationRef = instance; + }} + placeholder={this.userStore.text.googleSearch} + placeholderTextColor="white" + placeholderStyle={{ fontFamily: Fonts.century, fontWeight: "bold" }} + minLength={1} // minimum length of text to search + autoFocus={false} + returnKeyType={"search"} // Can be left out for default return key https://facebook.github.io/react-native/docs/textinput.html#returnkeytype + listViewDisplayed="false" // true/false/undefined + fetchDetails={true} + renderDescription={row => row.description} // custom description render + onPress={(data, details = null) => { + this.props.onPress && this.props.onPress(data, details); + console.log(details) + }} + textInputProps={{ + onFocus: () => { + this.props.onFocus && this.props.onFocus(); + } + // onBlur:() => { + // this.props.onBlur && this.props.onBlur(); + + // }, + // onEndEditing:() => { + // this.props.onEndEditing && this.props.onEndEditing(); + // }, + }} + getDefaultValue={() => + this.userStore.pickupPointLabel[this.getIndex(this.userStore.pickupPointId)] + .value + } + + query={{ + // available options: https://developers.google.com/places/web-service/autocomplete + key: GOOGLE_MAPS_APIKEY, + language: "zh-TW||en", // language of the results + types: ["(cities)", "(country)"], + components: "country:hk" // default: 'geocode' + }} + styles={{ + textInputContainer: { + backgroundColor: theme.mainColor, + borderTopWidth: 0, + borderBottomWidth: 0, + width: width, + paddingRight: 20, + paddingLeft: 20, + marginBottom: 10 + }, + textInput: { + textAlign: "center", + height: verticalScale(40), + color: "white", + fontSize: 16, + borderWidth: 0, + backgroundColor: theme.searchMapInputColor, + fontFamily: Fonts.century, + fontWeight: "bold" + }, + description: { + fontWeight: "bold" + }, + listView: { + backgroundColor: "white" + }, + predefinedPlacesDescription: { + color: "#1faadb", + } + }} + currentLocation={false} // Will add a 'Current location' button at the top of the predefined places list + currentLocationLabel="Current location" + nearbyPlacesAPI="GooglePlacesSearch" // Which API to use: GoogleReverseGeocoding or GooglePlacesSearch + GoogleReverseGeocodingQuery={ + { + // available options for GoogleReverseGeocoding API : https://developers.google.com/maps/documentation/geocoding/intro + } + } + GooglePlacesSearchQuery={{ + // available options for GooglePlacesSearch API : https://developers.google.com/places/web-service/search + rankby: "distance", + types: "food" + }} + filterReverseGeocodingByTypes={[ + "locality", + "administrative_area_level_3" + ]} // filter the reverse geocoding results by types - ['locality', 'administrative_area_level_3'] if you want to display only cities + predefinedPlaces={this.props.perdefinedPlaces} + debounce={200} // debounce the requests in ms. Set to 0 to remove debounce. By default 0ms. + // renderLeftButton={() => } + //renderRightButton={() => Custom text } + /> + + ); + } +} diff --git a/app/components/Menu/listItem.js b/app/components/Menu/listItem.js new file mode 100755 index 0000000..e8c51f3 --- /dev/null +++ b/app/components/Menu/listItem.js @@ -0,0 +1,307 @@ +//plugin +import React, { Component } from "react"; +import Text from "react-native-text"; +import { + StyleSheet, + View, + FlatList, + TouchableOpacity, + Image +} from "react-native"; +import { observable, transaction } from "mobx"; +import { width, height } from "../../config/screen"; +import Icon from "react-native-vector-icons/dist/MaterialCommunityIcons"; +import { observer, inject } from "mobx-react/native"; +import FastImage from "react-native-fast-image"; +import firebase from "react-native-firebase"; +import Log from '../../config/log' +import { scale, verticalScale, moderateScale } from "react-native-size-matters"; +// function +import Size from "../../config/size"; +import theme from "../../config/colors"; +import { Fonts } from "../../config/fonts"; + +var size = new Size(); +var log = new Log(); +@inject(["menuStore"], ["userStore"]) +@observer +export default class ListItem extends Component { + @observable + soldout = false + constructor(props) { + super(props); + this.store = this.props.menuStore; + this.userStore = this.props.userStore; + } + + recommended() { + return ( + + ); + } + + componentDidMount(){ + if(this.props.items.quota - this.props.items.sold == 0){ + this.soldout = true + }else{ + this.soldout = false + } + } + foodAttributes() { + return ( + + + + + ); + } + + add() { + if(!this.soldout){ + this.store.addCount(this.props.items.id); + } + } + + sum() { + if(!this.soldout){ + this.store.sumCount(this.props.items.id); + } + } + + onClickAction() { + transaction(() => { + this.store.menuDetails.id = this.props.items.id; + if (!this.userStore.logined) { + this.store.menuDetails.tel = ""; + } else { + this.store.menuDetails.tel = this.userStore.userData.data.mobile; + } + this.store.menuDetails.count = this.props.items.count; + this.store.menuDetails.price = this.props.items.price; + this.store.menuDetails.imageURL = this.props.items.cuisine.photo; + this.store.menuDetails.name = this.props.items.cuisine.name; + this.store.menuDetails.nameEn = this.props.items.cuisine.nameEn; + this.store.menuDetails.tags = this.props.items.cuisine.tags; + this.store.menuDetails.intro_ch = this.props.items.cuisine.description; + this.store.menuDetails.intro_en = this.props.items.cuisine.descriptionEn; + this.store.menuDetails.restaurant.name = this.props.items.cuisine.restaurant.name; + this.store.menuDetails.restaurant.nameEn = this.props.items.cuisine.restaurant.nameEn; + this.store.menuDetails.restaurant.addr = this.props.items.cuisine.restaurant.addr; + this.store.menuDetails.restaurant.addrEn = this.props.items.cuisine.restaurant.addrEn; + }); + this.props.menu.foodInformationPagePopUp(); + // firebase.analytics().logEvent('click_food_details', { foodName: this.store.menuDetails.nameEn }); + log.firebaseLog('click_food_details',{foodName: this.store.menuDetails.nameEn,logined: this.userStore.logined}) + } + + getViewStye() { + if(this.props.index == this.store.menu.data.content.length-1) { + return { + marginBottom: 80, alignItems: "center" + } + } else { + return { + marginBottom: 20, alignItems: "center" + } + } + } + + render() { + + var lang = this.userStore.languageSelection; + return ( + + this.onClickAction()} + > + + + + HKD ${this.props.items.price} + + + { + this.soldout?( + + Sold out + + ):( + + ) + + } + + + + {/* + + {this.props.items.cuisine.description} + || {this.props.items.cuisine.descriptionEn} + + */} + + + + + {this.userStore.dataLanguage(this.props.items.cuisine, "name")} + + + + {this.userStore.dataLanguage( + this.props.items.cuisine.restaurant, + "name" + )} + + + + + this.sum()} + /> + + {this.props.items.count} + + this.add()} + /> + + + + ); + } +} diff --git a/app/components/Menu/map.js b/app/components/Menu/map.js new file mode 100755 index 0000000..5d1170a --- /dev/null +++ b/app/components/Menu/map.js @@ -0,0 +1,248 @@ +import React, { Component } from "react"; +import { + Platform, + StyleSheet, + View, + Alert, + NetInfo, + Geolocation, + TouchableOpacity, + SafeAreaView, + StatusBar, + Image, + TouchableHighlight +} from "react-native"; +import Header from "../../components/Public/header"; +import Text from "react-native-text"; +import { SearchBar } from "react-native-elements"; +import { observer, inject } from "mobx-react/native"; +import { observable, transaction } from "mobx"; +import { scale, verticalScale, moderateScale } from "react-native-size-matters"; +import { width, height } from "../../config/screen"; +import Permissions from "react-native-permissions"; +import MapView, { Polyline } from "react-native-maps"; +import MapViewDirections from "react-native-maps-directions"; +import AsyncStorageHelper from "../../config/asyncStorageHelper"; +import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view"; +import theme from "../../config/colors"; +import MyStatusBar from "../../components/Public/statusBar"; +const asyncStorageHelper = new AsyncStorageHelper(); +import { GooglePlacesAutocomplete } from "react-native-google-places-autocomplete"; +const origin = { latitude: 22.320508, longitude: 114.170222 }; +const destination = { latitude: 22.320568, longitude: 114.171273 }; +const GOOGLE_MAPS_APIKEY = "AIzaSyB_9Wi7BcAgqMPxZvW_5DWb8UxF5W9tWz0"; + +@inject(["menuStore"], ["userStore"]) +@observer +export default class Map extends Component { + @observable + markerId = null; + @observable + region = { + latitude: 22.396427, + longitude: 114.109497, + latitudeDelta: 1, + longitudeDelta: 1 + }; + + constructor(props) { + super(props); + this.store = this.props.menuStore; + this.userStore = this.props.userStore; + this.state = { + region: { + latitude: null, + longitude: null, + latitudeDelta: 1, + longitudeDelta: 1 + } + }; + } + + _requestPermission() { + Permissions.request("location").then(response => { + // Returns once the user has chosen to 'allow' or to 'not allow' access + // Response is one of: 'authorized', 'denied', 'restricted', or 'undetermined' + // this.setState({ photoPermission: response }) + if ( + response == "allow" || + response == "restricted" || + response == "authorized" + ) { + navigator.geolocation.getCurrentPosition( + position => { + console.log(position.coords.latitude); + this.map.animateToRegion({ + latitude: position.coords.latitude, + longitude: position.coords.longitude, + latitudeDelta: 0.02, + longitudeDelta: 0.02 + }); + }, + error => { + console.log(error); + + this.region = { + latitude: 22.396427, + longitude: 114.109497, + latitudeDelta: 1, + longitudeDelta: 1 + }; + }, + { enableHighAccuracy: false, timeout: 4000, maximumAge: 10000 } + ); + } else { + this.region = { + latitude: 22.396427, + longitude: 114.109497, + latitudeDelta: 1, + longitudeDelta: 1 + }; + } + }); + } + + goPosition(data, details) { + this.map.animateToRegion({ + latitude: details.geometry.location.lat, + longitude: details.geometry.location.lng, + latitudeDelta: 0.02, + longitudeDelta: 0.02 + }); + + for (var i = 0; i < this.props.perfdefinedPlaces.length; i++) { + if ( + this.props.perfdefinedPlaces[i].geometry.location.lat == + details.geometry.location.lat && + this.props.perfdefinedPlaces[i].geometry.location.lng == + details.geometry.location.lng + ) { + this.markerId = this.props.perfdefinedPlaces[i].id + break; + } + } + + console.log(details); + } + + clickMarkerAction(id) { + this.markerId = id; + this.store.pickupPointId = id; + this.userStore.pickupPointId = id; + } + picked(id) { + asyncStorageHelper.saveData("pickupPointId", id); + this.store.getMenuItem(this); + //this.props.navigation.navigate('menu') + } + confirmButton() { + if (!this.markerId) { + return null; + } else { + return ( + { + this.props.onPress && this.props.onPress(this.markerId); + }} + > + + {this.userStore.text.confirm} + + + ); + } + } + + componentWillMount() { + this.setUserLocation(); + this._requestPermission(); + if (this.props.onRef) { + this.props.onRef(undefined); + } + } + + componentDidMount() { + if (this.props.onRef) { + this.props.onRef(this); + } + } + + setUserLocation() { + console.log(this.userStore.userLocation); + if (this.userStore.userLocation === null) { + this.setState({ + region: { + latitude: 22.396427, + longitude: 114.109497, + latitudeDelta: 1, + longitudeDelta: 1 + } + }); + } else { + this.setState({ + region: { + latitude: this.userStore.userLocation.coords.latitude, + longitude: this.userStore.userLocation.coords.longitude, + + latitudeDelta: 0.02, + longitudeDelta: 0.02 + } + }); + } + } + render() { + return ( + + { + this.map = ref; + }} + style={{ width: width, flex: 1 }} + showsUserLocation={true} + initialRegion={this.region} + > + {this.userStore.pickupPointLabel.map(marker => ( + this.clickMarkerAction(marker.id)} + > + + + ))} + + + {this.confirmButton()} + + ); + } +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + alignItems: "center", + backgroundColor: "#F5FCFF" + }, + welcome: { + fontSize: 20, + textAlign: "center", + margin: 10 + }, + instructions: { + textAlign: "center", + color: "#333333", + marginBottom: 5 + } +}); diff --git a/app/components/Menu/menuDetailsView.js b/app/components/Menu/menuDetailsView.js new file mode 100755 index 0000000..be40762 --- /dev/null +++ b/app/components/Menu/menuDetailsView.js @@ -0,0 +1,160 @@ +import React, { Component } from "react"; +import { + TextInput, + View, + ScrollView, + StyleSheet, + TouchableOpacity, + Image +} from "react-native"; +import { scale, verticalScale, moderateScale } from "react-native-size-matters"; +import Size from "../../config/size"; +import Text from "react-native-text"; +import FastImage from "react-native-fast-image"; +import { width, height } from "../../config/screen"; +import Icon from "react-native-vector-icons/dist/Ionicons"; +import theme from "../../config/colors"; +import { Fonts } from "../../config/fonts"; +import { observable, transaction } from "mobx"; +import { observer, inject } from "mobx-react/native"; +const size = new Size(); + +@inject(["menuStore"], ["userStore"]) +@observer +export default class MenuDetailsView extends Component { + constructor(props) { + super(props); + this.store = this.props.menuStore; + this.userStore = this.props.userStore; + } + separate(index) { + // console.log(index + " "+ this.store.menuDetails.tags.size) + if (index < 2) { + return ( + + ); + } + } + + tags() {} + + render() { + console.log(this.userStore.languageSelection); + console.log(this.store.menuDetails.tags); + const tagsLoop = this.store.menuDetails.tags.map((tags, index) => ( + + + {this.userStore.dataLanguage(tags, "name")} + + {this.separate(index)} + + )); + return ( + + this.props.self.foodInformationPageDismiss()} + > + + + + + + + + + HKD ${this.store.menuDetails.price} + + + + + + + {this.userStore.dataLanguage(this.store.menuDetails, "name")} + + + {this.userStore.dataLanguage( + this.store.menuDetails.restaurant, + "name" + )} + + + {this.userStore.dataLanguage( + this.store.menuDetails.restaurant, + "addr" + )} + + + {tagsLoop} + + + {this.userStore.dataLanguage(this.store.menuDetails, "intro")} + + + + + + ); + } +} + +const styles = StyleSheet.create({ + marginSide: { + marginRight: 10, + marginLeft: 10 + }, + introView: { + marginRight: 10, + marginLeft: 10, + marginTop: 15, + fontSize: 20 + }, + TextStyle: { + fontFamily: Fonts.century, + color: theme.coolGrey + } +}); diff --git a/app/components/Menu/menuFlatList.js b/app/components/Menu/menuFlatList.js new file mode 100755 index 0000000..e6026a5 --- /dev/null +++ b/app/components/Menu/menuFlatList.js @@ -0,0 +1,109 @@ +import React, { Component } from 'react'; +import Text from 'react-native-text'; +import {StyleSheet, View, FlatList,ActivityIndicator} from 'react-native'; +import Size from '../../config/size' +import { observer,inject } from 'mobx-react/native' +import ListItem from './listItem' +import { width, height } from '../../config/screen'; + +@inject(["menuStore"], ["userStore"]) +@observer +export default class MenuFlatList extends Component{ + + constructor(props) { + super(props); + this.store = this.props.menuStore + this.userStore = this.props.userStore + } + + _renderFooter(){ + if (this.store.pageNo == this.store.totalPage) { + console.log('no more') + return ( + + + No more + + + ); + } else if(this.store.pageNo < this.store.totalPage) { + console.log('loading') + return ( + + + Loading... + + ); + }else{ + return null + } + + + + } + + _onEndReached(){ + + if(this.store.loading){ + console.log('pull loading') + return ; + } + + if((this.store.pageNo!=1) && (this.store.pageNo>=this.store.totalPage)){ + console.log('last page') + return; + } else { + console.log( 'pull load more ') + this.store.pageNo++; + console.log(this.store.pageNo) + this.store.menuloadmore() + } + +} + + + render(){ + return( + } + //refreshing={this.store.loading} + //onRefresh = {()=>{console.log('reload'); this.store.getMenuItem(this);}} + // ListFooterComponent={this._renderFooter.bind(this)} + // onEndReached= {this._onEndReached.bind(this)} + // onEndReachedThreshold={10}//执行上啦的时候10%执行 + /> + + + ) + } + +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + flexDirection: 'row', + justifyContent: 'center', + alignItems: 'center', + backgroundColor: '#F5FCFF', + }, + title: { + fontSize: 15, + color: 'blue', + }, + footer:{ + flexDirection:'row', + height:24, + justifyContent:'center', + alignItems:'center', + marginBottom:10, + }, + content: { + fontSize: 15, + color: 'black', + } +}); \ No newline at end of file diff --git a/app/components/Menu/topMessageText.js b/app/components/Menu/topMessageText.js new file mode 100755 index 0000000..0c2c202 --- /dev/null +++ b/app/components/Menu/topMessageText.js @@ -0,0 +1,189 @@ +import React, { Component } from "react"; +import Text from "react-native-text"; +import { StyleSheet, View } from "react-native"; +import { observable, transaction } from "mobx"; +import { scale, verticalScale, moderateScale } from "react-native-size-matters"; +import { observer, inject } from "mobx-react/native"; +import Size from "../../config/size"; +import theme from "../../config/colors"; +import { Fonts } from "../../config/fonts"; +const size = new Size(); +@inject(["menuStore"]) +@observer +export default class TopMessageText extends Component { + orderClose = false; + constructor(props) { + super(props); + this.store = this.props.menuStore; + this.state = { + time: this.timeInit(), + amPm: "am", + countDownTime: "" + }; + this.getTime = this.getTime.bind(this); + } + + getInitialState() { + return { + time: "00:00:00", + amPm: "am" + }; + } + + secToCountDownTimer() { + var time1 = this.props.date.getTime(); + var time2 = new Date().getTime(); + var diff = Math.abs(time1 - time2); + addZero = n => (n < 10 ? "0" + n : n); + var min = addZero(Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60))); + var second = addZero(Math.floor((diff % (1000 * 60)) / 1000)); + this.setState({ countDownTime: `${min}:${second}` }); + } + + countDownTimer() { + if ( + this.props.date.getDate() == new Date().getDate() && + this.props.date.getMonth() == new Date().getMonth() && + this.props.date.getHours() - new Date().getHours() == 1 + ) { + return ( + + + {this.state.countDownTime + " remain"} + + + ); + } else { + return ; + } + } + + timeInit() { + const takeTwelve = n => (n > 12 ? n - 12 : n), + addZero = n => (n < 10 ? "0" + n : n); + + let d, h, m, s, t, amPm; + + d = this.props.date; + h = addZero(takeTwelve(d.getHours())); + m = addZero(d.getMinutes()); + s = addZero(d.getSeconds()); + amPm = d.getHours() >= 12 ? "p.m" : "a.m"; + t = `${h}:${m}${amPm}`; + + return t; + } + + componentDidMount() { + this.loadInterval = setInterval(this.getTime, 1000); + } + + getTime() { + + this.secToCountDownTimer(); + + } + + componentWillMount() { + this.secToCountDownTimer(); + this.setState({ time: this.timeInit() }); + } + message() { + var month = new Array(12); + month[0] = "January"; + month[1] = "February"; + month[2] = "March"; + month[3] = "April"; + month[4] = "May"; + month[5] = "June"; + month[6] = "July"; + month[7] = "August"; + month[8] = "September"; + month[9] = "October"; + month[10] = "November"; + month[11] = "December"; + var date = this.props.date; + + return ( + "Menu For " + + date.getUTCDate() + + "-" + + month[date.getMonth()] + + "-" + + date.getFullYear() + ); + } + + cutoffDateHandle() { + this.props.date; + } + + render() { + const countDown = this.store.date; + if (this.orderClose == false) { + return ( + + + + {this.message()} + + + + + + {"Orders Close at " + this.timeInit()} + + + {this.countDownTimer()} + + {/* + {this.store.timestamp.getHours()} + */} + + ); + } + } +} diff --git a/app/components/Menu/totalPriceView.js b/app/components/Menu/totalPriceView.js new file mode 100755 index 0000000..3a10705 --- /dev/null +++ b/app/components/Menu/totalPriceView.js @@ -0,0 +1,176 @@ +// plugin +import React, { Component } from 'react'; +import Text from 'react-native-text'; +import { StyleSheet, View, FlatList, TouchableOpacity, Alert } from 'react-native'; +import { observer, inject } from 'mobx-react/native' +import { observable, transaction } from 'mobx'; +import Icon from 'react-native-vector-icons/dist/Ionicons'; +import { scale, verticalScale, moderateScale } from 'react-native-size-matters'; +// function +import Size from '../../config/size'; +import Log from '../../config/log' +import { width, height } from '../../config/screen'; +const size = new Size; +import AsyncStorageHelper from '../../config/asyncStorageHelper' +import theme from '../../config/colors' +const log = new Log() +const asyncStorageHelper = new AsyncStorageHelper +@inject(["menuStore"], ["userStore"]) +@observer +export default class TotalPriceView extends Component { + + constructor(props) { + super(props); + this.store = this.props.menuStore; + this.userStore = this.props.userStore; + } + + getIndex(id){ + var index = this.userStore.pickupPointLabel.findIndex(function(item, i){ + + return item.id == parseInt(id) + }); + + return index + + } + + + deg2rad(deg) { + return deg * (Math.PI / 180) + } + + getDistanceFromLatLonInKm(lat1, lon1, lat2, lon2) { + var R = 6371; // Radius of the earth in km + var dLat = this.deg2rad(lat2 - lat1); // deg2rad below + var dLon = this.deg2rad(lon2 - lon1); + var a = + Math.sin(dLat / 2) * Math.sin(dLat / 2) + + Math.cos(this.deg2rad(lat1)) * Math.cos(this.deg2rad(lat2)) * + Math.sin(dLon / 2) * Math.sin(dLon / 2) + ; + var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + var d = R * c; // Distance in km + return d; + } + + + clickAction() { + if (this.userStore.logined) { + asyncStorageHelper.getData('pickupPointId', pickupPointId => { + if (pickupPointId != null) { + if(this.userStore.addedCard ){ + log.firebaseLog("press_order_now_button",{logined:this.userStore.logined}) + this.userStore.pickupIndex = this.getIndex(pickupPointId) + this.store.getOrderedItems(this.props.self,this.userStore.userData.data.token) + + }else{ + Alert.alert( + '', + 'Please input your credit Card info', + [ + { text: 'Cancel', onPress: () => {console.log('Cancel'),log.firebaseLog('press_creditCard_info_cancel_onMenu_alert',{logined:this.userStore.logined}) }}, + { text: 'Ok', onPress: () => {this.props.self.navigatieAction('account'),log.firebaseLog('press_creditCard_info_ok_onMenu_alert',{logined:this.userStore.logined}) }}, + ], + + ) + + } + } + else { + + if (this.userStore.userLocation != null) { + var Param = {} + try { + this.userStore.pickupLoactionPoint.data.content.forEach(element => { + var km = this.getDistanceFromLatLonInKm(element.lat,element.lng,this.userStore.userLocation.coords.latitude,this.userStore.userLocation.coords.longitude) + console.log(km) + if(km<1){ + Param = {'title': 'Pick the loaction'} + } + }); + } catch (e) { + console.log(e) + + } + Alert.alert( + '', + 'Please select the pickup location', + [ + { text: 'Cancel', onPress: () => console.log('Cancel') }, + { text: 'Go', onPress: () => this.props.self.navigatieAction('Location',Param) }, + ], + + ) + + }else{ + Alert.alert( + '', + 'Please select the pickup location', + [ + { text: 'Cancel', onPress: () => console.log('Cancel') }, + { text: 'Go', onPress: () => this.props.self.navigatieAction('account') }, + ], + + ) + } + } + }) + + } else { + Alert.alert( + '', + 'Please login', + [ + { text: 'Cancel', onPress: () => console.log('Cancel') }, + { text: 'Login', onPress: () => this.props.self.navigatieAction('Login') }, + ], + + ) + } + } + + render() { + if (!this.store.selected) { + return null + } else { + return ( + + + this.store.cleanAll()} /> + + HKD {this.store.totalMoney.toFixed(2)} + + + + this.clickAction()}> + + ORDER NOW + + + + + + ) + } + } +} \ No newline at end of file diff --git a/app/components/MyOrders/foodsRow.js b/app/components/MyOrders/foodsRow.js new file mode 100755 index 0000000..6d33e71 --- /dev/null +++ b/app/components/MyOrders/foodsRow.js @@ -0,0 +1,86 @@ +import React from "react"; +import { View } from "react-native"; +import Text from "react-native-text"; +import { scale, verticalScale, moderateScale } from "react-native-size-matters"; +import Size from "../../config/size"; +const size = new Size(); +import theme from "../../config/colors"; +import { Fonts } from "../../config/fonts"; +import { width, height } from "../../config/screen"; + +const dataLanguage = (data, value, lang) => { + if (lang == "english") { + switch (value) { + case "name": + return data.nameEn; + break; + } + } else { + switch (value) { + case "name": + return data.name; + break; + } + } +}; + +const FoodsRow = props => { + return ( + + + + + {dataLanguage(props.items.cuisine, "name", props.lang)} + + + {dataLanguage(props.items.cuisine.restaurant, "name", props.lang)} + + + Quantity: {props.items.count} + + + + + HKD ${(props.items.price*props.items.count).toFixed(2)} + + + + + ); +}; + +export default FoodsRow; diff --git a/app/components/MyOrders/foodsRowForOrderDetails.js b/app/components/MyOrders/foodsRowForOrderDetails.js new file mode 100755 index 0000000..f3983b6 --- /dev/null +++ b/app/components/MyOrders/foodsRowForOrderDetails.js @@ -0,0 +1,83 @@ +import React from "react"; +import { View } from "react-native"; +import Text from "react-native-text"; +import { scale, verticalScale, moderateScale } from "react-native-size-matters"; +import Size from "../../config/size"; +const size = new Size(); +import theme from "../../config/colors"; +import { Fonts } from "../../config/fonts"; +import { width, height } from "../../config/screen"; + +const dataLanguage = (data, value, lang) => { + if (lang == "english") { + switch (value) { + case "name": + return data.nameEn; + break; + } + } else { + switch (value) { + case "name": + return data.name; + break; + } + } +}; + +const FoodsRowForOrderDetails = props => { + return ( + + + + + {dataLanguage(props.items.menu.cuisine, "name", props.lang)} + + + + {dataLanguage(props.items.menu.cuisine.restaurant, "name", props.lang)} + + Quantity: {props.items.qty} + + + + HKD ${(props.items.price*props.items.qty).toFixed(2)} + + + + + ); +}; + +export default FoodsRowForOrderDetails; diff --git a/app/components/MyOrders/orderFlatList.js b/app/components/MyOrders/orderFlatList.js new file mode 100755 index 0000000..691fa9d --- /dev/null +++ b/app/components/MyOrders/orderFlatList.js @@ -0,0 +1,63 @@ +import React, { Component } from "react"; +import Text from "react-native-text"; +import { StyleSheet, View, FlatList, ActivityIndicator } from "react-native"; +import Size from "../../config/size"; +import { observer, inject } from "mobx-react/native"; +import { width, height } from "../../config/screen"; +import OrderListItems from "./orderListItems"; +@inject(["menuStore"], ["userStore"]) +@observer +export default class OrderFlatList extends Component { + constructor(props) { + super(props); + this.store = this.props.menuStore; + this.userStore = this.props.userStore; + } + + render() { + console.log(this.store.passOrder); + return ( + ( + + )} + //refreshing={this.store.loading} + // onRefresh = {()=>{console.log('reload'); this.store.getMenuItem(this);}} + // ListFooterComponent={this._renderFooter.bind(this)} + // onEndReached= {this._onEndReached.bind(this)} + // onEndReachedThreshold={10}//执行上啦的时候10%执行 + /> + ); + } +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + flexDirection: "row", + justifyContent: "center", + alignItems: "center", + backgroundColor: "#F5FCFF" + }, + title: { + fontSize: 15, + color: "blue" + }, + footer: { + flexDirection: "row", + height: 24, + justifyContent: "center", + alignItems: "center", + marginBottom: 10 + }, + content: { + fontSize: 15, + color: "black" + } +}); diff --git a/app/components/MyOrders/orderListItems.js b/app/components/MyOrders/orderListItems.js new file mode 100755 index 0000000..6cd7d7e --- /dev/null +++ b/app/components/MyOrders/orderListItems.js @@ -0,0 +1,276 @@ +//plugin +import React, { Component } from "react"; +import Text from "react-native-text"; +import { View, FlatList, TouchableOpacity, StyleSheet,Linking } from "react-native"; +import Icon from "react-native-vector-icons/dist/Ionicons"; +import { observer, inject } from "mobx-react/native"; +import FastImage from "react-native-fast-image"; +import { width, height } from "../../config/screen"; +import { scale, verticalScale, moderateScale } from "react-native-size-matters"; +// function +import Size from "../../config/size"; +import theme from "../../config/colors"; +import { Fonts } from "../../config/fonts"; + +var size = new Size(); +@inject(["menuStore"], ["userStore"]) +@observer +export default class OrderListItems extends Component { + constructor(props) { + super(props); + this.store = this.props.menuStore; + this.userStore = this.props.userStore; + } + + foodName() { + var name = ""; + if (this.props.items.items.length == 1) { + name = this.props.items.items[0].menu.cuisine.name; + } else { + this.props.items.items.forEach(element => { + name += element.menu.cuisine.name + ", "; + }); + } + return name; + } + + linkToEmail(){ + Linking.openURL('mailto:support@hangryfood.co?subject=&body=') + } + + food(items) { + return ( + + + + + + Food name + + + + + {this.userStore.dataLanguage( + items.menu.cuisine, + "name" + )} + + + + + + + + Restauant + + + + + {this.userStore.dataLanguage( + items.menu.cuisine.restaurant, + "name" + )} + + + + + + + + Qty. + + + {items.qty} + + + + + Price + + + {"$"+items.price} + + + + + + Subtatal + + + { "$"+items.price*items.qty} + + + + + ); + } + + discountHandle(){ + if(this.props.items.discount >0){ + return( + + {"- "+this.props.items.discount} + + ) + }else{ + return( + + N/A + + ) + } + } + clickAction() { + if(this.props.whichOrder){ + this.store.goOrderDetail(this.props.items,this) + } + + //this.props.navigation.navigate("OrderDetails", { data: this.props.items }); + } + + go(data,time){ +this.props.navigation.navigate("OrderDetails", { data: data, time: time }); + } + + render() { + console.log("show up " + this.props.items.status); + if (this.props.items.status != "E") { + return ( + + this.clickAction()} + style={{ + flexDirection: "row" + }} + > + + + Order details + + + {"Order No: " + this.props.items.id} + + {this.props.items.items.map((items, key) => { + return {this.food(items)}; + })} + + + + Pick up Point + + + + + {this.userStore.dataLanguage( + this.props.items.pickUpLocation, + "name" + )} + + + + + + + + Discount + + + + {this.discountHandle()} + + + + + + + Total + + + + + HKD$ {this.props.items.payAmount} + + + + + + + + + + Status + + + + + + {size.status(this.props.items.status)} + + + this.linkToEmail()} + style={{ + backgroundColor: theme.mainColor, + alignItems: "center", + justifyContent: "center", + marginLeft:20, + width: scale(80), + height: verticalScale(20), + borderRadius: 4, + marginBottom: 5 + }} + > + + contact us + + + + + + + + + ); + } else { + return null; + } + } +} + +const styles = StyleSheet.create({ + textView: { + marginLeft: 20, + fontFamily: Fonts.century, + marginBottom: 5 + } +}); diff --git a/app/components/Public/commonTextInput.js b/app/components/Public/commonTextInput.js new file mode 100755 index 0000000..0e32edf --- /dev/null +++ b/app/components/Public/commonTextInput.js @@ -0,0 +1,53 @@ +import React from "react"; +import { TextInput, View } from "react-native"; +import { scale, verticalScale, moderateScale } from "react-native-size-matters"; +import Size from "../../config/size"; +const size = new Size(); +import theme from "../../config/colors"; +import { width, height } from "../../config/screen"; +import {Fonts} from "../../config/fonts" + +const CommonTextInput = props => { + return ( + + props.inputRef && props.inputRef(input)} + returnKeyType = {props.returnKeyType} + onSubmitEditing = {props.onSubmitEditing && props.onSubmitEditing} + onFocus={props.onFocus} + /> + + ); +}; + +export default CommonTextInput; diff --git a/app/components/Public/commonTextView.js b/app/components/Public/commonTextView.js new file mode 100755 index 0000000..0f88927 --- /dev/null +++ b/app/components/Public/commonTextView.js @@ -0,0 +1,61 @@ +import React from "react"; +import { View, TextInput, TouchableOpacity } from "react-native"; +import Text from "react-native-text"; +import { scale, verticalScale, moderateScale } from "react-native-size-matters"; +import Size from "../../config/size"; +import { Fonts } from "../../config/fonts"; +import theme from "../../config/colors"; +const size = new Size(); +import { width, height } from "../../config/screen"; + +const CommonTextView = props => { + return ( + + + {props.title} + + {!props.input ? ( + + {props.content} + + ) : ( + + + + {props.buttonTitle} + + + )} + + ); +}; + +export default CommonTextView; diff --git a/app/components/Public/drawerNavigationHeader.js b/app/components/Public/drawerNavigationHeader.js new file mode 100755 index 0000000..b4df6e9 --- /dev/null +++ b/app/components/Public/drawerNavigationHeader.js @@ -0,0 +1,75 @@ +import React, { Component } from 'react'; +import { Platform, StyleSheet, View, TouchableOpacity } from 'react-native'; +import { scale, verticalScale, moderateScale } from 'react-native-size-matters'; +import Icon from 'react-native-vector-icons/dist/Ionicons'; +import Icon2 from 'react-native-vector-icons/dist/MaterialCommunityIcons'; +import Text from 'react-native-text'; +//function +import { width, height } from '../../config/screen'; +import Size from '../../config/size' +const size = new Size; + +export default class DrawerNavigationHeader extends Component { + + + logoutIcon() { + if (this.props.logout == true) { + return( + this.props.onPress && this.props.onPress()} /> + ) + } + } + + render() { + return ( + + + + + { this.props.navigation.navigate('DrawerOpen'); }} /> + + + + + {this.props.title} + + + + + + + {this.logoutIcon()} + + + + + ); + } +} + diff --git a/app/components/Public/header.js b/app/components/Public/header.js new file mode 100755 index 0000000..97f5ba2 --- /dev/null +++ b/app/components/Public/header.js @@ -0,0 +1,48 @@ +import React, {Component} from 'react'; +import {Platform, StyleSheet, View, TouchableOpacity} from 'react-native'; +import {scale, verticalScale, moderateScale } from 'react-native-size-matters'; +import Icon from 'react-native-vector-icons/dist/Ionicons'; +import Text from 'react-native-text'; +import { width, height } from '../../config/screen'; +import Size from '../../config/size' +const size = new Size; +export default class Header extends Component { + + backAction(){ + if(this.props.login){ + this.props.navigation.navigate('menu') + }else{ + this.props.navigation.goBack() + } + } + + render() { + return ( + + + {this.props.title} + + { !this.props.order?( + this.backAction()}> + + + ):( + this.props.onPress && this.props.onPress()} > + + + ) + } + + ); + } +} + diff --git a/app/components/Public/loader.js b/app/components/Public/loader.js new file mode 100755 index 0000000..6952b3e --- /dev/null +++ b/app/components/Public/loader.js @@ -0,0 +1,50 @@ +import React, { Component } from 'react'; +import { + StyleSheet, + View, + Modal, + ActivityIndicator +} from 'react-native'; + +const Loader = props => { + const { + loading, + ...attributes + } = props; + + return ( + {console.log('c lose modal')}}> + + + + + + + ) +} + +const styles = StyleSheet.create({ + modalBackground: { + flex: 1, + alignItems: 'center', + flexDirection: 'column', + justifyContent: 'space-around', + backgroundColor: '#00000040' + }, + activityIndicatorWrapper: { + backgroundColor: '#FFFFFF', + height: 100, + width: 100, + borderRadius: 10, + display: 'flex', + alignItems: 'center', + justifyContent: 'space-around' + } +}); + +export default Loader; \ No newline at end of file diff --git a/app/components/Public/signInUpHeader.js b/app/components/Public/signInUpHeader.js new file mode 100755 index 0000000..91d8bcd --- /dev/null +++ b/app/components/Public/signInUpHeader.js @@ -0,0 +1,64 @@ +import React, { Component } from "react"; +import { + Platform, + StyleSheet, + View, + TouchableOpacity, + Image +} from "react-native"; +import { scale, verticalScale, moderateScale } from "react-native-size-matters"; +import Icon from "react-native-vector-icons/dist/MaterialCommunityIcons"; +import Text from "react-native-text"; +import { width, height } from "../../config/screen"; +import Size from "../../config/size"; +import theme from "../../config/colors"; +const size = new Size(); +export default class SignInUpHeader extends Component { + backAction() { + this.props.navigation.goBack(); + } + + render() { + return ( + + + + {this.props.back ? ( + this.backAction()} + /> + + ) : ( + + )} + + + ); + } +} diff --git a/app/components/Public/statusBar.js b/app/components/Public/statusBar.js new file mode 100755 index 0000000..e41ffe5 --- /dev/null +++ b/app/components/Public/statusBar.js @@ -0,0 +1,30 @@ +import React, { Component } from 'react'; +import { + StyleSheet, + View, + StatusBar, + Platform, +} from 'react-native'; +import { ifIphoneX } from 'react-native-iphone-x-helper' +import { scale, verticalScale, moderateScale } from "react-native-size-matters"; +const MyStatusBar = ({backgroundColor, ...props}) => ( + + + +) + + +const STATUSBAR_HEIGHT = Platform.OS === 'ios' ?verticalScale(10) : StatusBar.height; +const APPBAR_HEIGHT = Platform.OS === 'ios' ? verticalScale(44) : verticalScale(56); +const styles = StyleSheet.create({ + statusBar: { + ...ifIphoneX({ + height:0, + }, + { + height:15, + }) + }, +}) + +export default MyStatusBar; \ No newline at end of file diff --git a/app/components/README.md b/app/components/README.md new file mode 100755 index 0000000..ba4787d --- /dev/null +++ b/app/components/README.md @@ -0,0 +1 @@ +# Custom components diff --git a/app/components/signUp/countdownButton.js b/app/components/signUp/countdownButton.js new file mode 100755 index 0000000..f11366a --- /dev/null +++ b/app/components/signUp/countdownButton.js @@ -0,0 +1,32 @@ +import React from 'react' +import {TouchableOpacity,View} from 'react-native' +import Text from 'react-native-text' +import {scale, verticalScale, moderateScale } from 'react-native-size-matters'; +import Size from '../../config/size' +const size = new Size; +import TimerCountdown from 'react-native-timer-countdown'; +import { width, height } from '../../config/screen'; + +export default class CountDownButton { + + render(){ + if(this.props.resend){ + return( + + + RESEND + + + ) + } + return( + + ) +} +} + diff --git a/app/config/README.md b/app/config/README.md new file mode 100755 index 0000000..55d8a80 --- /dev/null +++ b/app/config/README.md @@ -0,0 +1,3 @@ +# config file + +Notice: please copy *.js.example to *.js first before starting your application diff --git a/app/config/asyncStorageHelper.js b/app/config/asyncStorageHelper.js new file mode 100755 index 0000000..870b315 --- /dev/null +++ b/app/config/asyncStorageHelper.js @@ -0,0 +1,45 @@ +import {AsyncStorage} from 'react-native' +import React, { Component } from 'react'; +export default class AsyncStorageHelper extends Component{ + + async saveData(key, value) { + try { + await AsyncStorage.setItem(key, JSON.stringify(value)); + } catch (error) { + console.error('save error'+ error.message); + } + } + + async saveString(key, value) { + try { + await AsyncStorage.setItem(key, value); + } catch (error) { + console.error('save error'+ error.message); + } + } + + async getData(key, callback) { + try { + var value = await AsyncStorage.getItem(key).then( + (values) => { + callback(values); + } + ) + } catch (error) { + // console.error('get error'); + } + } + + async removeItemValue(key) { + try { + await AsyncStorage.removeItem(key); + return true; + } + catch (exception) { + console.log(exception) + return false; + } + } + + +} \ No newline at end of file diff --git a/app/config/colors.js b/app/config/colors.js new file mode 100755 index 0000000..bdec32d --- /dev/null +++ b/app/config/colors.js @@ -0,0 +1,12 @@ +const theme = { + mainColor : 'rgb(255, 184, 25)', + searchMapInputColor: '#FFCE6B', + menuPriceBoxBackgroundColor: 'rgba(255,255,255,0.8)', + menuPriceBoxTextColor:'#585858', + foodNameColor:'#6D6E70', + coolGrey :'#53565A', + inputBgc:'#E6E7E8', + forgetTextColor:'#A6A8AB' + +} +export default theme \ No newline at end of file diff --git a/app/config/connectFail.js b/app/config/connectFail.js new file mode 100755 index 0000000..aecdb12 --- /dev/null +++ b/app/config/connectFail.js @@ -0,0 +1,29 @@ +import React, { Component } from "react"; +import { + Platform, + StyleSheet, + View, + Alert, + NetInfo, + Geolocation, + TouchableOpacity, + SafeAreaView, + StatusBar, + Image, + TouchableHighlight +} from "react-native"; +export default class ConnectFail { + +fail(){ + Alert.alert( + '', + 'Connect failed', + [ + { text: 'Cancel', onPress: () => console.log('Cancel') }, + { text: 'ok', onPress: () => console.log('ok') }, + ], + + ) +} + +} \ No newline at end of file diff --git a/app/config/fonts.js b/app/config/fonts.js new file mode 100755 index 0000000..e1855ba --- /dev/null +++ b/app/config/fonts.js @@ -0,0 +1,5 @@ +export const Fonts = { + century : 'CenturyGothic', + cgb: 'GOTHICB', + +} \ No newline at end of file diff --git a/app/config/index.js b/app/config/index.js new file mode 100755 index 0000000..03f032b --- /dev/null +++ b/app/config/index.js @@ -0,0 +1,4 @@ +export default { + // required: API base url + apiBaseURL: 'http://localhost:4567/', +} diff --git a/app/config/index.js.example b/app/config/index.js.example new file mode 100755 index 0000000..0a75b2e --- /dev/null +++ b/app/config/index.js.example @@ -0,0 +1,4 @@ +export default { + // required: API base url + apiBaseURL: 'http://example.com', +} diff --git a/app/config/language.js b/app/config/language.js new file mode 100755 index 0000000..08e8090 --- /dev/null +++ b/app/config/language.js @@ -0,0 +1,59 @@ +export default { + en:{ + confirm:'confirm', + menu:'Menu', + myOrder:'My Order', + profile:'Profile', + setting:'Setting', + slectLang: 'Choose your language', + referYourFriends: 'Refer Your Friends', + coupon:'Coupon', + myCoupons:'My Coupons', + used: 'Uesd', + cannotuse:'Unavailable', + canuse: 'Can use', + expired:'Expired', + expireDate:'Expire Date', + termandconditions:'Term & Conditions', + privacy:'Privacy', + contactUs:'Contact us', + language:'Language', + notification:'Notification', + howtouse:'How to use', + logout:'Logout', + googleSearch:'Input Your Address', + // referFriendContent:"Share the WARMTH this chilling season. Don’t leave yourself HANGRY Input the promo code \"Hangry\" to enjoy 10% OFF their first purchase order. \n Download Link: \n" + referFriendContent:"" + + }, + zh:{ + confirm:'確認', + menu:'菜單', + myOrder:'訂單', + profile:'帳戶', + setting:'設置', + slectLang: '選擇你的語言', + referYourFriends:'推薦你的朋友', + coupon:'優惠券', + myCoupons:'我的優惠券', + used: '已使用', + canuse:'可使用', + cannotuse:'不可使用', + expired:'已過期', + expireDate:'過期日子', + termandconditions:'條款和條件', + privacy:'隱私', + contactUs:'聯繫我們', + language:'語言', + notification:'通知', + howtouse:'如何使用', + logout:'登出', + googleSearch:'輸入你的地址', + //referFriendContent:"在這個寒冷的季節分享溫暖輸入此推薦碼\"可亨\"首次訂單九折優惠 \n 立即下載: \n" + referFriendContent:"" + + + + + } +} \ No newline at end of file diff --git a/app/config/log.js b/app/config/log.js new file mode 100755 index 0000000..866a7dc --- /dev/null +++ b/app/config/log.js @@ -0,0 +1,9 @@ +import firebase from 'react-native-firebase' +export default class Log { + firebaseLog(logName,param){ + firebase.analytics().logEvent(logName,param); + } + firebaseClass(page){ + firebase.analytics().setCurrentScreen(page); + } + } \ No newline at end of file diff --git a/app/config/screen.js b/app/config/screen.js new file mode 100755 index 0000000..e82618c --- /dev/null +++ b/app/config/screen.js @@ -0,0 +1,4 @@ +import {Dimensions} from 'react-native' + +export const width = Dimensions.get('window').width; +export const height = Dimensions.get('window').height; \ No newline at end of file diff --git a/app/config/size.js b/app/config/size.js new file mode 100755 index 0000000..e2cc1f9 --- /dev/null +++ b/app/config/size.js @@ -0,0 +1,49 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Dimensions, Text, StyleSheet } from 'react-native'; +const { width, height } = Dimensions.get('window'); +const flattenStyle = StyleSheet.flatten; +const realWidth = height > width ? width : height; + +export default class Size{ + + getSize(size){ + return Math.round(size * realWidth / 375); + } + + status(value){ + switch(value){ + case 'A': + return 'Not pay yet' + break; + + case 'V': + return 'Cancelled' + break; + + case 'I': + return 'Cancel by unpaid' + break; + + case 'E': + return 'Expired by unpaid' + break; + + case 'P': + return 'Preparing' + break; + + case 'D': + return 'ready' + break; + + case 'F': + return 'Completed' + break; + + default: + return 'Undefind' + break + } + } +} \ No newline at end of file diff --git a/app/containers/Account/account.js b/app/containers/Account/account.js new file mode 100755 index 0000000..803988a --- /dev/null +++ b/app/containers/Account/account.js @@ -0,0 +1,125 @@ +import React, { Component } from "react"; +import { + Platform, + StyleSheet, + Text, + View, + Alert, + SafeAreaView +} from "react-native"; +import { observable } from "mobx"; +import { observer, inject } from "mobx-react/native"; +import ScrollableTabView from "react-native-scrollable-tab-view"; +//component +import DrawerNavigationHeader from "../../components/Public/drawerNavigationHeader"; +import AccountSettings from "./accountSettings"; +import Login from "../Login/login"; +import Payment from "./payment"; +import Header from '../../components/Public/signInUpHeader' +// function +import { width, height } from "../../config/screen"; +import Log from '../../config/log' +import theme from "../../config/colors"; +import {Fonts} from '../../config/fonts' +import AsyncStorageHelper from "../../config/asyncStorageHelper"; +import language from "../../config/language"; +const asyncStorageHelper = new AsyncStorageHelper(); +const log = new Log() +@inject(["menuStore"], ["userStore"]) +@observer +export default class Account extends Component { + + constructor(props) { + super(props); + this.store = this.props.userStore; + this.menuStore = this.props.menuStore; + } + + @observable + static navigationOptions = { + drawerLabel: "Account", + swipeEnabled: false, + tabBarLabel: language.en.profile, + }; + + + componentWillMount() { + this.init(); + } + + init() { + console.log(this.store.logined) + if (!this.store.logined) { + // this.props.navigation.navigate("Login"); + } else { + } + } + + changeIndex(index) { + this.tabMap.index = index; + } + + logoutAlert() { + Alert.alert( + "Logout", + "Are you sure to Logout?", + [ + { + text: "Cancel", + onPress: () => console.log("Cancel Pressed"), + style: "cancel" + }, + { text: "Sure", onPress: () => this.logoutAction() } + ], + { cancelable: false } + ); + } + + logoutAction() { + this.store.logoutPost(this); + } + navigatieAction(page) { + this.props.navigation.navigate(page); + } + + render() { + log.firebaseClass('profile') + return ( + + +
+ + + + + + + ); + } +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + height: height, + width: width + }, + welcome: { + fontSize: 20, + textAlign: "center", + margin: 10 + }, + instructions: { + textAlign: "center", + color: "#333333", + marginBottom: 5 + } +}); diff --git a/app/containers/Account/accountSettings.js b/app/containers/Account/accountSettings.js new file mode 100755 index 0000000..7d2c49e --- /dev/null +++ b/app/containers/Account/accountSettings.js @@ -0,0 +1,336 @@ +import React, { Component } from "react"; +import { + Platform, + StyleSheet, + View, + TextInput, + TouchableOpacity +} from "react-native"; +import { observable } from "mobx"; +import { observer, inject } from "mobx-react/native"; +import Text from "react-native-text"; +import { scale, verticalScale, moderateScale } from "react-native-size-matters"; +import { Dropdown } from "react-native-material-dropdown"; +import { Button } from "react-native-elements"; +import firebase from "react-native-firebase"; +import Toast, { DURATION } from "react-native-easy-toast"; +//component +import DrawerNavigationHeader from "../../components/Public/drawerNavigationHeader"; +import theme from "../../config/colors"; +import { Fonts } from "../../config/fonts"; +import Size from "../../config/size"; +import Countdown, { CountdownStatus } from "rn-countdown"; +// function +import { width, height } from "../../config/screen"; +import AsyncStorageHelper from "../../config/asyncStorageHelper"; +import Loader from "../../components/Public/loader"; + +const asyncStorageHelper = new AsyncStorageHelper(); +const size = new Size(); + +@inject(["menuStore"], ["userStore"]) +@observer +export default class AccountSettings extends Component { + @observable pickupPointIndexExist = false; + password = ""; + pickupPoint = []; + @observable index = 1; + @observable + code = 0; + @observable + password = ""; + @observable + verificationCode = ""; + static navigationOptions = { + drawerLabel: "Account" + }; + + constructor(props) { + super(props); + this.store = this.props.userStore; + this.menuStore = this.props.menuStore; + } + navigatieAction(page, Param) { + this.props.navigation.navigate(page, Param); + } + checking() { + asyncStorageHelper.getData("pickupPointId", pickupPointId => { + if (pickupPointId != null) { + console.log("index: " + pickupPointId); + this.pickupPointIndexExist = true; + this.index = this.getIndex(pickupPointId); + } + }); + } + + changeIndex(index) { + this.tabMap.index = index; + } + + positionValue() { + if (this.pickupPointIndexExist) { + return this.store.pickupPointLabel[this.pickupPointIndex].value; + } else { + return null; + } + } + + positionChange(id) { + asyncStorageHelper.saveData("pickupPointId", id); + this.menuStore.getMenuItem(); + } + + handleNetworkFailed = () => alert("network failed"); + + handleStopCountdown = () => this.countdown && this.countdown.stopCountdown(); + + handleClickCountdown = () => { + var bodyFormData = new FormData(); + + bodyFormData.append("country_code", "852"); + bodyFormData.append( + "phone_number", + this.store.userData.data.member.mobile.toString() + ); + + this.store.sendsmsVerify(bodyFormData, this); + }; + + getIndex(id) { + var index = this.store.pickupPointLabel.findIndex(function(item, i) { + return item.id == parseInt(id); + }); + return index; + } + + startToCountDown = () => { + this.countdown && this.countdown.startCountdown(); + }; + + resetPassword() { + // this.store.signupPost(api.signup, this.signInData, this); + if (this.verificationCode != null || this.verificationCode != "") { + if (this.password != null || this.password != "") { + if (this.password.length >= 8) { + var data = { + verificationCode: this.verificationCode, + mobile: this.store.userData.data.member.mobile.toString(), + countryCode: "852", + password: this.password + }; + this.store.forgotPassword(data, this); + } else { + this.refs.toast.show("your password must be at least 8 characters"); + } + } else { + this.refs.toast.show("Please enter reset password"); + } + } else { + this.refs.toast.show("Please enter verification number"); + } + } + + countDownButton() { + return ( + + (this.countdown = r)} + time={60} + onPress={this.handleClickCountdown} + onNetworkFailed={this.handleNetworkFailed} + onDidFinishCountdown={this.handleCountdownOver} + > + {({ status, time }) => { + let title, containerStyle, titleStyle; + switch (status) { + case CountdownStatus.Idle: + title = "send"; + containerStyle = styles.countdown; + titleStyle = styles.countdownTitle; + break; + case CountdownStatus.Counting: + title = `sent(${time})`; + containerStyle = styles.countdown; + titleStyle = styles.countdownTitle; + break; + case CountdownStatus.Over: + title = "send"; + containerStyle = styles.countdown; + titleStyle = styles.countdownTitle; + break; + } + return ( + + {title} + + ); + }} + + + ); + } + + render() { + if (this.store.logined) { + this.checking(); + return ( + + + + + + {this.store.userData.data.member.name} + + + + + {this.store.userData.data.member.email} + + + + + +{this.store.userData.data.member.countryCode}{" "} + {this.store.userData.data.member.mobile} + + + + + (this.password = value)} + /> + + + {this.countDownButton()} + + + (this.verificationCode = text)} + placeholderTextColor={theme.foodNameColor} + /> + + + +