Objetivos de esta lección
Ahora que hemos logrado entender como utilizar los componentes de Ionic en React y como controlar los estados de una aplicación en React, requerimos aprender como utilizar los dispositivos nativos de un equipo como pueden ser la cámara o el sistema de archivos.
Para ello Ionic utiliza la librería Capacitor que permite acceso a los elementos nativos de cada dispositivo. En este tutorial vamos a aprender como utilizar alguno de ellos para construir aplicaciones de Ionic.
Configuración inicial de un proyecto en Ionic
Creamos un proyecto en Ionic React y eliminamos los elementos que no se suelen utilizar como lo hemos venido haciendo.
Creación de los componentes básicos
La aplicación consiste en un gestor de memorias, momentos o recuerdos, para ello iniciamos con los siguientes componentes.
Empezamos creando BuenosRecuerdos
.
import {
IonContent,
IonHeader,
IonPage,
IonTitle,
IonToolbar,
} from "@ionic/react";
import React from "react";
export const BuenosRecuerdos: React.FC = () => {
return (
<IonPage>
<IonHeader>
<IonToolbar>
<IonTitle>Buenos Recuerdos</IonTitle>
<IonButtons slot="end">
<IonButton routerLink="/recuerdos/agregar">
<IonIcon slot="icon-only" icon={add} />
</IonButton>
</IonButtons>
</IonToolbar>
</IonHeader>
<IonContent>
<h2>Buenos Recuerdos</h2>
</IonContent>
</IonPage>
);
};
Ahora creamos MalosRecuerdos
.
import {
IonContent,
IonHeader,
IonPage,
IonTitle,
IonToolbar,
} from "@ionic/react";
import React from "react";
export const MalosRecuerdos: React.FC = () => {
return (
<IonPage>
<IonHeader>
<IonToolbar>
<IonTitle>Malos Recuerdos</IonTitle>
<IonButtons slot="end">
<IonButton routerLink="/recuerdos/agregar">
<IonIcon slot="icon-only" icon={add} />
</IonButton>
</IonButtons>
</IonToolbar>
</IonHeader>
<IonContent>
<h2>Malos Recuerdos</h2>
</IonContent>
</IonPage>
);
};
También es necesario un componente para agregar recuerdos AgregarRecuerdo
.
import {
IonContent,
IonHeader,
IonPage,
IonTitle,
IonToolbar,
} from "@ionic/react";
import React from "react";
export const AgregarRecuerdo: React.FC = () => {
return (
<IonPage>
<IonHeader>
<IonToolbar>
<IonButtons slot="start">
<IonBackButton defaultHref="/recuerdos/buenos" />
</IonButtons>
<IonTitle>Agregar Nuevo Recuerdo</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent>
<h2>Nuevo Recuerdo</h2>
</IonContent>
</IonPage>
);
};
Además se necesita un componente para mostrar rutas invalidas NotFound
.
import {
IonContent,
IonHeader,
IonPage,
IonTitle,
IonToolbar,
} from "@ionic/react";
import React from "react";
export const NotFound: React.FC = () => {
return (
<IonPage>
<IonHeader>
<IonToolbar>
<IonTitle>Pagina no encontrada</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent>
<h2>Pagina no encontrada</h2>
</IonContent>
</IonPage>
);
};
Implementamos las rutas dentro de nuestro router que se encuentra dentro de App
.
export const App: React.FC = () => (
<IonApp>
<IonReactRouter>
<IonTabs>
<IonRouterOutlet>
<Route
exact
path="/recuerdos/buenos"
component={BuenosRecuerdos}
/>
<Route
exact
path="/recuerdos/malos"
component={MalosRecuerdos}
/>
<Route
exact
path="/recuerdos/agregar"
component={AgregarRecuerdo}
/>
<Redirect exact path="/" to="/recuerdos/agregar" />
<Route component={NotFound} />
</IonRouterOutlet>
<IonTabBar slot="bottom">
<IonTabButton
href="/recuerdos/buenos"
tab="buenos-recuerdos"
>
<IonIcon icon={happy} />
<IonLabel>Buenos Recuerdos</IonLabel>
</IonTabButton>
<IonTabButton href="/recuerdos/malos" tab="malos-recuerdos">
<IonIcon icon={sad} />
<IonLabel>Malos Recuerdos</IonLabel>
</IonTabButton>
</IonTabBar>
</IonTabs>
</IonReactRouter>
</IonApp>
);
Desplegar botones en base a la plataforma en uso
Vamos a complementar los componentes BuenosRecuerdos
y MalosRecuerdos
de forma que usemos un botón IconFab
cuando la plataforma en uso sea Android. Y simultáneamente el botón +
cuando estemos usando otro sistema.
Para ello usamos la función isPlatform
de @ionic/react
.
export const BuenosRecuerdos: React.FC = () => {
return (
<IonPage>
<IonHeader>
<IonToolbar>
<IonTitle>Buenos Recuerdos</IonTitle>
{!isPlatform("android") && (
<IonButtons slot="end">
<IonButton routerLink="/recuerdos/agregar">
<IonIcon slot="icon-only" icon={add} />
</IonButton>
</IonButtons>
)}
</IonToolbar>
</IonHeader>
<IonContent>
<h2>Buenos Recuerdos</h2>
{!isPlatform("android") && (
<IonFab vertical="bottom" horizontal="end">
<IonFabButton routerLink="/recuerdos/agregar">
<IonIcon icon={add} />
</IonFabButton>
</IonFab>
)}
</IonContent>
</IonPage>
);
};
export const MalosRecuerdos: React.FC = () => {
return (
<IonPage>
<IonHeader>
<IonToolbar>
<IonTitle>Malos Recuerdos</IonTitle>
{!isPlatform("android") && (
<IonButtons slot="end">
<IonButton routerLink="/recuerdos/agregar">
<IonIcon slot="icon-only" icon={add} />
</IonButton>
</IonButtons>
)}
</IonToolbar>
</IonHeader>
<IonContent>
<h2>Malos Recuerdos</h2>
{!isPlatform("android") && (
<IonFab vertical="bottom" horizontal="end">
<IonFabButton routerLink="/recuerdos/agregar">
<IonIcon icon={add} />
</IonFabButton>
</IonFab>
)}
</IonContent>
</IonPage>
);
};
Como utilizar de Capacitor
Para tomar una fotografía podemos echar mano de capacitor. En el Sitio Web Oficial de Capacitor se puede encontrar toda la información acerca de las funcionalidades que capacitor integra, como pueden ser el uso de la cámara mediante capacitor.
Para agregar capacitor a un proyecto existente, utiliza.
ionic integrations enable capacitor
Cuando se complete a instalación se creará un archivo capacitor.config.json
.
{
"appId": "io.apuntes.miapp",
"appName": "miapp",
"bundledWebRuntime": false,
"npmClient": "npm",
"webDir": "build",
"plugins": {
"SplashScreen": {
"launchShowDuration": 0
}
},
"cordova": {}
}
Ahora compilamos nuestra aplicación.
yarn build
Al terminar de compilar el proyecto tendremos un directorio build listo para producción, este directorio con los compilados es necesario para ejecutar nuestra aplicación.
Para agregar soporte de Android en capacitor utilizamos.
ionic capacitor add android
¿Cómo compilar una aplicación Android en Ionic?
Después de ejecutar el build con Ionic procedemos a realizar la compilación en Android.
ionic capacitor build android
¿Cómo abrir una aplicación Android con Ionic?
Ahora para abrir la aplicación en Android utilizando Ionic ejecutamos el siguiente comando.
ionic capacitor open android
Después de ejecutar el comando anterior Android Studio abrirá y podremos ejecutar la aplicación que acabamos de compilar, siempre que tengamos las librerías de Android necesarias así como la versión de Android requerida.
¿Cómo asignar permisos de uso de la cámara en Ionic?
Para utilizar ciertos dispositivos las aplicaciones solicitan dichos permisos al momento de intentar acceder a ellos.
Vamos al archivo app/src/main/AndroidManifest.xml
y eliminamos todos los permisos que no requerimos.
<!-- Permissions -->
<!-- Camera, Photos, input file -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- Network API -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- Navigator.getUserMedia -->
<!-- Video -->
<uses-permission android:name="android.permission.CAMERA" />
En el caso anterior hemos dejado solo los permisos de lectura y escritura de archivos, así como de acceso a la red y la cámara.
Ahora podemos complementar la aplicación para capturar la imagen utilizando la librería capacitor.
import { CameraResultType, CameraSource, Plugins } from "@capacitor/core";
// ...
const { Camera } = Plugins;
export const AgregarRecuerdo: React.FC = () => {
const [foto, setFoto] = useState<{
path: string;
preview: string;
}>();
const tomarFotoHandler = async () => {
const foto = await Camera.getPhoto({
resultType: CameraResultType.Uri,
source: CameraSource.Camera,
quality: 90,
width: 500,
});
if (!foto || !foto.path || !foto.webPath) {
return;
}
setFoto({
path: foto.path,
preview: foto.webPath,
});
};
return (
<IonPage>
<IonHeader>
<IonToolbar>
<IonButtons slot="start">
<IonBackButton defaultHref="/recuerdos/buenos" />
</IonButtons>
<IonTitle>Agregar Nuevo Recuerdo</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent className="ion-padding">
<IonGrid>
<IonRow>
<IonCol>
<IonItem>
<IonLabel position="floating">Titulo</IonLabel>
<IonInput type="text" />
</IonItem>
</IonCol>
</IonRow>
{!foto ? (
<IonRow>
<IonCol>
<h2>No ha seleccionado una imagen aun.</h2>
</IonCol>
</IonRow>
) : (
<IonRow>
<IonCol>
<img
src={foto?.preview}
alt="Vista previa de la foto"
/>
</IonCol>
</IonRow>
)}
<IonRow>
<IonCol>
<IonButton
fill="outline"
onClick={tomarFotoHandler}
>
<IonIcon icon={camera} slot="start" />
<IonLabel slot="end">Tomar foto</IonLabel>
</IonButton>
</IonCol>
</IonRow>
// ...
</IonGrid>
</IonContent>
</IonPage>
);
};
Agregamos el paquete @ionic/react-hooks
.
yarn add @ionic/react-hooks
¿Cómo almacenar los datos en un Context en React?
Para almacenar la información en el nivel mas superior creamos un context dentro de RecuerdosContext
.
import React from "react";
export interface Recuerdo {
id: string;
path: string;
titulo: string;
tipo: "bueno" | "malo";
}
export const RecuerdosContext = React.createContext<{
recuerdos: Recuerdo[];
agregarRecuerdo: (
path: string,
titulo: string,
tipo: "bueno" | "malo"
) => void;
}>({
recuerdos: [],
agregarRecuerdo: () => {},
});
Nuestro
Context
contiene un arreglorecuerdos: Recuerdo[]
yagregarRecuerdo
que permitirá agregar valores a este arreglo.
Creamos un ContextContainer
implementando RecuerdosContext
para tal caso.
import React, { useState } from "react";
import { RecuerdosContext, Recuerdo } from "./recuerdos.context";
export const RecuerdosContextProvider: React.FC = (props) => {
const [recuerdos, setRecuerdos] = useState<Recuerdo[]>([]);
const agregarRecuerdo = (
path: string,
titulo: string,
tipo: "bueno" | "malo"
) => {
const nuevoRecuerdo: Recuerdo = {
id: Math.random().toString(),
titulo,
tipo,
path,
};
setRecuerdos([...recuerdos, nuevoRecuerdo]);
};
return (
<RecuerdosContext.Provider
value={{
agregarRecuerdo,
recuerdos,
}}
>
{props.children}
</RecuerdosContext.Provider>
);
};
Un
ContextContainer
permite englobar los valores derecuerdos: Recuerdo[]
y manipularlos a través deaddRecuerdo
. En este ejemplo se controlan los estados utilizando el estado (state)useState<Recuerdo[]>
.
Vamos a implementar nuestro ContextContainer
dentro de nuestro router en App
.
export const App: React.FC = () => (
<IonApp>
<IonReactRouter>
<RecuerdosContextProvider>// ...</RecuerdosContextProvider>
</IonReactRouter>
</IonApp>
);
Realizamos la implementación utilizando los elementos de @capacitor/core
.
import {
CameraResultType,
CameraSource,
Filesystem,
FilesystemDirectory,
Plugins,
} from "@capacitor/core";
// ...
import { base64FromPath } from "@ionic/react-hooks/filesystem";
import { RecuerdosContext } from "../data/recuerdos.context";
import { useHistory } from "react-router";
const { Camera } = Plugins;
export const AgregarRecuerdo: React.FC = () => {
// estado para controlar la foto elegida
const [foto, setFoto] = useState<{
path: string;
preview: string;
}>();
// estado para controlar el recuerdo elegido
const [tipoDeRecuerdo, setTipoDeRecuerdo] = useState<"bueno" | "malo">();
// controlador del context (información global)
const recuerdosCtx = useContext(RecuerdosContext);
// control del manejo del history para mover la pagina
const history = useHistory();
// referencia al campo titulo para extraer su valor
const tituloRef = useRef<HTMLIonInputElement>(null);
// handler para controlar el cambio de tipo de recuerdo
const seleccionarTipoDeRecuerdoHandler = (event: CustomEvent) => {
const tipoDeRecuerdoSeleccionado = event.detail.value;
setTipoDeRecuerdo(tipoDeRecuerdoSeleccionado);
};
// implementación del plugin Camera de capacitor para capturar fotos
const tomarFotoHandler = async () => {
const nuevaFoto = await Camera.getPhoto({
resultType: CameraResultType.Uri,
source: CameraSource.Camera,
quality: 80,
width: 500,
});
if (!nuevaFoto || !nuevaFoto.path || !nuevaFoto.webPath) {
return;
}
setFoto({
path: nuevaFoto.path,
preview: nuevaFoto.webPath,
});
};
// handler para agregar al recuerdo utilizando el context
const agregarRecuerdoHandler = async () => {
const titulo = tituloRef.current?.value;
if (
!titulo ||
titulo.toString().trim().length === 0 ||
!tipoDeRecuerdo ||
!foto
) {
return;
}
const nombreDeArchivo = new Date().getTime() + ".jpg";
const base64 = await base64FromPath(foto!.preview);
Filesystem.writeFile({
path: nombreDeArchivo,
data: base64,
directory: FilesystemDirectory.Data,
});
recuerdosCtx.agregarRecuerdo(
nombreDeArchivo,
titulo.toString().trim(),
tipoDeRecuerdo
);
history.replace("/recuerdos/buenos");
};
return (
<IonPage>
// ...
<IonContent className="ion-padding">
<IonGrid>
<IonRow>
<IonCol>
<IonItem>
<IonInput
type="text"
ref={tituloRef}
placeholder="Titulo"
/>
</IonItem>
</IonCol>
</IonRow>
<IonRow>
<IonCol>
<IonSelect
onIonChange={seleccionarTipoDeRecuerdoHandler}
placeholder="Tipo de recuerdo"
>
<IonSelectOption value="bueno">
Buen recuerdo
</IonSelectOption>
<IonSelectOption value="malo">
Mal recuerdo
</IonSelectOption>
</IonSelect>
</IonCol>
</IonRow>
{!foto ? (
<IonRow>
<IonCol>
<h2>No ha seleccionado una imagen aun.</h2>
</IonCol>
</IonRow>
) : (
<IonRow>
<IonCol>
<img
src={foto?.preview}
alt="Vista previa de la foto"
/>
</IonCol>
</IonRow>
)}
<IonRow>
<IonCol>
<IonButton
fill="outline"
onClick={tomarFotoHandler}
>
<IonIcon icon={camera} slot="start" />
<IonLabel slot="end">Tomar foto</IonLabel>
</IonButton>
</IonCol>
</IonRow>
<IonRow>
<IonCol>
<IonButton
fill="outline"
onClick={agregarRecuerdoHandler}
>
<IonIcon icon={add} />
<IonLabel>Agregar</IonLabel>
</IonButton>
</IonCol>
</IonRow>
</IonGrid>
</IonContent>
</IonPage>
);
};
Actualizar el componente BuenosRecuerdos
para mostrar los recuerdos almacenados mediante el ContextContainer
.
// ...
import React, { useContext } from "react";
import { RecuerdosContext } from "../data/recuerdos.context";
export const BuenosRecuerdos: React.FC = () => {
const recuerdosCtx = useContext(RecuerdosContext);
const buenosRecuerdos = recuerdosCtx.recuerdos.filter(
(recuerdo) => recuerdo.tipo === "bueno"
);
return (
<IonPage>
// ...
<IonContent>
<h2>Buenos Recuerdos</h2>
<IonGrid>
{buenosRecuerdos.map((recuerdo) => {
return (
<IonRow key={recuerdo.id}>
<IonCol>
<IonCard>
<img
src={recuerdo.path}
alt={recuerdo.titulo}
/>
<IonCardHeader>
<IonCardTitle>
{recuerdo.titulo}
</IonCardTitle>
</IonCardHeader>
</IonCard>
</IonCol>
</IonRow>
);
})}
</IonGrid>
// ...
</IonContent>
</IonPage>
);
};
¿Cómo almacenar los datos en el System Storage (Sitema de Archivos) con Ionic?
Primero dentro de RecuerdosContext
vamos a expandir la interface Recuerdo
para que podamos guardar un string como base64.
export interface Recuerdo {
id: string;
path: string;
titulo: string;
tipo: "bueno" | "malo";
base64url: string;
}
Hay que complementar también RecuerdosContext
para que la función agregarRecuerdo
reciba el base64 además de initContext
que nos permitirá tomar los valores iniciales del sistema de archivos e inyectarlos en el context.
export const RecuerdosContext = React.createContext<{
recuerdos: Recuerdo[];
agregarRecuerdo: (
path: string,
base64url: string,
titulo: string,
tipo: "bueno" | "malo"
) => void;
initContext: () => void;
}>({
recuerdos: [],
agregarRecuerdo: () => {},
initContext: () => {},
});
Para la implementación del ContextProvider
agregamos una interface que excluya el base64 y solo contenga id, titulo, tipo y path
como parte de la información de cada Recuerdo
.
interface RecuerdoAlmacenable {
id: string;
titulo: string;
tipo: "bueno" | "malo";
path: string;
}
Además requerimos actualizar el ContextProvider
de forma que:
- Tenga un observer que actualice los valores en el filesystem cada que cambie el arreglo
Recuerdos
. - Invoque la carga de datos al cargar el
ContextProvider
y solo durante la primera llamada a este.
export const RecuerdosContextProvider: React.FC = (props) => {
const [recuerdos, setRecuerdos] = useState<Recuerdo[]>([]);
useEffect(() => {
const recuerdosAlmacenables: RecuerdoAlmacenable[] = recuerdos.map(
(recuerdo) => {
return {
id: recuerdo.id,
titulo: recuerdo.titulo,
tipo: recuerdo.tipo,
path: recuerdo.path,
};
}
);
Storage.set({
key: "recuerdos",
value: JSON.stringify(recuerdosAlmacenables),
});
}, [recuerdos]);
const agregarRecuerdo = (
path: string,
base64url: string,
titulo: string,
tipo: "bueno" | "malo"
) => {
const nuevoRecuerdo: Recuerdo = {
id: Math.random().toString(),
titulo,
tipo,
path,
base64url,
};
setRecuerdos([...recuerdos, nuevoRecuerdo]);
};
const initContext = useCallback(async () => {
const datosEnElStorage = await Storage.get({ key: "recuerdos" });
const recuerdosAlmacenados: RecuerdoAlmacenable[] =
datosEnElStorage.value ? JSON.parse(datosEnElStorage.value) : [];
const recuerdos: Recuerdo[] = [];
for (const recuerdoAlmacenado of recuerdosAlmacenados) {
const archivo = await Filesystem.readFile({
path: recuerdoAlmacenado.path,
directory: FilesystemDirectory.Data,
});
recuerdos.push({
...recuerdoAlmacenado,
base64url: `data:image/jpeg;base64, ${archivo.data}`,
});
}
setRecuerdos(recuerdos);
}, []);
return (
<RecuerdosContext.Provider
value={{
agregarRecuerdo,
recuerdos,
initContext,
}}
>
{props.children}
</RecuerdosContext.Provider>
);
};
Movemos la implementación del ContextContainer
del router al index en donde englobaremos el componente App
.
import React from "react";
import ReactDOM from "react-dom";
import { App } from "./app";
import { RecuerdosContextProvider } from "./data/recuerdos.context-provider";
import * as serviceWorker from "./serviceWorker";
ReactDOM.render(
<RecuerdosContextProvider>
<App />
</RecuerdosContextProvider>,
document.getElementById("root")
);
serviceWorker.unregister();
Removemos la implementación del ContextContainer
del router e implementamos el observer useEffect
para que se invoque solo al arrancar el el initContext()
.
export const App: React.FC = () => {
const recuerdosCtx = useContext(RecuerdosContext);
const { initContext } = recuerdosCtx;
useEffect(() => {
initContext();
}, [initContext]);
return (
<IonApp>
<IonReactRouter>
<IonTabs>// ...</IonTabs>
</IonReactRouter>
</IonApp>
);
};
Actualizamos la llamada a agregarRecuerdo
dentro de AgregarRecuerdo
.
const agregarRecuerdoHandler = async () => {
// ...
recuerdosCtx.agregarRecuerdo(
nombreDeArchivo,
base64,
titulo.toString().trim(),
tipoDeRecuerdo
);
history.replace("/recuerdos/buenos");
};
Actualizar las referencias de path
a base64url
.
<IonGrid>
{buenosRecuerdos.map((recuerdo) => {
return (
<IonRow key={recuerdo.id}>
<IonCol>
<IonCard>
<img src={recuerdo.base64url} alt={recuerdo.titulo} />
<IonCardHeader>
<IonCardTitle>{recuerdo.titulo}</IonCardTitle>
</IonCardHeader>
</IonCard>
</IonCol>
</IonRow>
);
})}
</IonGrid>
Actualizamos también la referencia del botón IonFab
.
{
isPlatform("android") && (
<IonFab vertical="bottom" horizontal="end" slot="fixed">
<IonFabButton routerLink="/recuerdos/agregar">
<IonIcon icon={add} />
</IonFabButton>
</IonFab>
);
}
Refactorización del código consolidando la lista y los items
Vamos a crear dos nuevos componentes, uno RecuerdoLista
y otro RecuerdoItem
.
En RecuerdoItem
vamos a extraer el código relativo a cada una de las filas de los recuerdos.
import { IonCard, IonCardHeader, IonCardTitle } from "@ionic/react";
import React from "react";
import { Recuerdo } from "../data/recuerdos.context";
export const RecuerdoItem: React.FC<{ item: Recuerdo }> = (props) => {
return (
<IonCard>
<img src={props.item.base64url} alt={props.item.titulo} />
<IonCardHeader>
<IonCardTitle>{props.item.titulo}</IonCardTitle>
</IonCardHeader>
</IonCard>
);
};
Para enviar los parámetros utilizados por este componente, utilizamos la propiedad
item
del tipoRecuerdo
.
En RecuerdoLista
realizamos la implementación del recorrido del arreglo de recuerdos
.
import { IonCol, IonGrid, IonRow } from "@ionic/react";
import React from "react";
import { Recuerdo } from "../data/recuerdos.context";
import { RecuerdoItem } from "./recuerdo-item.component";
export const RecuerdosLista: React.FC<{ items: Recuerdo[] }> = (props) => {
return (
<IonGrid>
{props.items.map((recuerdo) => {
return (
<IonRow key={recuerdo.id}>
<IonCol>
<RecuerdoItem item={recuerdo} />
</IonCol>
</IonRow>
);
})}
</IonGrid>
);
};
En este caso dentro de
items
tenemos un arreglo deRecuerdo[]
.
Ahora dentro de los componentes BuenosRecuerdos
y MalosRecuerdos realizamos la implementación
.
// ...
export const BuenosRecuerdos: React.FC = () => {
const recuerdosCtx = useContext(RecuerdosContext);
const recuerdos = recuerdosCtx.recuerdos.filter(
(recuerdo) => recuerdo.tipo === "bueno"
);
return (
<IonPage>
// ...
<IonContent>
<h2>Buenos Recuerdos</h2>
<RecuerdosLista items={recuerdos} />
{isPlatform("android") && (
<IonFab vertical="bottom" horizontal="end" slot="fixed">
<IonFabButton routerLink="/recuerdos/agregar">
<IonIcon icon={add} />
</IonFabButton>
</IonFab>
)}
</IonContent>
</IonPage>
);
};
// ...
export const MalosRecuerdos: React.FC = () => {
const recuerdosCtx = useContext(RecuerdosContext);
const recuerdos = recuerdosCtx.recuerdos.filter(
(recuerdo) => recuerdo.tipo === "malo"
);
return (
<IonPage>
// ...
<IonContent>
<h2>Malos Recuerdos</h2>
<RecuerdosLista items={recuerdos} />
{isPlatform("android") && (
<IonFab vertical="bottom" horizontal="end" slot="fixed">
<IonFabButton routerLink="/recuerdos/agregar">
<IonIcon icon={add} />
</IonFabButton>
</IonFab>
)}
</IonContent>
</IonPage>
);
};
Uso de la cámara dentro del navegador
Para poder utilizar la cámara a nivel del navegador requerimos instalar el siguiente componente.
yarn add @ionic/pwa-elements
Una ves instalado el paquete @ionic/pwa-elements
realizamos la implementación sobre el objeto window
.
import { defineCustomElements } from "@ionic/pwa-elements/loader";
// ...
ReactDOM.render(
<RecuerdosContextProvider>
<App />
</RecuerdosContextProvider>,
document.getElementById("root")
);
serviceWorker.unregister();
defineCustomElements(window);
Como capacitor
no regresa una propiedad path
al subir una imagen pero como hemos creado un nombre personalizado podemos solo basarnos en dicho nombre para retraer las imágenes.
// ...
export const AgregarRecuerdo: React.FC = () => {
const [foto, setFoto] = useState<{
path: string | undefined;
preview: string;
}>();
// ...
const seleccionarTipoDeRecuerdoHandler = (event: CustomEvent) => {
const tipoDeRecuerdoSeleccionado = event.detail.value;
setTipoDeRecuerdo(tipoDeRecuerdoSeleccionado);
};
const tomarFotoHandler = async () => {
const nuevaFoto = await Camera.getPhoto({
resultType: CameraResultType.Uri,
source: CameraSource.Camera,
quality: 80,
width: 500,
});
if (!nuevaFoto || !nuevaFoto.webPath) {
return;
}
setFoto({
path: nuevaFoto.path,
preview: nuevaFoto.webPath,
});
};
const agregarRecuerdoHandler = async () => {
const titulo = tituloRef.current?.value;
if (
!titulo ||
titulo.toString().trim().length === 0 ||
!tipoDeRecuerdo ||
!foto
) {
return;
}
const nombreDeArchivo = new Date().getTime() + ".jpg";
const base64 = await base64FromPath(foto!.preview);
await Filesystem.writeFile({
path: nombreDeArchivo,
data: base64,
directory: FilesystemDirectory.Data,
});
recuerdosCtx.agregarRecuerdo(
nombreDeArchivo,
base64,
titulo.toString().trim(),
tipoDeRecuerdo
);
history.replace("/recuerdos/buenos");
};
return <IonPage>// ...</IonPage>;
};
Si probamos nuevamente la aplicación nos daremos cuenta que funciona correctamente. En el caso del navegador Chrome los datos se guardan dentro del IndexedDB, para acceder a este hay que abrir Application > Storage > IndexedDB.