Sunday 31 May 2020

ionic react share

click share button, action sheet pops up

swipe action sheet up for more options

select share with textnow
//tab1.tsx

import React, { useState } from 'react';
import {
  IonContent, IonHeader, IonPage, IonTitle, IonToolbar,
  IonAlert, IonButton, IonCard, IonCardHeader,
  IonCardSubtitle, IonCardTitle, IonCardContent,
  IonItem, IonIcon, IonLabel, IonBadge, IonList,
  IonItemDivider, IonCheckbox, IonChip, IonAvatar,
  IonGrid, IonRow, IonCol, IonInput, IonToggle,
  IonModal, IonRefresher, IonRefresherContent,
  IonTextarea, IonSelect, IonListHeader,
  IonSelectOption, IonButtons, IonBackButton,
  IonMenuButton, IonSegment, IonSearchbar,
  IonSegmentButton, IonFooter, IonText, IonToast,
  useIonViewDidEnter, useIonViewDidLeave,
  useIonViewWillEnter, useIonViewWillLeave
} from '@ionic/react';
import ExploreContainer from '../components/ExploreContainer';
import './Tab1.css';
import {
  person
} from 'ionicons/icons';
import { Plugins } from '@capacitor/core';

const Tab1: React.FC = () => {
  const { Share } = Plugins;

  const shareRet = async () => {
    await Share.share({
      title: 'See cool stuff',
      text: 'Really awesome thing you need to see right meow',
      url: 'http://ionicframework.com/',
      dialogTitle: 'Share with buddies'
    });
  }

  return (
    <IonPage>
      <IonHeader>
        <IonToolbar>
          <IonTitle>Share Examples</IonTitle>
        </IonToolbar>
      </IonHeader>
      <IonContent>
        <IonList>
          <IonItem>
            <IonButton onClick={() => shareRet()}>share</IonButton>
          </IonItem>
        </IonList>
      </IonContent>
    </IonPage>
  );
};

export default Tab1;

reference:
https://capacitor.ionicframework.com/docs/apis/share

ionic react screen orientation

page opens in portrait orientation

press landscape to fix screen in landscape orientation

press auto to unlock screen

rotate phone, screen rotates too

press portrait to fix screen in portrait orientation
//cmd

npm install @ionic-native/core
npm install @ionic-native/screen-orientation
npm install cordova-plugin-screen-orientation

---------------------------------------------
//tab3.tsx

import React, { useState, useEffect } from 'react';
import {
  IonContent, IonHeader, IonPage, IonTitle, IonToolbar,
  IonAlert, IonButton, IonCard, IonCardHeader,
  IonCardSubtitle, IonCardTitle, IonCardContent,
  IonItem, IonIcon, IonLabel, IonBadge, IonList,
  IonItemDivider, IonCheckbox, IonFab, IonFabButton,
  IonFabList, IonItemGroup, IonItemSliding,
  IonItemOptions, IonItemOption, IonNote, IonMenu,
  IonRouterOutlet, IonListHeader, IonMenuToggle,
  IonButtons, IonMenuButton, IonInput, IonSplitPane,
  IonPopover, IonSpinner, IonRadioGroup, IonRadio,
  IonRange, IonSearchbar, IonFooter, IonSegmentButton,
  IonSegment, IonToast, IonToggle, IonTextarea
} from '@ionic/react';
import ExploreContainer from '../components/ExploreContainer';
import './Tab3.css';
import {
  call, home, heart, pin, star,
  globe, basket, camera, bookmark
} from 'ionicons/icons';
import { ScreenOrientation } from '@ionic-native/screen-orientation'

const Tab3: React.FC = () => {
  const [oritation, setOritation] = useState<string>('')

  useEffect(() => {
    setOritation(ScreenOrientation.type)
  }, []);

  return (
    <IonPage>
      <IonHeader>
        <IonToolbar>
          <IonTitle>screenOrientation Example</IonTitle>
        </IonToolbar>
      </IonHeader>
      <IonContent>
        <IonList>
          <IonItem>
            <IonButton onClick={() => {
              ScreenOrientation.lock(ScreenOrientation.ORIENTATIONS.PORTRAIT);
              setOritation(ScreenOrientation.ORIENTATIONS.PORTRAIT)
            }}
            >Portrait</IonButton>
            <IonButton onClick={() => {
              ScreenOrientation.lock(ScreenOrientation.ORIENTATIONS.LANDSCAPE);
              setOritation(ScreenOrientation.ORIENTATIONS.LANDSCAPE)
            }}
            >Landscape</IonButton>
            <IonButton onClick={() => {
              ScreenOrientation.unlock();
              setOritation('auto')
            }}
            >Auto</IonButton>
          </IonItem>
          <IonItem>
            <IonLabel>Orientation:</IonLabel>
            {oritation}
          </IonItem>
        </IonList>
      </IonContent>
    </IonPage>

  );
};

