Tuesday 17 March 2020

為什麼貓會在看到你的時候把肚皮翻過來

buffalo trip

Expense:

March 17, 2020
CRM course
2 hard drives

March 19
Taxi x 2

March 21
fuel x 2

March 22
fuel
hotel

March 23
car rental

March 24
hotel
hard drive
allen key

March 25
gas, tools, fuel

March 26
hotel, fuel

March 27
hotel

March 29
hotel

March 30
tool, hotel

March 31
hotel

April 1
hotel

April 2
hotel, fuel

April 4
taxi, fuel

April 6
taxi

Timesheet

March 19 ferry to Montreal 10h
March 20 install 10h
March 21 survey 12h
March 22 weather 8h
March 23 weather 8h
March 24 troubleshoot 8h
March 25 troubleshoot, survey 16h
March 26 weather 8h
March 27 survey 12h
March 28 weather 8h
March 29 weather 8h
March 30 weather 8h
March 31 weather 8h
April 1 survey 12h
April 2 weather 8h
April 3 weather 8h
April 4 ferry to Montreal 8h
April 5 fly back 8h
April 6 fly back 8h

Sunday 15 March 2020

cornavirus task force gives update

expo sensors

//device motion object

Object {
  "acceleration": Object {
    "x": 0.4774880111217499,
    "y": 0.10282400250434875,
    "z": -0.38873401284217834,
  },
  "accelerationIncludingGravity": Object {
    "x": -3.1230223178863525,
    "y": -8.132352828979492,
    "z": -4.447468280792236,
  },
  "orientation": 0,
  "rotation": Object {
    "alpha": 2.700334310531616,
    "beta": 0.9731265902519226,
    "gamma": -0.6596294641494751,
  },
  "rotationRate": Object {
    "alpha": -0.04015243425965309,
    "beta": -0.03880959376692772,
    "gamma": 0.08099979907274246,
  },
}

--------------------------
//Magnetometer

Object {
  "x": -15.711748123168945,
  "y": -42.86347579956055,
  "z": -14.824956893920898,
}

reference:
https://docs.expo.io/versions/latest/sdk/devicemotion/

Saturday 14 March 2020

press conference with coronavirus task force

expo gps3 export

stop record after recording for a while

press show record

press export

txt file is in download folder

open it up

//gps.js

import React, { useState, useEffect } from 'react';
import { Clipboard, ShadowPropTypesIOS } from 'react-native';
import {
    Container, Header, Title, Content, Footer,
    FooterTab, Button, Left, Right, Body, Icon, Text,
    Accordion, Card, CardItem, Thumbnail, ListItem,
    CheckBox, DatePicker, DeckSwiper, Fab, View,
    Badge, Form, Item, Input, Label, Picker, Textarea,
    Switch, Radio, Spinner, Tab, Tabs, TabHeading,
    ScrollableTab, H1, H2, H3, Drawer,
} from 'native-base';
import * as Font from 'expo-font'
import * as Location from 'expo-location'
import * as TaskManager from 'expo-task-manager'
import useInterval from 'react-useinterval'
import openMap from 'react-native-open-maps'
import { connect } from 'react-redux';
import { addLocations, deleteLocations } from './Redux/locationActions'
import * as MediaLibrary from 'expo-media-library'
import * as FileSystem from 'expo-file-system'

const LOCATION_TASK_NAME = 'background-location-task';
let background_location = null

