Search code examples
reactjstypescriptreact-nativeionic-frameworkionic5

How to add additional info to an image?


I am new to React and Ionic and builded the Ionic React photo gallery app with the help of this ionic tutorial. Now I also want to add additional info like its date, its location, some inputfields,... to the image when it is displayed but I just did not figure out how.

This is the code from the tutorial:

import { useState, useEffect } from "react";
import { isPlatform } from '@ionic/react';

import { Camera, CameraResultType, CameraSource, Photo } from '@capacitor/camera';
import { Filesystem, Directory } from '@capacitor/filesystem'
import { Storage } from '@capacitor/storage'
import { Capacitor } from '@capacitor/core';
import internal from "assert";
import { defaultMaxListeners } from "stream";

const PHOTO_STORAGE = "photos";

export function usePhotoGallery() {

  const [photos, setPhotos] = useState<UserPhoto[]>([]);

  useEffect(() => {
    const loadSaved = async () => {
      const {value} = await Storage.get({key: PHOTO_STORAGE });

      const photosInStorage = (value ? JSON.parse(value) : []) as UserPhoto[];
      // If running on the web...
      if (!isPlatform('hybrid')) {
        for (let photo of photosInStorage) {
          const file = await Filesystem.readFile({
            path: photo.filepath,
            directory: Directory.Data
          });
          // Web platform only: Load the photo as base64 data
          photo.webviewPath = `data:image/jpeg;base64,${file.data}`;
        }
      }
      setPhotos(photosInStorage);
    };
    loadSaved();
  }, []);

  const takePhoto = async () => {
    const cameraPhoto = await Camera.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);
    Storage.set({key: PHOTO_STORAGE,value: JSON.stringify(newPhotos)});

  };

  const savePicture = async (photo: Photo, fileName: string): Promise<UserPhoto> => {
    let base64Data: string;
    // "hybrid" will detect Cordova or Capacitor;
    if (isPlatform('hybrid')) {
      const file = await Filesystem.readFile({
        path: photo.path!
      });
      base64Data = file.data;
    } else {
      base64Data = await base64FromPath(photo.webPath!);
    }
    const savedFile = await Filesystem.writeFile({
      path: fileName,
      data: base64Data,
      directory: Directory.Data
    });

    if (isPlatform('hybrid')) {
      // Display the new image by rewriting the 'file://' path to HTTP
      // Details: https://ionicframework.com/docs/building/webview#file-protocol
      return {
        filepath: savedFile.uri,
        webviewPath: Capacitor.convertFileSrc(savedFile.uri),
      };
    }
    else {
      // Use webPath to display the new image instead of base64 since it's
      // already loaded into memory
      return {
        filepath: fileName,
        webviewPath: photo.webPath
      };
    }
  };

  const deletePhoto = async (photo: UserPhoto) => {
    // 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
    Storage.set({key: PHOTO_STORAGE, value: JSON.stringify(newPhotos) });

    // delete photo file from filesystem
    const filename = photo.filepath.substr(photo.filepath.lastIndexOf('/') + 1);
    await Filesystem.deleteFile({
      path: filename,
      directory: Directory.Data
    });
    setPhotos(newPhotos);
  };
  
  return {
    deletePhoto,
    photos,
    takePhoto
  };
}

export interface UserPhoto {
  filepath: string;
  webviewPath?: string;
}

export async function base64FromPath(path: string): Promise<string> {
  const response = await fetch(path);
  const blob = await response.blob();
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onerror = reject;
    reader.onload = () => {
      if (typeof reader.result === 'string') {
        resolve(reader.result);
      } else {
        reject('method did not return a string')
      }
    };
    reader.readAsDataURL(blob);
  });
}

I managed to get the date and location with the Geolocation Plugin but I have no clue how to bind it to the taken photo...

I assume that I have to add the desired data via function base64FromPath() for web and const file = await Filesystem.readFile() for mobile? And afterwards display with the export interface UserPhoto part but I am completly lost and do not know where to start. Any suggestions welcome!