export default Tab3;

reference:
https://stackoverflow.com/questions/57787916/what-is-the-right-way-to-use-ionic-native-cordova-plugins-with-ionic-react
https://ionicframework.com/docs/native/screen-orientation

Friday 29 May 2020

ionic react prompt



//tab2.tsx

import React, { useState } from 'react';
import {
  IonContent, IonHeader, IonPage, IonTitle, IonToolbar,
  IonAlert, IonButton, IonCard, IonCardHeader,
  IonCardSubtitle, IonCardTitle, IonCardContent,
  IonItem, IonIcon, IonLabel, IonDatetime, IonFooter,
  IonList, IonItemDivider, IonTextarea, IonAvatar,
  IonText, IonThumbnail, IonSelectOption, IonInput,
  IonSelect, IonCheckbox, IonToggle, IonRange,
  IonNote, IonRadio, IonItemSliding, IonItemOption,
  IonItemOptions, IonListHeader, IonChip, IonImg,
  IonLoading, IonProgressBar, IonSkeletonText,
  IonReorder, IonReorderGroup, IonSlide, IonSlides,
  IonTabBar, IonTabs, IonTabButton, IonBadge,
  IonRouterOutlet
} from '@ionic/react';
import ExploreContainer from '../components/ExploreContainer';
import './Tab2.css';
import {
  calendar, personCircle, map,
  informationCircle
} from 'ionicons/icons';
import { Plugins } from '@capacitor/core';

const Tab2: React.FC = () => {
  const { Modals } = Plugins;
  const [prompt, setPrompt] = useState<string>('')
  const [prompt2, setPrompt2] = useState<string>('')

  const showConfirm = async () => {
    let confirmRet = await Modals.confirm({
      title: 'Confirm',
      message: 'Are you sure you\'d like to press the red button?'
    });
    setPrompt(JSON.stringify(confirmRet))
  }

  const showPrompt = async () => {
    let promptRet = await Modals.prompt({
      title: 'Hello',
      message: 'What\'s your name?'
    });
    let promptRet2 = await Modals.prompt({
      title: 'Hello',
      message: 'How are you?'
    });
    setPrompt(JSON.stringify(promptRet))
    setPrompt2(JSON.stringify(promptRet2))
  }

  return (
    <IonPage>
      <IonHeader>
        <IonToolbar>
          <IonTitle>Modal Examples</IonTitle>
        </IonToolbar>
      </IonHeader>
      <IonContent>
        <IonList>
          <IonItem >
            <IonButton onClick={() => showConfirm()}>confirm</IonButton>
            <IonButton onClick={() => showPrompt()}>prompt</IonButton>
          </IonItem>
          <IonItem>
            {prompt}
          </IonItem>
          <IonItem>
            {prompt2}
          </IonItem>
        </IonList>
      </IonContent>
    </IonPage >
  )
}

export default Tab2;

reference:

麦积石窟

The 3D printing revolution

Thursday 28 May 2020

ionic react notification


click notify button, 2 secs later notification shows up
 //tab1.tsx

import React, { useState } from 'react';
import {
  IonContent, IonHeader, IonPage, IonTitle, IonToolbar,
  IonAlert, IonButton, IonCard, IonCardHeader,
  IonCardSubtitle, IonCardTitle, IonCardContent,
  IonItem, IonIcon, IonLabel, IonBadge, IonList,
  IonItemDivider, IonCheckbox, IonChip, IonAvatar,
  IonGrid, IonRow, IonCol, IonInput, IonToggle,
  IonModal, IonRefresher, IonRefresherContent,
  IonTextarea, IonSelect, IonListHeader,
  IonSelectOption, IonButtons, IonBackButton,
  IonMenuButton, IonSegment, IonSearchbar,
  IonSegmentButton, IonFooter, IonText, IonToast,
  useIonViewDidEnter, useIonViewDidLeave,
  useIonViewWillEnter, useIonViewWillLeave
} from '@ionic/react';
import ExploreContainer from '../components/ExploreContainer';
import './Tab1.css';
import {
  person
} from 'ionicons/icons';
import { RefresherEventDetail } from '@ionic/core';
import axios from 'axios'
import { Plugins } from '@capacitor/core';