function GPS(props) {
    const [loadfont, setloadfont] = useState(true)
    const [permission_status, setpermission_status] = useState('undetermined')
    const [acc, setacc] = useState(-1)
    const [altitude, setaltitude] = useState(-1)
    const [heading, setheading] = useState(-1)
    const [latitude, setlatitude] = useState(-1)
    const [longitude, setlonggitude] = useState(-1)
    const [speed, setspeed] = useState(-1)
    const [time, settime] = useState('')
    const [updatePeriod, setupdatePeriod] = useState(null)
    const [address, setaddress] = useState(null)
    const [card_collapse1, setcard_collapse1] = useState(false)
    const [card_collapse2, setcard_collapse2] = useState(false)
    const [record, setrecord] = useState(false)
    const [storagePermission, setstoragePermission] = useState('denied')

    useEffect(() => {
        initializeApp()
        return () => stop_update_location()
    }, [])

    initializeApp = async () => {
        const { status } = await Location.requestPermissionsAsync()
        setpermission_status(status)

        const grantStorage = await MediaLibrary.requestPermissionsAsync()
        setstoragePermission(grantStorage.status)

        await Font.loadAsync({
            Roboto: require("native-base/Fonts/Roboto.ttf"),
            Roboto_medium: require("native-base/Fonts/Roboto_medium.ttf")
        })
        setloadfont(false)
    }

    useInterval(() => {
        update_location()

        if (record && time !== '') {
            props.dispatch(addLocations({
                latitude: latitude,
                longitude: longitude,
                time: time,
            }))
        }
    }, updatePeriod);

    update_location = async () => {

        await Location.startLocationUpdatesAsync(LOCATION_TASK_NAME, {
            accuracy: Location.Accuracy.High,
        })

        if (background_location) {
            const e = background_location.coords
            setacc(e.accuracy)
            setaltitude(e.altitude)
            setheading(e.heading)
            setlatitude(e.latitude)
            setlonggitude(e.longitude)
            setspeed(e.speed)
            settime(Date(background_location.timestamp).toLocaleString())
        }
    }

    stop_update_location = async () => {
        setupdatePeriod(null)
        const taskRegistered = await TaskManager.isTaskRegisteredAsync(LOCATION_TASK_NAME)
        if (taskRegistered) {
            await TaskManager.unregisterTaskAsync(LOCATION_TASK_NAME)
        }
    }

    search_address = async () => {
        if (background_location) {
            const _address = await Location.reverseGeocodeAsync(background_location.coords)
            setaddress(_address[0])
        }
    }

    open_map = () => {
        if (background_location) {
            const e = background_location.coords
            openMap({ latitude: e.latitude, longitude: e.longitude })
        }
    }

    write_to_clipboard = (text) => {
        Clipboard.setString(text);
    }

    display_record = () => {
        let text = ''
        props.locations_store.map(item => {
            text = text + item.time + ',' + item.latitude.toString() + ',' + item.longitude.toString() + '\n'
        })
        alert(text)
    }

    export_locations = async () => {
        let text = ''
        props.locations_store.map(item => {
            text = text + item.time + ',' + item.latitude.toString() + ',' + item.longitude.toString() + '\n'
        })

        const fileUri = FileSystem.cacheDirectory + "location.txt";
        await FileSystem.writeAsStringAsync(fileUri, text, { encoding: FileSystem.EncodingType.UTF8 });
        const asset = await MediaLibrary.createAssetAsync(fileUri)
        await MediaLibrary.createAlbumAsync("Download", asset, false)

        alert('file exported to download folder')
    }

    if (permission_status !== 'granted' || storagePermission !== 'granted') {
        <Container style={{ backgroundColor: '#1C2833' }}>
            <Text>App needs location and storage permission granted</Text>
        </Container>
    }

    if (loadfont) {
        return <Container style={{ backgroundColor: '#1C2833' }}><Spinner /></Container>
    }

    return (
        <Container style={{ backgroundColor: '#1C2833' }}>
            <Content style={{ marginTop: 25 }}>
                <Card style={{ backgroundColor: '#1C2833' }}>
                    <CardItem header bordered button style={{ backgroundColor: "#1C2833" }}
                        onPress={() => setcard_collapse1(!card_collapse1)}>
                        <View style={{ flex: 1, flexDirection: 'row', justifyContent: 'space-between' }}>
                            <H3 style={{ color: '#D0D3D4' }}>Realtime GPS</H3>
                            <H3 style={{ color: "#D0D3D4" }}>+</H3>
                        </View>
                    </CardItem>
                    {card_collapse1 ? null :
                        <CardItem bordered style={{ backgroundColor: '#1C2833' }}>
                            <Text style={{ color: "#D0D3D4", fontStyle: 'italic' }}>
                                Accuracy: {acc} Meters{'\n'}
                                Altitude: {altitude} Meters{'\n'}
                                Heading: {heading} Degree{'\n'}
                                Latitude: {latitude} Degree{'\n'}
                                Longitude: {longitude} Degree{'\n'}
                                Speed: {speed} M/S{'\n'}
                                Time: {time}
                            </Text>
                            <Button light small bordered icon style={{ position: 'absolute', right: 10, top: 10 }}
                                onPress={() => { write_to_clipboard(latitude + ',' + longitude) }}>
                                <Icon name='copy'></Icon>
                            </Button>
                        </CardItem>
                    }
                    <CardItem header bordered button style={{ backgroundColor: '#1C2833' }}
                        onPress={() => setcard_collapse2(!card_collapse2)}>
                        <View style={{ flex: 1, flexDirection: 'row', justifyContent: 'space-between' }}>
                            <H3 style={{ color: '#D0D3D4' }}>Address (update location first)</H3>
                            <H3 style={{ color: '#D0D3D4' }}>+</H3>
                        </View>
                    </CardItem>
                    {card_collapse2 ? null :
                        <CardItem style={{ backgroundColor: '#1C2833' }}>
                            {address ?
                                <Text style={{ color: '#D0D3D4' }}>
                                    {address.name}  {address.street} {'\n'}
                                    {address.city} {address.region} {address.country}  {address.postalCode}
                                </Text>
                                : <Text style={{ color: '#D0D3D4' }}>Waiting for GPS location</Text>}
                            <Button light small bordered icon style={{ position: 'absolute', right: 10, top: 10 }}
                                onPress={() => {
                                    write_to_clipboard(address.name + ' ' + address.street + ' ' +
                                        address.city + ' ' + address.region + ' ' + address.country + ' ' + address.postalCode)
                                }}>
                                <Icon name='copy'></Icon>
                            </Button>
                        </CardItem>
                    }
                </Card>

                <View style={{ flexDirection: 'row', justifyContent: "space-around", marginTop: 10 }}>
                    <Button small style={{ width: 150, justifyContent: "center" }} onPress={() => setupdatePeriod(1000)}>
                        <Text>Update Location</Text>
                    </Button>
                    <Button small style={{ width: 150, justifyContent: "center" }} onPress={() => stop_update_location()}>
                        <Text>Stop Update</Text>
                    </Button>
                </View>
                <View style={{ flexDirection: 'row', justifyContent: "space-around", marginTop: 10 }}>
                    <Button small style={{ width: 150, justifyContent: "center" }} onPress={() => search_address()}>
                        <Text>Search Address</Text>
                    </Button>
                    <Button small style={{ width: 150, justifyContent: "center" }} onPress={() => open_map()}>
                        <Text>Open Map</Text>
                    </Button>
                </View>
                <View style={{ flexDirection: 'row', justifyContent: "space-around", marginTop: 10 }}>
                    <Button small style={{ width: 150, justifyContent: "center" }} onPress={() => {
                        props.dispatch(deleteLocations()); setupdatePeriod(1000); setrecord(true)
                    }}>
                        <Text>New Record</Text>
                    </Button>
                    <Button small style={{ width: 150, justifyContent: "center" }}
                        onPress={() => { setupdatePeriod(1000); setrecord(true) }}>
                        <Text>Continue Record</Text>
                    </Button>
                </View>
                <View style={{ flexDirection: 'row', justifyContent: "space-around", marginTop: 10 }}>
                    <Button small style={{ width: 150, justifyContent: "center" }}
                        onPress={() => { stop_update_location(); setrecord(false) }}>
                        <Text>Stop Record</Text>
                    </Button>
                    <Button small style={{ width: 150, justifyContent: "center" }}
                        onPress={() => display_record()}>
                        <Text>Show Record</Text>
                    </Button>
                </View>
                <View style={{ flexDirection: 'row', justifyContent: "space-around", marginTop: 10 }}>
                    <Button small style={{ width: 150, justifyContent: "center" }}
                        onPress={() => { export_locations() }}>
                        <Text>Export Locations</Text>
                    </Button>

                </View>
            </Content>
        </Container>
    );
}

TaskManager.defineTask(LOCATION_TASK_NAME, ({ data, error }) => {
    if (error) {
        // Error occurred - check `error.message` for more details.
        console.log(error.message)
        return;
    }
    if (data) {
        const { locations } = data;
        // do something with the locations captured in the background
        background_location = locations[locations.length - 1]
    }
});

export default connect(
    (store) => {
        return {
            locations_store: store.locationReducer.locations,
        };
    }
)(GPS);

reference:
http://chuanshuoge2.blogspot.com/2020/03/expo-gps2-redux-persist.html

Wednesday 11 March 2020

Trump Addresses The Nation On Coronavirus

expo gps2 redux persist

app opens click new record

press stop record, press show record
time, latitude, longitude are recorded

press continue record

data append to the existing

close app, reopen app, press show record

location data persist, didn't lose when app close

press continue record, list keep growing

//app.js

import React from 'react';
import { PersistGate } from 'redux-persist/integration/react';
import { Provider } from 'react-redux';
import GPS from './gps';
import { store, persistor } from './Redux/store';
import { View, ActivityIndicator } from 'react-native';

export default function App() {
  renderLoading = () => {
    return (
      <View>
        <ActivityIndicator size={"large"} />
      </View>
    );
  };

  return (
    // Redux: Global Store
    <Provider store={store}>
      <PersistGate loading={() => renderLoading()} persistor={persistor}>
        <GPS />
      </PersistGate>
    </Provider>
  );
};