Solution

  • I found a way might not be the prettiest or directest but it seems to work for me.

    Within the takePhoto function I get the location and date and then pass it on via the savePicture function. Inside the savePicture function I return latitude, longitude and time and therefore can access it through the loadSaved function within the photo object. Inside interface UserPhoto the 3 variables need also to be declared.

    Here is the updated code if somebody would like to make a suggestion:

    import { useState, useEffect } from "react";
    import { isPlatform } from '@ionic/react';
    
    
    import { Geolocation, Geoposition } from '@ionic-native/geolocation';
    import { Camera, CameraResultType, CameraSource, CameraDirection, Photo } from '@capacitor/camera';
    import { Filesystem, Directory } from '@capacitor/filesystem'
    import { Storage } from '@capacitor/storage'
    import { Capacitor } from '@capacitor/core';
    import { stringify } from "querystring";
    
    const PHOTO_STORAGE = "photos";
    export function usePhotoGallery() {
    
      const [photos, setPhotos] = useState<UserPhoto[]>([]);
    
      useEffect(() => {
        const loadSaved = async () => {
          const {value} = await Storage.get({key: PHOTO_STORAGE });
    
          const photosInStorage = (value ? JSON.parse(value) : []) as UserPhoto[];
          // If running on the web...
          if (!isPlatform('hybrid')) {
            for (let photo of photosInStorage) {
              const file = await Filesystem.readFile({
                path: photo.filepath,
                directory: Directory.Data
              });
              // Web platform only: Load the photo as base64 data
              photo.webviewPath = `data:image/jpeg;base64,${file.data}`;
            }
          }
          setPhotos(photosInStorage);
        };
        loadSaved();
      }, []);
    
      const takePhoto = async () => {
        const position = await Geolocation.getCurrentPosition();
        const latitude = position.coords.latitude
        const longitude = position.coords.longitude
        const time = new Date(position.timestamp).toLocaleString()
        
        const cameraPhoto = await Camera.getPhoto({
          resultType: CameraResultType.Uri,
          source: CameraSource.Camera,
          direction: CameraDirection.Rear,
          quality: 100
        });
        const fileName = new Date().getTime() + '.jpeg';
        const savedFileImage = await savePicture(cameraPhoto, fileName, latitude, longitude, time);
        const newPhotos = [savedFileImage, ...photos];
        setPhotos(newPhotos);
        Storage.set({key: PHOTO_STORAGE,value: JSON.stringify(newPhotos)});
      };
    
      const savePicture = async (photo: Photo, fileName: string, latitude: number, longitude: number, time:string): Promise<UserPhoto> => {
        let base64Data: string;
        // "hybrid" will detect Cordova or Capacitor;
        if (isPlatform('hybrid')) {
          const file = await Filesystem.readFile({
            path: photo.path!
          });
          base64Data = file.data;
        } else {
          base64Data = await base64FromPath(photo.webPath!);
        }
        console.log("base64Data")
        console.log(base64Data)
        let savedFile = await Filesystem.writeFile({
          path: fileName,
          data: base64Data,
          directory: Directory.Data
        });
        console.log(savedFile)
    
        if (isPlatform('hybrid')) {
          // Display the new image by rewriting the 'file://' path to HTTP
          // Details: https://ionicframework.com/docs/building/webview#file-protocol
          return {
            filepath: savedFile.uri,
            webviewPath: Capacitor.convertFileSrc(savedFile.uri),
            latitude: latitude,
            longitude: longitude,
            time: time
          };
        }
        else {
          // Use webPath to display the new image instead of base64 since it's
          // already loaded into memory
          return {
            filepath: fileName,
            webviewPath: photo.webPath,
            latitude: latitude,
            longitude: longitude,
            time: time
          };
        }
      };
    
      const deletePhoto = async (photo: UserPhoto) => {
        // 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
        Storage.set({key: PHOTO_STORAGE, value: JSON.stringify(newPhotos) });
    
        // delete photo file from filesystem
        const filename = photo.filepath.substr(photo.filepath.lastIndexOf('/') + 1);
        await Filesystem.deleteFile({
          path: filename,
          directory: Directory.Data
        });
        setPhotos(newPhotos);
      };
    
      return {
        deletePhoto,
        photos,
        takePhoto
      };
    }
    
    export interface UserPhoto {
      filepath: string;
      webviewPath?: string;
      latitude: number;
      longitude: number;
      time: string;
    }
    
    export async function base64FromPath(path: string): Promise<string> {
      const response = await fetch(path);
      const blob = await response.blob();
      //const blob = new Blob(neu)
      return new Promise((resolve, reject) => {
        const reader = new FileReader();
        console.log(reader)
        reader.onerror = reject;
        reader.onload = () => {
          if (typeof reader.result === 'string') {
            resolve(reader.result);
          } else {
            reject('method did not return a string')
          }
        };
        reader.readAsDataURL(blob);
      });
    }
    

    To access is in my UI.tsx I use eg {photo.latitude} to display the taken latitude value