const Tab1: React.FC = () => {
  const { LocalNotifications } = Plugins;
  const notifs = async () => {
    await LocalNotifications.schedule({
      notifications: [
        {
          title: "Team Up Event",
          body: "When:  Wed Jun 24, 2020\
          \nCalendar: Vacation/Out of Office\
          \nWho:  Bob\
          \nCreated by: Bob on Wed May 27, 2020 11:00 AM (Time zone: Mountain Time US & Ca)",
          id: 1,
          schedule: { at: new Date(Date.now() + 1000 * 2) },
          sound: undefined,
          attachments: undefined,
          actionTypeId: "",
          extra: null
        }
      ]
    });
  }

  return (
    <IonPage>
      <IonHeader>
        <IonToolbar>
          <IonTitle>notifications Examples</IonTitle>
        </IonToolbar>
      </IonHeader>
      <IonContent>
        <IonList>
          <IonItem>
            <IonButton onClick={() => notifs()}>notify</IonButton>
          </IonItem>
        </IonList>
      </IonContent>
    </IonPage>
  );
};

export default Tab1;

---------------------------
//capacitor.config.json

{
  "appId": "io.ionic.starter",
  "appName": "abc",
  "bundledWebRuntime": false,
  "npmClient": "npm",
  "webDir": "build",
  "cordova": {},
  "plugins": {
    "LocalNotifications": {
      "iconColor": "#488AFF"
    }
  }
}

------------------------------
reference:

Wednesday 27 May 2020

ionic react haptic

click vibrate, phone vibrates
//tab3.tsx

import React, { useState } from 'react';
import {
  IonContent, IonHeader, IonPage, IonTitle, IonToolbar,
  IonAlert, IonButton, IonCard, IonCardHeader,
  IonCardSubtitle, IonCardTitle, IonCardContent,
  IonItem, IonIcon, IonLabel, IonBadge, IonList,
  IonItemDivider, IonCheckbox, IonFab, IonFabButton,
  IonFabList, IonItemGroup, IonItemSliding,
  IonItemOptions, IonItemOption, IonNote, IonMenu,
  IonRouterOutlet, IonListHeader, IonMenuToggle,
  IonButtons, IonMenuButton, IonInput, IonSplitPane,
  IonPopover, IonSpinner, IonRadioGroup, IonRadio,
  IonRange, IonSearchbar, IonFooter, IonSegmentButton,
  IonSegment, IonToast, IonToggle, IonTextarea
} from '@ionic/react';
import ExploreContainer from '../components/ExploreContainer';
import './Tab3.css';
import {
  call, home, heart, pin, star,
  globe, basket, camera, bookmark
} from 'ionicons/icons';
import { Plugins, HapticsImpactStyle } from '@capacitor/core';

const Tab3: React.FC = () => {
  const { Haptics } = Plugins;

  return (
    <IonPage>
      <IonHeader>
        <IonToolbar>
          <IonTitle>Haptics Example</IonTitle>
        </IonToolbar>
      </IonHeader>
      <IonContent>
        <IonList>
          <IonItem>
            <IonButton onClick={() => Haptics.vibrate()}>vibrate</IonButton>
            <IonButton onClick={() => Haptics.impact({
              style: HapticsImpactStyle.Heavy
            })}>
              heavy</IonButton>
            <IonButton onClick={() => Haptics.impact({
              style: HapticsImpactStyle.Medium
            })}>
              medium</IonButton>
            <IonButton onClick={() => Haptics.impact({
              style: HapticsImpactStyle.Light
            })}>
              light</IonButton>
          </IonItem>
          <IonItem>
            <IonButton onClick={() => Haptics.selectionStart()}>start</IonButton>
            <IonButton onClick={() => Haptics.selectionChanged()}>change</IonButton>
            <IonButton onClick={() => Haptics.selectionEnd()}>end</IonButton>
          </IonItem>
          <span>
            some text, some text,<br />
            some text, some text,
            </span>

        </IonList>
      </IonContent>
    </IonPage>

  );
};

export default Tab3;

-------------------------
//androidManifest.xml

<?xml version='1.0' encoding='utf-8'?>
<manifest package="capacitor.android.plugins"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:amazon="http://schemas.amazon.com/apk/res/android">
    <uses-permission android:name="android.permission.VIBRATE" />
<application >

</application>

</manifest>

---------------------------
reference:
https://capacitor.ionicframework.com/docs/apis/haptics#example

Tuesday 26 May 2020

ionic react geoloaction

//tab2.tsx