----------------------------------------------
//Redux/store.js

import createSecureStore from "redux-persist-expo-securestore";
import { createStore, applyMiddleware } from "redux";
import { persistStore, persistReducer } from "redux-persist";
import reducers from "./reducers";
import { createLogger } from 'redux-logger';

// Secure storage
const storage = createSecureStore();

const config = {
    key: "root",
    storage
};

const persistedReducer = persistReducer(config, reducers);

const store = createStore(
    persistedReducer,
    applyMiddleware(createLogger())
);
const persistor = persistStore(store);

export { store, persistor }

-------------------------------
//Redux/reducers/index.js

import { combineReducers } from 'redux';

import locationReducer from "./locationReducer";

export default combineReducers(
    {
        locationReducer
    })

----------------------------
//Redux/reducers/locationReducer.js

export default function reducer(
    state = {
        locations: []
    },
    action
) {
    switch (action.type) {

        case "add": {
            return { ...state, locations: state.locations.concat(action.payload) }
        }

        case "delete": {
            return { ...state, locations: [] }
        }
    }
    return state;
}

----------------------------
//Redux/locationActions.js

export function addLocations(locations) {

    return {
        type: 'add',
        payload: locations
    }
}

export function deleteLocations() {
    return {
        type: 'delete'
    }
}

-----------------------------
//gps.js

import React, { useState, useEffect } from 'react';
import { Clipboard, ShadowPropTypesIOS } from 'react-native';
import {
    Container, Header, Title, Content, Footer,
    FooterTab, Button, Left, Right, Body, Icon, Text,
    Accordion, Card, CardItem, Thumbnail, ListItem,
    CheckBox, DatePicker, DeckSwiper, Fab, View,
    Badge, Form, Item, Input, Label, Picker, Textarea,
    Switch, Radio, Spinner, Tab, Tabs, TabHeading,
    ScrollableTab, H1, H2, H3, Drawer,
} from 'native-base';
import * as Font from 'expo-font'
import * as Location from 'expo-location'
import * as TaskManager from 'expo-task-manager'
import useInterval from 'react-useinterval'
import openMap from 'react-native-open-maps'
import { connect } from 'react-redux';
import { addLocations, deleteLocations } from './Redux/locationActions'

const LOCATION_TASK_NAME = 'background-location-task';
let background_location = null

function GPS(props) {
    const [loadfont, setloadfont] = useState(true)
    const [permission_status, setpermission_status] = useState('undetermined')
    const [acc, setacc] = useState(-1)
    const [altitude, setaltitude] = useState(-1)
    const [heading, setheading] = useState(-1)
    const [latitude, setlatitude] = useState(-1)
    const [longitude, setlonggitude] = useState(-1)
    const [speed, setspeed] = useState(-1)
    const [time, settime] = useState('')
    const [updatePeriod, setupdatePeriod] = useState(null)
    const [address, setaddress] = useState(null)
    const [card_collapse1, setcard_collapse1] = useState(false)
    const [card_collapse2, setcard_collapse2] = useState(false)
    const [record, setrecord] = useState(false)

    useEffect(() => {
        initializeApp()
        return () => stop_update_location()
    }, [])

    initializeApp = async () => {
        const { status } = await Location.requestPermissionsAsync()
        setpermission_status(status)

        await Font.loadAsync({
            Roboto: require("native-base/Fonts/Roboto.ttf"),
            Roboto_medium: require("native-base/Fonts/Roboto_medium.ttf")
        })
        setloadfont(false)
    }

    useInterval(() => {
        update_location()

        if (record && time !== '') {
            props.dispatch(addLocations({
                latitude: latitude,
                longitude: longitude,
                time: time,
            }))
        }
    }, updatePeriod);

    update_location = async () => {

        await Location.startLocationUpdatesAsync(LOCATION_TASK_NAME, {
            accuracy: Location.Accuracy.High,
        })

        if (background_location) {
            const e = background_location.coords
            setacc(e.accuracy)
            setaltitude(e.altitude)
            setheading(e.heading)
            setlatitude(e.latitude)
            setlonggitude(e.longitude)
            setspeed(e.speed)
            settime(Date(background_location.timestamp).toLocaleString())
        }
    }

    stop_update_location = async () => {
        setupdatePeriod(null)
        const taskRegistered = await TaskManager.isTaskRegisteredAsync(LOCATION_TASK_NAME)
        if (taskRegistered) {
            await TaskManager.unregisterTaskAsync(LOCATION_TASK_NAME)
        }
    }

    search_address = async () => {
        if (background_location) {
            const _address = await Location.reverseGeocodeAsync(background_location.coords)
            setaddress(_address[0])
        }
    }

    open_map = () => {
        if (background_location) {
            const e = background_location.coords
            openMap({ latitude: e.latitude, longitude: e.longitude })
        }
    }

    write_to_clipboard = (text) => {
        Clipboard.setString(text);
    }

    display_record = () => {
        let text = ''
        props.locations_store.map(item => {
            text = text + item.time + ',' + item.latitude.toString() + ',' + item.longitude.toString() + '\n'
        })
        alert(text)
    }

    if (permission_status !== 'granted') {
        <Container style={{ backgroundColor: '#1C2833' }}>
            <Text>App needs location permission granted</Text>
        </Container>
    }

    if (loadfont) {
        return <Container style={{ backgroundColor: '#1C2833' }}><Spinner /></Container>
    }

    return (
        <Container style={{ backgroundColor: '#1C2833' }}>
            <Content style={{ marginTop: 25 }}>
                <Card style={{ backgroundColor: '#1C2833' }}>
                    <CardItem header bordered button style={{ backgroundColor: "#1C2833" }}
                        onPress={() => setcard_collapse1(!card_collapse1)}>
                        <View style={{ flex: 1, flexDirection: 'row', justifyContent: 'space-between' }}>
                            <H3 style={{ color: '#D0D3D4' }}>Realtime GPS</H3>
                            <H3 style={{ color: "#D0D3D4" }}>+</H3>
                        </View>
                    </CardItem>
                    {card_collapse1 ? null :
                        <CardItem bordered style={{ backgroundColor: '#1C2833' }}>
                            <Text style={{ color: "#D0D3D4", fontStyle: 'italic' }}>
                                Accuracy: {acc} Meters{'\n'}
                                Altitude: {altitude} Meters{'\n'}
                                Heading: {heading} Degree{'\n'}
                                Latitude: {latitude} Degree{'\n'}
                                Longitude: {longitude} Degree{'\n'}
                                Speed: {speed} M/S{'\n'}
                                Time: {time}
                            </Text>
                            <Button light small bordered icon style={{ position: 'absolute', right: 10, top: 10 }}
                                onPress={() => { write_to_clipboard(latitude + ',' + longitude) }}>
                                <Icon name='copy'></Icon>
                            </Button>
                        </CardItem>
                    }
                    <CardItem header bordered button style={{ backgroundColor: '#1C2833' }}
                        onPress={() => setcard_collapse2(!card_collapse2)}>
                        <View style={{ flex: 1, flexDirection: 'row', justifyContent: 'space-between' }}>
                            <H3 style={{ color: '#D0D3D4' }}>Address (update location first)</H3>
                            <H3 style={{ color: '#D0D3D4' }}>+</H3>
                        </View>
                    </CardItem>
                    {card_collapse2 ? null :
                        <CardItem style={{ backgroundColor: '#1C2833' }}>
                            {address ?
                                <Text style={{ color: '#D0D3D4' }}>
                                    {address.name}  {address.street} {'\n'}
                                    {address.city} {address.region} {address.country}  {address.postalCode}
                                </Text>
                                : <Text style={{ color: '#D0D3D4' }}>Waiting for GPS location</Text>}
                            <Button light small bordered icon style={{ position: 'absolute', right: 10, top: 10 }}
                                onPress={() => {
                                    write_to_clipboard(address.name + ' ' + address.street + ' ' +
                                        address.city + ' ' + address.region + ' ' + address.country + ' ' + address.postalCode)
                                }}>
                                <Icon name='copy'></Icon>
                            </Button>
                        </CardItem>
                    }
                </Card>

                <View style={{ flexDirection: 'row', justifyContent: "space-around", marginTop: 10 }}>
                    <Button small style={{ width: 150, justifyContent: "center" }} onPress={() => setupdatePeriod(1000)}>
                        <Text>Update Location</Text>
                    </Button>
                    <Button small style={{ width: 150, justifyContent: "center" }} onPress={() => stop_update_location()}>
                        <Text>Stop Update</Text>
                    </Button>
                </View>
                <View style={{ flexDirection: 'row', justifyContent: "space-around", marginTop: 10 }}>
                    <Button small style={{ width: 150, justifyContent: "center" }} onPress={() => search_address()}>
                        <Text>Search Address</Text>
                    </Button>
                    <Button small style={{ width: 150, justifyContent: "center" }} onPress={() => open_map()}>
                        <Text>Open Map</Text>
                    </Button>
                </View>
                <View style={{ flexDirection: 'row', justifyContent: "space-around", marginTop: 10 }}>
                    <Button small style={{ width: 150, justifyContent: "center" }} onPress={() => {
                        props.dispatch(deleteLocations()); setupdatePeriod(1000); setrecord(true)
                    }}>
                        <Text>New Record</Text>
                    </Button>
                    <Button small style={{ width: 150, justifyContent: "center" }}
                        onPress={() => { setupdatePeriod(1000); setrecord(true) }}>
                        <Text>Continue Record</Text>
                    </Button>
                </View>
                <View style={{ flexDirection: 'row', justifyContent: "space-around", marginTop: 10 }}>
                    <Button small style={{ width: 150, justifyContent: "center" }}
                        onPress={() => { stop_update_location(); setrecord(false) }}>
                        <Text>Stop Record</Text>
                    </Button>
                    <Button small style={{ width: 150, justifyContent: "center" }}
                        onPress={() => display_record()}>
                        <Text>Show Record</Text>
                    </Button>
                </View>
            </Content>
        </Container>
    );
}

TaskManager.defineTask(LOCATION_TASK_NAME, ({ data, error }) => {
    if (error) {
        // Error occurred - check `error.message` for more details.
        console.log(error.message)
        return;
    }
    if (data) {
        const { locations } = data;
        // do something with the locations captured in the background
        background_location = locations[locations.length - 1]
    }
});

export default connect(
    (store) => {
        return {
            locations_store: store.locationReducer.locations,
        };
    }
)(GPS);

reference:
https://chuanshuoge2.blogspot.com/2018/05/react-redux.html
https://chuanshuoge2.blogspot.com/2020/03/expo-location.html
http://chuanshuoge2.blogspot.com/2020/03/expo-gps-1.html

Monday 9 March 2020

expo gps 1

app opens, click update location

gps is turned on and updates every second

press stop update, press copy button beside gps info to copy location to clipboard

press search address

press Realtime GPS to collapse it, press open map

google map is the default map app, our location is at center

open offline gps app, paste copied location

offline gps finds our location
//app.js

import React, { useState, useEffect } from 'react';
import { Clipboard } from 'react-native';
import {
  Container, Header, Title, Content, Footer,
  FooterTab, Button, Left, Right, Body, Icon, Text,
  Accordion, Card, CardItem, Thumbnail, ListItem,
  CheckBox, DatePicker, DeckSwiper, Fab, View,
  Badge, Form, Item, Input, Label, Picker, Textarea,
  Switch, Radio, Spinner, Tab, Tabs, TabHeading,
  ScrollableTab, H1, H2, H3, Drawer,
} from 'native-base';
import * as Font from 'expo-font'
import * as Location from 'expo-location'
import * as TaskManager from 'expo-task-manager'
import useInterval from 'react-useinterval'
import openMap from 'react-native-open-maps'

const LOCATION_TASK_NAME = 'background-location-task';
let background_location = null