import React, { useState } from 'react';
import {
  IonContent, IonHeader, IonPage, IonTitle, IonToolbar,
  IonAlert, IonButton, IonCard, IonCardHeader,
  IonCardSubtitle, IonCardTitle, IonCardContent,
  IonItem, IonIcon, IonLabel, IonDatetime, IonFooter,
  IonList, IonItemDivider, IonTextarea, IonAvatar,
  IonText, IonThumbnail, IonSelectOption, IonInput,
  IonSelect, IonCheckbox, IonToggle, IonRange,
  IonNote, IonRadio, IonItemSliding, IonItemOption,
  IonItemOptions, IonListHeader, IonChip, IonImg,
  IonLoading, IonProgressBar, IonSkeletonText,
  IonReorder, IonReorderGroup, IonSlide, IonSlides,
  IonTabBar, IonTabs, IonTabButton, IonBadge,
  IonRouterOutlet
} from '@ionic/react';
import ExploreContainer from '../components/ExploreContainer';
import './Tab2.css';
import {
  calendar, personCircle, map,
  informationCircle
} from 'ionicons/icons';
import { Plugins } from '@capacitor/core';

const Tab2: React.FC = () => {
  const { Geolocation } = Plugins;
  const [coordinates, setCoordinates] = useState<any>(null)
  const [watching, setWatching] = useState<boolean>(false)
  const [watchN, setWatchN] = useState<string>('')

  const getCurrentPosition = async () => {
    const coordinates = await Geolocation.getCurrentPosition();
    setCoordinates(coordinates.coords)
    console.log(coordinates.coords)
  }

  const watchPosition = () => {
    const wait = Geolocation.watchPosition({}, (position, err) => {
      setWatching(true)
      setWatchN(wait.toString())
      setCoordinates(position.coords)
      console.log(wait, position)
    })
  }

  const clearWatch = () => {
    Geolocation.clearWatch({ id: watchN })
    setWatching(false)
  }

  return (
    <IonPage>
      <IonHeader>
        <IonToolbar>
          <IonTitle>Geolocation Examples</IonTitle>
        </IonToolbar>
      </IonHeader>
      <IonContent>
        <IonList>
          <IonItem >
            <IonButton onClick={() => getCurrentPosition()}>get location</IonButton>
            <IonButton onClick={() => watchPosition()} disabled={watching}>watch location</IonButton>
            <IonButton onClick={() => clearWatch()}>clear watch</IonButton>
          </IonItem>
          <IonItem>
            <IonTextarea readonly>
              accuracy {coordinates ? coordinates.accuracy : ''} <br />
              latitude: {coordinates ? coordinates.latitude : ''} <br />
              longitude: {coordinates ? coordinates.longitude : ''}<br />
              altitude: {coordinates ? coordinates.altitude : ''} <br />
              altitude Accuracy: {coordinates ? coordinates.altitudeAccuracy : ''} <br />
              heading: {coordinates ? coordinates.heading : ''} <br />
              speed: {coordinates ? coordinates.speed : ''}
            </IonTextarea>
          </IonItem>
        </IonList>
      </IonContent>
    </IonPage >
  )
}

export default Tab2;

------------------------------------
androidManifext.xml

<?xml version='1.0' encoding='utf-8'?>
<manifest package="capacitor.android.plugins"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:amazon="http://schemas.amazon.com/apk/res/android">
    <!-- Geolocation API -->
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-feature android:name="android.hardware.location.gps" />
<application >

</application>

</manifest>

----------------------
reference:

Sunday 24 May 2020

power tower

https://www.amazon.ca/gp/product/B07VNVN7HJ/ref=ppx_yo_dt_b_asin_title_o00_s00?ie=UTF8&psc=1

ionic react nested route

on tab3, click link1

route to /tab3/details/:id
press back button

click link3, detail 3 page opens

app.tsx

import React from 'react';
import { Redirect, Route } from 'react-router-dom';
import {
  IonApp,
  IonIcon,
  IonLabel,
  IonRouterOutlet,
  IonTabBar,
  IonTabButton,
  IonTabs, IonSplitPane
} from '@ionic/react';
import { IonReactRouter } from '@ionic/react-router';
import { ellipse, square, triangle } from 'ionicons/icons';
import Tab1 from './pages/Tab1';
import Tab2 from './pages/Tab2';
import Tab3 from './pages/Tab3';
import Detail from './pages/Detail';

/* Core CSS required for Ionic components to work properly */
import '@ionic/react/css/core.css';

/* Basic CSS for apps built with Ionic */
import '@ionic/react/css/normalize.css';
import '@ionic/react/css/structure.css';
import '@ionic/react/css/typography.css';

/* Optional CSS utils that can be commented out */
import '@ionic/react/css/padding.css';
import '@ionic/react/css/float-elements.css';
import '@ionic/react/css/text-alignment.css';
import '@ionic/react/css/text-transformation.css';
import '@ionic/react/css/flex-utils.css';
import '@ionic/react/css/display.css';

/* Theme variables */
import './theme/variables.css';

const App: React.FC = () => (
  <IonApp>
    <IonReactRouter>
      <IonTabs>
        <IonRouterOutlet>
          <Route path="/tab1" component={Tab1} exact={true} />
          <Route path="/tab2" component={Tab2} exact={true} />
          <Route path="/tab3" component={Tab3} exact={true} />
          <Route path='/tab3/details/:id' component={Detail} />
          <Route path="/" render={() => <Redirect to="/tab1" />} exact={true} />
        </IonRouterOutlet>
        <IonTabBar slot="bottom">
          <IonTabButton tab="tab1" href="/tab1">
            <IonIcon icon={triangle} />
            <IonLabel>Tab 1</IonLabel>
          </IonTabButton>
          <IonTabButton tab="tab2" href="/tab2">
            <IonIcon icon={ellipse} />
            <IonLabel>Tab 2</IonLabel>
          </IonTabButton>
          <IonTabButton tab="tab3" href="/tab3">
            <IonIcon icon={square} />
            <IonLabel>Tab 3</IonLabel>
          </IonTabButton>
        </IonTabBar>
      </IonTabs>
    </IonReactRouter>
  </IonApp>
);

export default App;

---------------------------
tab3.tsx

import React, { useState } from 'react';
import {
  IonContent, IonHeader, IonPage, IonTitle, IonToolbar,
  IonAlert, IonButton, IonCard, IonCardHeader,
  IonCardSubtitle, IonCardTitle, IonCardContent,
  IonItem, IonIcon, IonLabel, IonBadge, IonList,
  IonItemDivider, IonCheckbox, IonFab, IonFabButton,
  IonFabList, IonItemGroup, IonItemSliding,
  IonItemOptions, IonItemOption, IonNote, IonMenu,
  IonRouterOutlet, IonListHeader, IonMenuToggle,
  IonButtons, IonMenuButton, IonInput, IonSplitPane,
  IonPopover, IonSpinner, IonRadioGroup, IonRadio,
  IonRange, IonSearchbar, IonFooter, IonSegmentButton,
  IonSegment, IonToast, IonToggle
} from '@ionic/react';
import ExploreContainer from '../components/ExploreContainer';
import './Tab3.css';
import {
  call, home, heart, pin, star,
  globe, basket, camera, bookmark
} from 'ionicons/icons';
import { IonReactRouter } from '@ionic/react-router';

const Tab3: React.FC = () => {

  return (
    <IonPage>
      <IonHeader>
        <IonToolbar>
          <IonTitle>Routes Example</IonTitle>
        </IonToolbar>
      </IonHeader>
      <IonContent>
        <IonList>
          <IonItem routerLink="/tab3/details/1">
            <IonLabel>Link 1</IonLabel>
          </IonItem>
          <IonItem routerLink="/tab3/details/2">
            <IonLabel>Link 2</IonLabel>
          </IonItem>
          <IonItem routerLink="/tab3/details/3">
            <IonLabel>Link 3</IonLabel>
          </IonItem>
        </IonList>
      </IonContent>
    </IonPage>

  );
};

export default Tab3;

--------------------------------
detail.tsx

import React, { useState } from 'react';
import {
    IonContent, IonHeader, IonPage, IonTitle, IonToolbar,
    IonAlert, IonButton, IonCard, IonCardHeader,
    IonCardSubtitle, IonCardTitle, IonCardContent,
    IonItem, IonIcon, IonLabel, IonBadge, IonList,
    IonItemDivider, IonCheckbox, IonChip, IonAvatar,
    IonGrid, IonRow, IonCol, IonInput, IonToggle,
    IonModal, IonRefresher, IonRefresherContent,
    IonTextarea, IonSelect, IonListHeader,
    IonSelectOption, IonButtons, IonBackButton,
    IonMenuButton, IonSegment, IonSearchbar,
    IonSegmentButton, IonFooter, IonText, IonToast,
    useIonViewDidEnter, useIonViewDidLeave,
    useIonViewWillEnter, useIonViewWillLeave,
} from '@ionic/react';
import {
    person
} from 'ionicons/icons';
import { RefresherEventDetail } from '@ionic/core';
import axios from 'axios'
import { RouteComponentProps } from "react-router-dom";

interface Props extends RouteComponentProps<{
    id: string;
}> { }