export default function App() {
  const [loadfont, setloadfont] = useState(true)
  const [permission_status, setpermission_status] = useState('undetermined')
  const [acc, setacc] = useState(-1)
  const [altitude, setaltitude] = useState(-1)
  const [heading, setheading] = useState(-1)
  const [latitude, setlatitude] = useState(-1)
  const [longitude, setlonggitude] = useState(-1)
  const [speed, setspeed] = useState(-1)
  const [time, settime] = useState('')
  const [updatePeriod, setupdatePeriod] = useState(null)
  const [address, setaddress] = useState(null)
  const [card_collapse1, setcard_collapse1] = useState(false)
  const [card_collapse2, setcard_collapse2] = useState(false)

  useEffect(() => {
    initializeApp()
    return () => stop_update_location()
  }, [])

  initializeApp = async () => {
    const { status } = await Location.requestPermissionsAsync()
    setpermission_status(status)

    await Font.loadAsync({
      Roboto: require("native-base/Fonts/Roboto.ttf"),
      Roboto_medium: require("native-base/Fonts/Roboto_medium.ttf")
    })
    setloadfont(false)
  }

  useInterval(() => (
    update_location()
  ), updatePeriod);

  update_location = async () => {

    await Location.startLocationUpdatesAsync(LOCATION_TASK_NAME, {
      accuracy: Location.Accuracy.High,
    })

    if (background_location) {
      const e = background_location.coords
      setacc(e.accuracy)
      setaltitude(e.altitude)
      setheading(e.heading)
      setlatitude(e.latitude)
      setlonggitude(e.longitude)
      setspeed(e.speed)
      settime(Date(background_location.timestamp).toString())
    }
  }

  stop_update_location = async () => {
    setupdatePeriod(null)
    const taskRegistered = await TaskManager.isTaskRegisteredAsync(LOCATION_TASK_NAME)
    if (taskRegistered) {
      await TaskManager.unregisterTaskAsync(LOCATION_TASK_NAME)
    }
  }

  search_address = async () => {
    if (background_location) {
      const _address = await Location.reverseGeocodeAsync(background_location.coords)
      setaddress(_address[0])
    }
  }

  open_map = () => {
    if (background_location) {
      const e = background_location.coords
      openMap({ latitude: e.latitude, longitude: e.longitude })
    }
  }

  write_to_clipboard = (text) => {
    Clipboard.setString(text);
  }

  if (permission_status !== 'granted') {
    <Container style={{ backgroundColor: '#1C2833' }}>
      <Text>App needs location permission granted</Text>
    </Container>
  }

  if (loadfont) {
    return <Container style={{ backgroundColor: '#1C2833' }}><Spinner /></Container>
  }

  return (
    <Container style={{ backgroundColor: '#1C2833' }}>
      <Content style={{ marginTop: 25 }}>
        <Card style={{ backgroundColor: '#1C2833' }}>
          <CardItem header bordered button style={{ backgroundColor: "#1C2833" }}
            onPress={() => setcard_collapse1(!card_collapse1)}>
            <View style={{ flex: 1, flexDirection: 'row', justifyContent: 'space-between' }}>
              <H3 style={{ color: '#D0D3D4' }}>Realtime GPS</H3>
              <H3 style={{ color: "#D0D3D4" }}>+</H3>
            </View>
          </CardItem>
          {card_collapse1 ? null :
            <CardItem bordered style={{ backgroundColor: '#1C2833' }}>
              <Text style={{ color: "#D0D3D4", fontStyle: 'italic' }}>
                Accuracy: {acc} Meters{'\n'}
                Altitude: {altitude} Meters{'\n'}
                Heading: {heading} Degree{'\n'}
                Latitude: {latitude} Degree{'\n'}
                Longitude: {longitude} Degree{'\n'}
                Speed: {speed} M/S{'\n'}
                Time: {time}
              </Text>
              <Button light small bordered icon style={{ position: 'absolute', right: 10, top: 10 }}
                onPress={() => { write_to_clipboard(latitude + ',' + longitude) }}>
                <Icon name='copy'></Icon>
              </Button>
            </CardItem>
          }
          <CardItem header bordered button style={{ backgroundColor: '#1C2833' }}
            onPress={() => setcard_collapse2(!card_collapse2)}>
            <View style={{ flex: 1, flexDirection: 'row', justifyContent: 'space-between' }}>
              <H3 style={{ color: '#D0D3D4' }}>Address (update location first)</H3>
              <H3 style={{ color: '#D0D3D4' }}>+</H3>
            </View>
          </CardItem>
          {card_collapse2 ? null :
            <CardItem style={{ backgroundColor: '#1C2833' }}>
              {address ?
                <Text style={{ color: '#D0D3D4' }}>
                  {address.name}  {address.street} {'\n'}
                  {address.city} {address.region} {address.country}  {address.postalCode}
                </Text>
                : <Text style={{ color: '#D0D3D4' }}>Waiting for GPS location</Text>}
              <Button light small bordered icon style={{ position: 'absolute', right: 10, top: 10 }}
                onPress={() => {
                  write_to_clipboard(address.name + ' ' + address.street + ' ' +
                    address.city + ' ' + address.region + ' ' + address.country + ' ' + address.postalCode)
                }}>
                <Icon name='copy'></Icon>
              </Button>
            </CardItem>
          }
        </Card>

        <View style={{ flexDirection: 'row', justifyContent: "space-around", marginTop: 10 }}>
          <Button small style={{ width: 150, justifyContent: "center" }} onPress={() => setupdatePeriod(1000)}>
            <Text>Update Location</Text>
          </Button>
          <Button small style={{ width: 150, justifyContent: "center" }} onPress={() => stop_update_location()}>
            <Text>Stop Update</Text>
          </Button>
        </View>
        <View style={{ flexDirection: 'row', justifyContent: "space-around", marginTop: 10 }}>
          <Button small style={{ width: 150, justifyContent: "center" }} onPress={() => search_address()}>
            <Text>Search Address</Text>
          </Button>
          <Button small style={{ width: 150, justifyContent: "center" }} onPress={() => open_map()}>
            <Text>Open Map</Text>
          </Button>
        </View>
      </Content>
    </Container>
  );
}

TaskManager.defineTask(LOCATION_TASK_NAME, ({ data, error }) => {
  if (error) {
    // Error occurred - check `error.message` for more details.
    console.log(error.message)
    return;
  }
  if (data) {
    const { locations } = data;
    // do something with the locations captured in the background
    background_location = locations[locations.length - 1]
  }
});

reference:
http://chuanshuoge2.blogspot.com/2020/03/expo-location.html

trade suspended for 15 min




Saturday 7 March 2020

expo location

//location

Object {
  "coords": Object {
    "accuracy": 12,
    "altitude": 1084.7,
    "heading": 143.8192901611328,
    "latitude": 51.0763851,
    "longitude": -113.9689081,
    "speed": 0.8038911819458008,
  },
  "mocked": false,
  "timestamp": 1583619293000,
}

reference:
expo-location
https://docs.expo.io/versions/latest/sdk/location/

expo-task-manager
https://docs.expo.io/versions/latest/sdk/task-manager/
https://github.com/expo/expo/issues/3256

useInterval
https://chuanshuoge2.blogspot.com/2019/10/react-hooks-useinterval.html

expo-file-system read write string
https://stackoverflow.com/questions/54586216/how-to-create-text-file-in-react-native-expo
https://docs.expo.io/versions/latest/sdk/filesystem/#filesystemreadasstringasyncfileuri-options
https://forums.expo.io/t/append-content-to-file-expo-filesystem/4951

react native open map
https://www.npmjs.com/package/react-native-open-maps

react native map
https://medium.com/@rishi.vedpathak/react-native-map-with-real-time-location-selection-for-android-739c23f04930

write to clipboard
https://medium.com/the-react-native-log/react-native-basics-copy-to-clipboard-86023cda4175

redux persist store
https://itnext.io/react-native-why-you-should-be-using-redux-persist-8ad1d68fa48b
https://medium.com/survival-development/simple-redux-persist-configuration-in-react-native-expo-environment-5cae7c4a22
https://github.com/Cretezy/redux-persist-expo-securestore