const Detail: React.FC<Props> = ({ match }) => {

    return (
        <IonPage>
            <IonHeader>
                <IonToolbar>
                    <IonButtons slot="start">
                        <IonBackButton />
                    </IonButtons>
                    <IonTitle>Detail Page</IonTitle>
                </IonToolbar>
            </IonHeader>
            <IonContent>
                <IonList>
                    <IonItem>Detail # {match.params.id}</IonItem>
                </IonList>
            </IonContent>
        </IonPage>
    );
};

export default Detail;

reference:

Friday 15 May 2020

Florence trip

timesheet

May 15, fly to Florence, 10h
May 16, testing, 8h
May 17, testing, 10h
May 18, testing, 10h
May 19, testing, 10h
May 20, testing, 10h
May 21, fly back, 8h
May 22, fly back, 10h

expense

May 15, baggage, uber
May 16, uber
May 17, phone
May 18, hotel
May 19, hotel
May 21, gas, baggage, taxi
May 22, uber x 2

Saturday 9 May 2020

Mammoth lakes trip

Timesheet

May 8, ferry to Mammoth lakes, 20h
May 9, troubleshoot, 10h
May 10, troubleshoot, 10h
May 11, test flight, 8h
May 12, troubleshoot, 10h
May 13, testing 8h
May 14, driving 8h

Expense

May 7, phone charge
May 8, baggage, taxi
May 9, gas
May 14, gas x 2

Wednesday 6 May 2020

ionic react lifecycle

will enter event fired

did enter event fired

page opens

switch page, will leave event fired

did leave event fired

page switches
//tab1.tsx

import React, { useState } from 'react';
import {
  IonContent, IonHeader, IonPage, IonTitle, IonToolbar,
  IonAlert, IonButton, IonCard, IonCardHeader,
  IonCardSubtitle, IonCardTitle, IonCardContent,
  IonItem, IonIcon, IonLabel, IonBadge, IonList,
  IonItemDivider, IonCheckbox, IonChip, IonAvatar,
  IonGrid, IonRow, IonCol, IonInput, IonToggle,
  IonModal, IonRefresher, IonRefresherContent,
  IonTextarea, IonSelect, IonListHeader,
  IonSelectOption, IonButtons, IonBackButton,
  IonMenuButton, IonSegment, IonSearchbar,
  IonSegmentButton, IonFooter, IonText, IonToast,
  useIonViewDidEnter, useIonViewDidLeave,
  useIonViewWillEnter, useIonViewWillLeave
} from '@ionic/react';
import ExploreContainer from '../components/ExploreContainer';
import './Tab1.css';
import {
  person
} from 'ionicons/icons';
import { RefresherEventDetail } from '@ionic/core';
import axios from 'axios'

const Tab1: React.FC = () => {

  useIonViewDidEnter(() => {
    alert('Did enter event fired')
  });

  useIonViewDidLeave(() => {
    alert('Did leave event fired');
  });

  useIonViewWillEnter(() => {
    alert('Will enter event fired')
  });

  useIonViewWillLeave(() => {
    alert('Will leave event fired');
  });

  return (
    <IonPage>
      <IonHeader>
        <IonToolbar>
          <IonTitle>Ionic life cycle Examples</IonTitle>
        </IonToolbar>
      </IonHeader>
      <IonContent>

      </IonContent>
    </IonPage>
  );
};

export default Tab1;

reference:
https://ionicframework.com/docs/react/lifecycle

Monday 4 May 2020

ionic react FileSystem delete

snap 2 photos

press first photo, press delete on the bottom action sheet

close app and open app again, first photo is permanently deleted 

//hooks/usePhotoGallery.tsx

import { useState, useEffect } from "react";
import { useCamera } from '@ionic/react-hooks/camera';
import { useFilesystem, base64FromPath } from '@ionic/react-hooks/filesystem';
import { useStorage } from '@ionic/react-hooks/storage';
import { isPlatform } from '@ionic/react';
import {
    CameraResultType, CameraSource, CameraPhoto,
    Capacitor, FilesystemDirectory
} from "@capacitor/core";

export interface Photo {
    filepath: string;
    webviewPath?: string;
    base64?: string;
}

const PHOTO_STORAGE = "photos";

export function usePhotoGallery() {
    const [photos, setPhotos] = useState<Photo[]>([]);
    const { getPhoto } = useCamera();
    const { deleteFile, getUri, readFile, writeFile } = useFilesystem();
    const { get, set } = useStorage();

    useEffect(() => {
        const loadSaved = async () => {
            const photosString = await get('photos');
            const photos = (photosString ? JSON.parse(photosString) : []) as Photo[];
            for (let photo of photos) {
                const file = await readFile({
                    path: photo.filepath,
                    directory: FilesystemDirectory.Data
                });
                photo.base64 = `data:image/jpeg;base64,${file.data}`;
            }
            setPhotos(photos);
        };
        loadSaved();
    }, [get, readFile]);

    const takePhoto = async () => {
        const cameraPhoto = await getPhoto({
            resultType: CameraResultType.Uri,
            source: CameraSource.Camera,
            quality: 100
        });

        const fileName = new Date().getTime() + '.jpeg';

        const savedFileImage = await savePicture(cameraPhoto, fileName);

        const newPhotos = [savedFileImage, ...photos];
        setPhotos(newPhotos)

        set(PHOTO_STORAGE, JSON.stringify(newPhotos.map(p => {
            // Don't save the base64 representation of the photo data,
            // since it's already saved on the Filesystem
            const photoCopy = { ...p };
            delete photoCopy.base64;
            return photoCopy;
        })));
    };

    const savePicture = async (photo: CameraPhoto, fileName: string): Promise<Photo> => {
        const base64Data = await base64FromPath(photo.webPath!);
        const savedFile = await writeFile({
            path: fileName,
            data: base64Data,
            directory: FilesystemDirectory.Data
        });

        // Use webPath to display the new image instead of base64 since it's
        // already loaded into memory
        return {
            filepath: fileName,
            webviewPath: photo.webPath
        };
    };

    //delete photo from app storage, update photo array record
    const deletePhoto = async (photo: Photo) => {
        // Remove this photo from the Photos reference data array
        const newPhotos = photos.filter(p => p.filepath !== photo.filepath);

        // Update photos array cache by overwriting the existing photo array
        set(PHOTO_STORAGE, JSON.stringify(newPhotos));

        // delete photo file from filesystem
        //find file name after directory path
        const filename = photo.filepath.substr(photo.filepath.lastIndexOf('/') + 1);
        await deleteFile({
            path: filename,
            directory: FilesystemDirectory.Data
        });
        setPhotos(newPhotos);
    };

    return {
        photos,
        takePhoto,
        deletePhoto
    };
}

----------------------------------
//pages/tab2.tsx

import React, { useState } from 'react';
import {
  IonContent, IonHeader, IonPage, IonTitle, IonToolbar, IonButton,
  IonGrid, IonRow, IonCol, IonImg, IonFab, IonFabButton, IonIcon,
  IonActionSheet
} from '@ionic/react';
import ExploreContainer from '../components/ExploreContainer';
import './Tab2.css';
import { usePhotoGallery, Photo } from '../hooks/usePhotoGallery';
import { camera, trash, close } from 'ionicons/icons';

const Tab2: React.FC = () => {
  const { photos, takePhoto, deletePhoto } = usePhotoGallery();
  const [photoToDelete, setPhotoToDelete] = useState<Photo>();

  return (
    <IonPage>
      <IonHeader>
        <IonToolbar>
          <IonTitle>Camera Example</IonTitle>
        </IonToolbar>
      </IonHeader>
      <IonContent>
        <IonFab vertical='bottom' horizontal='center' slot='fixed'>
          <IonFabButton onClick={() => takePhoto()}>
            <IonIcon icon={camera} />
          </IonFabButton>
        </IonFab>
        <IonGrid>
          <IonRow>
            {photos.map((photo, index) => (
              <IonCol size="6" key={index}>
                <IonImg onClick={() => setPhotoToDelete(photo)}
                  src={photo.base64 ?? photo.webviewPath} />
              </IonCol>
            ))}
          </IonRow>
        </IonGrid>
        <IonActionSheet
          isOpen={!!photoToDelete}
          buttons={[{
            text: 'Delete',
            role: 'destructive',
            icon: trash,
            cssClass: 'danger-action',
            handler: () => {
              if (photoToDelete) {
                deletePhoto(photoToDelete);
                setPhotoToDelete(undefined);
              }
            }
          }, {
            text: 'Cancel',
            icon: close,
            role: 'cancel'
          }]}
          onDidDismiss={() => setPhotoToDelete(undefined)}
        />
      </IonContent>
    </IonPage>
  );
};

export default Tab2;

------------------------------
//pages/tab2.css

.danger-action {
    --color: red !important;
    --button-color: red !important;
  }

reference:
https://ionicframework.com/docs/react/your-first-app/7-live-reload
https://www.w3schools.com/jsref/tryit.asp?filename=tryjsref_lastindexof
https://www.w3schools.com/jsref/tryit.asp?filename=tryjsref_substr

actionsheet button color
https://stackoverflow.com/questions/44129065/how-to-change-the-color-of-custom-icons-in-an-action-sheet-ionic
https://github.com/ionic-team/ionic/issues/17300