Thursday 5 March 2020

expo forex

app opens, show today's exchange rate, default base 1000 CAD

tap on EU, change to 100 EUR

values of other currencies updates too

long press on EU, enter past 30 day rate, default base is CAD, tap on Canada

flag list opens, select US

base currency changed to USD. Tap on EU, select HongKong

graph shows USD VS. HKD now

//app.js

import React, { useState, useEffect } from 'react';
import { ScrollView, Modal, Dimensions, Image } from 'react-native';
import axios from 'axios'
import * as Font from 'expo-font'
import {
  Container, Header, Title, Content, Footer,
  FooterTab, Button, Left, Right, Body, Icon, Text,
  Accordion, Card, CardItem, Thumbnail, ListItem,
  CheckBox, DatePicker, DeckSwiper, View, Fab,
  Badge, Form, Item, Input, Label, Picker, Textarea,
  Switch, Radio, Spinner, Tab, Tabs, TabHeading,
  ScrollableTab, H1, H2, H3, Drawer,
} from 'native-base';
import { Col, Row, Grid } from "react-native-easy-grid";
import ForexListItem from './forexItem'
import { Ionicons } from '@expo/vector-icons';
import { LineChart } from 'react-native-chart-kit'
import { countryFlag } from './countryFlag'
import FlagList from './FlagList'