Sunday 3 May 2020

ionic react FileSystem load

snap 2 photos, close app

open camera folder, images are saved

delete pine cone picture 

pine cone picture deleted

open app again, pine cone picture is still loaded
when pictures are taken, they are saved in 2 locations: 
app storage and phone camera folder

open app setting and clear storage

app storage cleaned

open app again, no more picture loaded

open camera folder, locus picture is still on file
//hooks/usePhotoGallery.tsx

import { useState, useEffect } from "react";
import { useCamera } from '@ionic/react-hooks/camera';
import { useFilesystem, base64FromPath } from '@ionic/react-hooks/filesystem';
import { useStorage } from '@ionic/react-hooks/storage';
import { isPlatform } from '@ionic/react';
import {
    CameraResultType, CameraSource, CameraPhoto,
    Capacitor, FilesystemDirectory
} from "@capacitor/core";

export interface Photo {
    filepath: string;
    webviewPath?: string;
    base64?: string;
}

const PHOTO_STORAGE = "photos";

export function usePhotoGallery() {
    const [photos, setPhotos] = useState<Photo[]>([]);
    const { getPhoto } = useCamera();
    const { deleteFile, getUri, readFile, writeFile } = useFilesystem();
    const { get, set } = useStorage();

    //load pictures from app storage when app page opens for one time only
    useEffect(() => {
        const loadSaved = async () => {
            const photosString = await get('photos');
            const photos = (photosString ? JSON.parse(photosString) : []) as Photo[];
            for (let photo of photos) {
                const file = await readFile({
                    path: photo.filepath,
                    directory: FilesystemDirectory.Data
                });
                photo.base64 = `data:image/jpeg;base64,${file.data}`;
            }
            setPhotos(photos);
        };
        loadSaved();
    }, [get, readFile]);

    //save 2 copies of photo, one on app storage, the other on phone camera folder 
    const takePhoto = async () => {
        const cameraPhoto = await getPhoto({
            resultType: CameraResultType.Uri,
            source: CameraSource.Camera,
            quality: 100
        });

        const fileName = new Date().getTime() + '.jpeg';

        const savedFileImage = await savePicture(cameraPhoto, fileName);

        const newPhotos = [savedFileImage, ...photos];
        setPhotos(newPhotos)

        //log app storage path for photos taken
        set(PHOTO_STORAGE, JSON.stringify(newPhotos.map(p => {
            // Don't save the base64 representation of the photo data,
            // since it's already saved on the Filesystem
            const photoCopy = { ...p };
            delete photoCopy.base64;
            return photoCopy;
        })));
    };

    const savePicture = async (photo: CameraPhoto, fileName: string): Promise<Photo> => {
        const base64Data = await base64FromPath(photo.webPath!);
        const savedFile = await writeFile({
            path: fileName,
            data: base64Data,
            directory: FilesystemDirectory.Data
        });

        // Use webPath to display the new image instead of base64 since it's
        // already loaded into memory
        return {
            filepath: fileName,
            webviewPath: photo.webPath
        };
    };

    return {
        photos,
        takePhoto
    };
}

---------------------------------
//tab2.tsx

import React from 'react';
import {
  IonContent, IonHeader, IonPage, IonTitle, IonToolbar, IonButton,
  IonGrid, IonRow, IonCol, IonImg, IonFab, IonFabButton, IonIcon
} from '@ionic/react';
import ExploreContainer from '../components/ExploreContainer';
import './Tab2.css';
import { usePhotoGallery } from '../hooks/usePhotoGallery';
import { camera } from 'ionicons/icons';

const Tab2: React.FC = () => {
  const { photos, takePhoto } = usePhotoGallery();

  return (
    <IonPage>
      <IonHeader>
        <IonToolbar>
          <IonTitle>Camera Example</IonTitle>
        </IonToolbar>
      </IonHeader>
      <IonContent>
        <IonFab vertical='bottom' horizontal='center' slot='fixed'>
          <IonFabButton onClick={() => takePhoto()}>
            <IonIcon icon={camera} />
          </IonFabButton>
        </IonFab>
        <IonGrid>
          <IonRow>
            {photos.map((photo, index) => (
              <IonCol size="6" key={index}>
                <IonImg src={photo.base64 ?? photo.webviewPath} />
              </IonCol>
            ))}
          </IonRow>
        </IonGrid>
      </IonContent>
    </IonPage>
  );
};

export default Tab2;

reference:
https://ionicframework.com/docs/react/your-first-app/4-loading-photos
http://chuanshuoge2.blogspot.com/2020/05/ionic-react-filesystem-save.html