export default function App() {
  const [loadfont, setloadfont] = useState(true)
  const [data, setdata] = useState(null)
  const [cad, setcad] = useState(1000)
  const [show_amount, setshow_amount] = useState(false)
  const [input_currency, setinput_currency] = useState('CAD')
  const [input_amount, setinput_amount] = useState(1000)
  const [btc_rate, setbtc_rate] = useState(null)
  const [x, setx] = useState([])
  const [y, sety] = useState([])
  const [show_chart, setshow_chart] = useState(false)
  const [base_currency, setbase_currency] = useState(null)
  const [convert_currency, setconvert_currency] = useState(null)
  const [base_or_conversion, setbase_or_conversion] = useState(false)
  const [show_flags, setshow_flags] = useState(false)

  useEffect(() => {
    initializeApp()
  }, [])

  initializeApp = async () => {
    await Font.loadAsync({
      Roboto: require("native-base/Fonts/Roboto.ttf"),
      Roboto_medium: require("native-base/Fonts/Roboto_medium.ttf")
    });
    setloadfont(false)

    fetch_latest()
  }

  fetch_latest = () => {
    axios({
      method: 'get',
      url: 'https://api.exchangeratesapi.io/latest?base=CAD',
    })
      .then(response => {
        setdata(response.data.rates)
        fetch_btc(response.data.rates.USD)
      })
      .catch(function (error) {
        alert(error);
        setTimeout(() => {
          fetch_latest()
        }, 5000);
      });
  }

  fetch_btc = (CADUSD) => {
    axios({
      method: 'get',
      url: 'https://api.coindesk.com/v1/bpi/currentprice.json',
    })
      .then(response => {
        const rate = CADUSD / parseFloat(response.data.bpi.USD.rate.replace(',', ''))
        setbtc_rate(rate)
      })
      .catch(function (error) {
        alert(error);
        setTimeout(() => {
          fetch_btc(CADUSD)
        }, 5000);
      });
  }

  forex_button_press = (value, currency) => {
    setshow_amount(true)
    setinput_amount(value)
    setinput_currency(currency)
  }

  exchange_button_press = () => {
    setshow_amount(false)
    input_currency === 'BTC' ?
      setcad(input_amount / btc_rate)
      : setcad(input_amount / data[input_currency])
  }

  load_history = (a, b) => {
    setbase_currency(a)
    setconvert_currency(b)

    const d = new Date()
    const end = d.toISOString().split('T')[0]
    d.setDate(-30)
    const start = d.toISOString().split('T')[0]

    const url = 'https://api.exchangeratesapi.io/history?start_at=' + start
      + '&end_at=' + end + '&symbols=' + b + '&base=' + a

    axios({
      method: 'get',
      url: url,
    })
      .then(response => {
        const history = JSON.stringify(response.data.rates)
        const history_clean = history.replace(/{/g, '').replace(/}/g, '')
          .replace(/"/g, '').replace(/:/g, '').split(',')

        let date = [], rate = []
        history_clean.sort().map(item => {
          const item_split = item.split(b)
          date.push(item_split[0])
          rate.push(parseFloat(item_split[1]))
        })

        setx(date)
        sety(rate)
        setshow_chart(true)
      })
      .catch(function (error) {
        alert(error);
      });
  }

  changeCountry = (currency) => {
    base_or_conversion ?
      load_history(base_currency, currency)
      : load_history(currency, convert_currency)
  }

  if (loadfont || !data) {
    return (<Container style={{ backgroundColor: '#1C2833' }}>
      <Content><Spinner /></Content></Container>)
  }

  return (
    <Container style={{ backgroundColor: '#1C2833', marginTop: 25 }}>
      <Modal
        animationType="slide"
        transparent={true}
        visible={show_amount}
      >
        <Content>

          <Item style={{ backgroundColor: 'white' }}>
            <Input
              style={{ height: 100, fontSize: 30 }}
              keyboardType="number-pad"
              value={input_amount}
              onChangeText={e => setinput_amount(e ? e.match(/^([0-9]+(\.[0-9]+)?)/g)[0] : '1')}
              onSubmitEditing={() => exchange_button_press()} />
            <Text style={{ fontSize: 30 }}>{input_currency}</Text>
            <Button large transparent onPress={() => exchange_button_press()}>
              <Icon active name='swap' style={{ fontSize: 50 }} />
            </Button>
            <Button large transparent onPress={() => setshow_amount(false)}>
              <Icon active name='close' style={{ fontSize: 50 }} />
            </Button>
          </Item>
        </Content>
      </Modal>

      <Modal
        animationType="fade"
        transparent={false}
        visible={show_chart}
      >
        <Container style={{ backgroundColor: '#1C2833' }}>
          <Header >
            <Left>
              <Button transparent onPress={() => setshow_chart(false)}>
                <Ionicons name='ios-arrow-round-back' size={40} color="white"></Ionicons>
              </Button>
            </Left>
            <Body style={{ alignItems: 'center' }}>
              <Title>Last 30 Day Rate</Title>
            </Body>
            <Right>
            </Right>
          </Header>

          <View style={{ flexDirection: 'row', width: '100%', justifyContent: 'space-around' }}>
            {base_currency ?
              <Button transparent onPress={() => { setbase_or_conversion(false); setshow_flags(true) }}>
                <Image
                  style={{ width: 50, height: 40, marginTop: 5 }}
                  source={countryFlag[base_currency]}
                />
              </Button>
              : null}

            {convert_currency ?
              <Button transparent onPress={() => { setbase_or_conversion(true); setshow_flags(true) }}>
                <Image
                  style={{ width: 50, height: 40, marginTop: 5 }}
                  source={countryFlag[convert_currency]}
                />
              </Button>
              : null}
          </View>

          <LineChart
            data={{
              labels: [x[0]],
              datasets: [{
                data: y
              }]
            }}
            width={Dimensions.get('window').width} // from react-native
            height={300}
            chartConfig={{
              backgroundColor: 'blue',
              backgroundGradientFrom: 'green',
              backgroundGradientTo: '#ffa726',
              decimalPlaces: 3, // optional, defaults to 2dp
              color: (opacity = 1) => `rgba(255, 255, 255, 0.7)`,

            }}
            bezier
            style={{
              marginVertical: 8,
              borderRadius: 16
            }}
          />
        </Container>
      </Modal>

      <FlagList show={show_flags} close={() => setshow_flags(false)}
        country_selection={(currency) => changeCountry(currency)}></FlagList>

      <Content>
        <ScrollView>
          <Grid>
            <Col style={{ alignItems: 'center' }}>
              <Row style={{ height: 50 }}>
                <ForexListItem currency='CAD' rate={data.CAD} CAD={cad}
                  buttonPress={(value) => forex_button_press(value, 'CAD')}
                  buttonLongPress={() => { }}
                ></ForexListItem>
              </Row>

              <Row style={{ height: 50 }}>
                <ForexListItem currency='CNY' rate={data.CNY} CAD={cad}
                  buttonPress={(value) => forex_button_press(value, 'CNY')}
                  buttonLongPress={() => load_history('CAD', 'CNY')}
                ></ForexListItem>
              </Row>

              <Row style={{ height: 50 }}>
                <ForexListItem currency='AUD' rate={data.AUD} CAD={cad}
                  buttonPress={(value) => forex_button_press(value, 'AUD')}
                  buttonLongPress={() => load_history('CAD', 'AUD')}
                ></ForexListItem>
              </Row>

              <Row style={{ height: 50 }}>
                <ForexListItem currency='HKD' rate={data.HKD} CAD={cad}
                  buttonPress={(value) => forex_button_press(value, 'HKD')}
                  buttonLongPress={() => load_history('CAD', 'HKD')}
                ></ForexListItem>
              </Row>

              <Row style={{ height: 50 }}>
                <ForexListItem currency='ISK' rate={data.ISK} CAD={cad}
                  buttonPress={(value) => forex_button_press(value, 'ISK')}
                  buttonLongPress={() => load_history('CAD', 'ISK')}
                ></ForexListItem>
              </Row>

              <Row style={{ height: 50 }}>
                <ForexListItem currency='HUF' rate={data.HUF} CAD={cad}
                  buttonPress={(value) => forex_button_press(value, 'HUF')}
                  buttonLongPress={() => load_history('CAD', 'HUF')}
                ></ForexListItem>
              </Row>

              <Row style={{ height: 50 }}>
                <ForexListItem currency='SEK' rate={data.SEK} CAD={cad}
                  buttonPress={(value) => forex_button_press(value, 'SEK')}
                  buttonLongPress={() => load_history('CAD', 'SEK')}
                ></ForexListItem>
              </Row>

              <Row style={{ height: 50 }}>
                <ForexListItem currency='BRL' rate={data.BRL} CAD={cad}
                  buttonPress={(value) => forex_button_press(value, 'BRL')}
                  buttonLongPress={() => load_history('CAD', 'BRL')}
                ></ForexListItem>
              </Row>

              <Row style={{ height: 50 }}>
                <ForexListItem currency='MYR' rate={data.MYR} CAD={cad}
                  buttonPress={(value) => forex_button_press(value, 'MYR')}
                  buttonLongPress={() => load_history('CAD', 'MYR')}
                ></ForexListItem>
              </Row>

              <Row style={{ height: 50 }}>
                <ForexListItem currency='NOK' rate={data.NOK} CAD={cad}
                  buttonPress={(value) => forex_button_press(value, 'NOK')}
                  buttonLongPress={() => load_history('CAD', 'NOK')}
                ></ForexListItem>
              </Row>

              <Row style={{ height: 50 }}>
                <ForexListItem currency='MXN' rate={data.MXN} CAD={cad}
                  buttonPress={(value) => forex_button_press(value, 'MXN')}
                  buttonLongPress={() => load_history('CAD', 'MXN')}
                ></ForexListItem>
              </Row>

              <Row style={{ height: 50 }}>
                <ForexListItem currency='PLN' rate={data.PLN} CAD={cad}
                  buttonPress={(value) => forex_button_press(value, 'PLN')}
                  buttonLongPress={() => load_history('CAD', 'PLN')}
                ></ForexListItem>
              </Row>
            </Col>
            <Col>
              <Row style={{ height: 50 }}>
                <ForexListItem currency='USD' rate={data.USD} CAD={cad}
                  buttonPress={(value) => forex_button_press(value, 'USD')}
                  buttonLongPress={() => load_history('CAD', 'USD')}
                ></ForexListItem>
              </Row>

              <Row style={{ height: 50 }}>
                <ForexListItem currency='EUR' rate={data.EUR} CAD={cad}
                  buttonPress={(value) => forex_button_press(value, 'EUR')}
                  buttonLongPress={() => load_history('CAD', 'EUR')}
                ></ForexListItem>
              </Row>

              <Row style={{ height: 50 }}>
                <ForexListItem currency='GBP' rate={data.GBP} CAD={cad}
                  buttonPress={(value) => forex_button_press(value, 'GBP')}
                  buttonLongPress={() => load_history('CAD', 'GBP')}
                ></ForexListItem>
              </Row>

              <Row style={{ height: 50 }}>
                <ForexListItem currency='CHF' rate={data.CHF} CAD={cad}
                  buttonPress={(value) => forex_button_press(value, 'CHF')}
                  buttonLongPress={() => load_history('CAD', 'CHF')}
                ></ForexListItem>
              </Row>

              <Row style={{ height: 50 }}>
                <ForexListItem currency='PHP' rate={data.PHP} CAD={cad}
                  buttonPress={(value) => forex_button_press(value, 'PHP')}
                  buttonLongPress={() => load_history('CAD', 'PHP')}
                ></ForexListItem>
              </Row>

              <Row style={{ height: 50 }}>
                <ForexListItem currency='CZK' rate={data.CZK} CAD={cad}
                  buttonPress={(value) => forex_button_press(value, 'CZK')}
                  buttonLongPress={() => load_history('CAD', 'CZK')}
                ></ForexListItem>
              </Row>

              <Row style={{ height: 50 }}>
                <ForexListItem currency='IDR' rate={data.IDR} CAD={cad}
                  buttonPress={(value) => forex_button_press(value, 'IDR')}
                  buttonLongPress={() => load_history('CAD', 'IDR')}
                ></ForexListItem>
              </Row>

              <Row style={{ height: 50 }}>
                <ForexListItem currency='HRK' rate={data.HRK} CAD={cad}
                  buttonPress={(value) => forex_button_press(value, 'HRK')}
                  buttonLongPress={() => load_history('CAD', 'HRK')}
                ></ForexListItem>
              </Row>

              <Row style={{ height: 50 }}>
                <ForexListItem currency='BGN' rate={data.BGN} CAD={cad}
                  buttonPress={(value) => forex_button_press(value, 'BGN')}
                  buttonLongPress={() => load_history('CAD', 'BGN')}
                ></ForexListItem>
              </Row>

              <Row style={{ height: 50 }}>
                <ForexListItem currency='NZD' rate={data.NZD} CAD={cad}
                  buttonPress={(value) => forex_button_press(value, 'NZD')}
                  buttonLongPress={() => load_history('CAD', 'NZD')}
                ></ForexListItem>
              </Row>

              <Row style={{ height: 50 }}>
                <ForexListItem currency='SGD' rate={data.SGD} CAD={cad}
                  buttonPress={(value) => forex_button_press(value, 'SGD')}
                  buttonLongPress={() => load_history('CAD', 'SGD')}
                ></ForexListItem>
              </Row>
            </Col>
            <Col>
              <Row style={{ height: 50 }}>
                {btc_rate ?
                  <ForexListItem currency='BTC' rate={btc_rate} CAD={cad}
                    buttonPress={(value) => forex_button_press(value, 'BTC')}
                    buttonLongPress={() => { }}
                  ></ForexListItem> : <Spinner />
                }
              </Row>

              <Row style={{ height: 50 }}>
                <ForexListItem currency='JPY' rate={data.JPY} CAD={cad}
                  buttonPress={(value) => forex_button_press(value, 'JPY')}
                  buttonLongPress={() => load_history('CAD', 'JPY')}
                ></ForexListItem>
              </Row>

              <Row style={{ height: 50 }}>
                <ForexListItem currency='RUB' rate={data.RUB} CAD={cad}
                  buttonPress={(value) => forex_button_press(value, 'RUB')}
                  buttonLongPress={() => load_history('CAD', 'RUB')}
                ></ForexListItem>
              </Row>

              <Row style={{ height: 50 }}>
                <ForexListItem currency='KRW' rate={data.KRW} CAD={cad}
                  buttonPress={(value) => forex_button_press(value, 'KRW')}
                  buttonLongPress={() => load_history('CAD', 'KRW')}
                ></ForexListItem>
              </Row>

              <Row style={{ height: 50 }}>
                <ForexListItem currency='DKK' rate={data.DKK} CAD={cad}
                  buttonPress={(value) => forex_button_press(value, 'DKK')}
                  buttonLongPress={() => load_history('CAD', 'DKK')}
                ></ForexListItem>
              </Row>

              <Row style={{ height: 50 }}>
                <ForexListItem currency='RON' rate={data.RON} CAD={cad}
                  buttonPress={(value) => forex_button_press(value, 'RON')}
                  buttonLongPress={() => load_history('CAD', 'RON')}
                ></ForexListItem>
              </Row>

              <Row style={{ height: 50 }}>
                <ForexListItem currency='INR' rate={data.INR} CAD={cad}
                  buttonPress={(value) => forex_button_press(value, 'INR')}
                  buttonLongPress={() => load_history('CAD', 'INR')}
                ></ForexListItem>
              </Row>

              <Row style={{ height: 50 }}>
                <ForexListItem currency='THB' rate={data.THB} CAD={cad}
                  buttonPress={(value) => forex_button_press(value, 'THB')}
                  buttonLongPress={() => load_history('CAD', 'THB')}
                ></ForexListItem>
              </Row>

              <Row style={{ height: 50 }}>
                <ForexListItem currency='TRY' rate={data.TRY} CAD={cad}
                  buttonPress={(value) => forex_button_press(value, 'TRY')}
                  buttonLongPress={() => load_history('CAD', 'TRY')}
                ></ForexListItem>
              </Row>

              <Row style={{ height: 50 }}>
                <ForexListItem currency='ZAR' rate={data.ZAR} CAD={cad}
                  buttonPress={(value) => forex_button_press(value, 'ZAR')}
                  buttonLongPress={() => load_history('CAD', 'ZAR')}
                ></ForexListItem>
              </Row>

              <Row style={{ height: 50 }}>
                <ForexListItem currency='ILS' rate={data.ILS} CAD={cad}
                  buttonPress={(value) => forex_button_press(value, 'ILS')}
                  buttonLongPress={() => load_history('CAD', 'ILS')}
                ></ForexListItem>
              </Row>
            </Col>
          </Grid>
        </ScrollView>
      </Content>
    </Container>
  );
}

--------------------------
//forexItem.js

import React, { Component } from 'react';
import {
    Container, Header, Title, Content, Footer,
    FooterTab, Button, Left, Right, Body, Icon, Text,
    Accordion, Card, CardItem, Thumbnail, ListItem,
    CheckBox, DatePicker, DeckSwiper, View, Fab,
    Badge, Form, Item, Input, Label, Picker, Textarea,
    Switch, Radio, Spinner, Tab, Tabs, TabHeading,
    ScrollableTab, H1, H2, H3, Drawer,
} from 'native-base';
import { Image } from 'react-native';
import { countryFlag } from './countryFlag'

export default class ForexListItem extends Component {
    constructor(props) {
        super(props);
        this.state = {
        };
    }

    render() {
        const { currency, rate, CAD, buttonPress, buttonLongPress } = this.props
        const amount = currency === 'BTC' ? (rate * CAD).toFixed(4) : (rate * CAD).toFixed(2)

        return (
            <Button style={{ flex: 1, margin: 2, backgroundColor: 'rgba(255, 255, 255, 0.2)' }}
                onPress={() => buttonPress(amount)}
                onLongPress={() => buttonLongPress()}>
                {currency ?
                    <Image
                        style={{ width: 50, height: 40 }}
                        source={countryFlag[currency]}
                    />
                    : null}
                <View style={{ alignItems: 'center', flex: 1 }}>
                    <Text style={{ color: 'white', fontSize: 12 }}>{amount}</Text>
                    <Text style={{ color: 'gold', fontSize: 12 }}>{currency}</Text>

                </View>
            </Button>
        )
    }
}

reference:
https://www.npmjs.com/package/react-native-chart-kit
https://chuanshuoge2.blogspot.com/2020/02/free-foreign-exchange-rates-api.html
http://chuanshuoge2.blogspot.com/2020/02/nativebase-forex_6.html