Skip to main content

添加手机

我们的照片图库应用要想完整,就必须在 iOS、Android 和网页上运行——而且都使用同一个代码库。只需要做一些小的逻辑更改以支持移动平台,安装一些原生工具,然后在设备上运行应用。走吧!

🌐 Our photo gallery app won’t be complete until it runs on iOS, Android, and the web - all using one codebase. All it takes is some small logic changes to support mobile platforms, installing some native tooling, then running the app on a device. Let’s go!

导入平台 API

🌐 Import Platform API

让我们先进行一些小的代码修改——这样当我们将应用部署到设备上时,它就会“直接运行”。

🌐 Let’s start with making some small code changes - then our app will “just work” when we deploy it to a device.

将 Ionic Platform API 导入 photo.service.ts,用于获取当前设备的信息。在这种情况下,它对于根据应用运行的平台(网页或移动端)选择执行哪段代码非常有用。

🌐 Import the Ionic Platform API into photo.service.ts, which is used to retrieve information about the current device. In this case, it’s useful for selecting which code to execute based on the platform the app is running on (web or mobile).

在文件顶部的导入中添加 Platform,并在 PhotoService 类中添加一个新的属性 platform。我们还需要更新构造函数来设置用户的平台。

🌐 Add Platform to the imports at the top of the file and a new property platform to the PhotoService class. We'll also need to update the constructor to set the user's platform.

import { Injectable } from '@angular/core';
import { Camera, CameraResultType, CameraSource } from '@capacitor/camera';
import type { Photo } from '@capacitor/camera';
import { Filesystem, Directory } from '@capacitor/filesystem';
import { Preferences } from '@capacitor/preferences';
// CHANGE: Add import
import { Platform } from '@ionic/angular';

@Injectable({
providedIn: 'root',
})
export class PhotoService {
public photos: UserPhoto[] = [];

private PHOTO_STORAGE: string = 'photos';

// CHANGE: Add a property to track the app's running platform
private platform: Platform;

// CHANGE: Update constructor to set `platform`
constructor(platform: Platform) {
this.platform = platform;
}

// ...existing code...
}

特定于平台的逻辑

🌐 Platform-specific Logic

首先,我们将更新照片保存功能以支持移动端。在 savePicture() 方法中,检查应用运行的平台。如果是“混合” (Capacitor,本地运行时),则使用 Filesystem.readFile() 方法将照片文件读取为 base64 格式。否则,在网页端运行应用时使用之前相同的逻辑。

🌐 First, we’ll update the photo saving functionality to support mobile. In the savePicture() method, check which platform the app is running on. If it’s “hybrid” (Capacitor, the native runtime), then read the photo file into base64 format using the Filesystem.readFile() method. Otherwise, use the same logic as before when running the app on the web.

savePicture() 更新为如下所示:

🌐 Update savePicture() to look like the following:

// CHANGE: Update the `savePicture()` method
private async savePicture(photo: Photo) {
let base64Data: string | Blob;

// CHANGE: Add platform check
// "hybrid" will detect Cordova or Capacitor
if (this.platform.is('hybrid')) {
// Read the file into base64 format
const file = await Filesystem.readFile({
path: photo.path!
});
base64Data = file.data;
} else {
// Fetch the photo, read as a blob, then convert to base64 format
const response = await fetch(photo.webPath!);
const blob = await response.blob();
base64Data = await this.convertBlobToBase64(blob) as string;
}

// Write the file to the data directory
const fileName = Date.now() + '.jpeg';
const savedFile = await Filesystem.writeFile({
path: fileName,
data: base64Data,
directory: Directory.Data
});

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

在移动设备上运行时,将 filepath 设置为 writeFile() 操作的结果减去 savedFile.uri。设置 webviewPath 时,使用特殊的 Capacitor.convertFileSrc() 方法(文件协议详细信息)。要使用此方法,我们需要在 photo.service.ts 中导入 Capacitor。

🌐 When running on mobile, set filepath to the result of the writeFile() operation - savedFile.uri. When setting the webviewPath, use the special Capacitor.convertFileSrc() method (details on the File Protocol). To use this method, we'll need to import Capacitor into photo.service.ts.

import { Injectable } from '@angular/core';
import { Camera, CameraResultType, CameraSource } from '@capacitor/camera';
import type { Photo } from '@capacitor/camera';
import { Filesystem, Directory } from '@capacitor/filesystem';
import { Preferences } from '@capacitor/preferences';
import { Platform } from '@ionic/angular';
// Change: Add import
import { Capacitor } from '@capacitor/core';

// ...existing code...

然后将 savePicture() 更新为如下所示:

🌐 Then update savePicture() to look like the following:

// CHANGE: Update `savePicture()` method
private async savePicture(photo: Photo) {
let base64Data: string | Blob;
// "hybrid" will detect mobile - iOS or Android
if (this.platform.is('hybrid')) {
const file = await Filesystem.readFile({
path: photo.path!,
});
base64Data = file.data;
} else {
// Fetch the photo, read as a blob, then convert to base64 format
const response = await fetch(photo.webPath!);
const blob = await response.blob();
base64Data = await this.convertBlobToBase64(blob) as string;
}

// Write the file to the data directory
const fileName = Date.now() + '.jpeg';
const savedFile = await Filesystem.writeFile({
path: fileName,
data: base64Data,
directory: Directory.Data,
});

// CHANGE: Add platform check
if (this.platform.is('hybrid')) {
// Display the new image by rewriting the 'file://' path to HTTP
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,
};
}
}

接下来,在 loadSaved() 方法中添加一段新的逻辑。在移动设备上,我们可以直接指向文件系统中的每张照片文件并自动显示它们。然而在网页上,我们必须将每张图片从文件系统中读取为 base64 格式。这是因为文件系统 API 在底层使用了 IndexedDB。更新 loadSaved() 方法:

🌐 Next, add a new bit of logic in the loadSaved() method. On mobile, we can directly point to each photo file on the Filesystem and display them automatically. On the web, however, we must read each image from the Filesystem into base64 format. This is because the Filesystem API uses IndexedDB under the hood. Update the loadSaved() method:

// CHANGE: Update `loadSaved()` method
public async loadSaved() {
const { value: photoList } = await Preferences.get({ key: this.PHOTO_STORAGE });
this.photos = (photoList ? JSON.parse(photoList) : []) as UserPhoto[];

// CHANGE: Add platform check
// If running on the web...
if (!this.platform.is('hybrid')) {
for (let photo of this.photos) {
const readFile = await Filesystem.readFile({
path: photo.filepath,
directory: Directory.Data
});

// Web platform only: Load the photo as base64 data
photo.webviewPath = `data:image/jpeg;base64,${readFile.data}`;
}
}
}

我们的照片库现在由一个可在 Web、Android 和 iOS 上运行的代码库组成。

🌐 Our Photo Gallery now consists of one codebase that runs on the web, Android, and iOS.

photos.service.ts 现在应该看起来像这样:

import { Injectable } from '@angular/core';
import { Camera, CameraResultType, CameraSource } from '@capacitor/camera';
import type { Photo } from '@capacitor/camera';
import { Filesystem, Directory } from '@capacitor/filesystem';
import { Preferences } from '@capacitor/preferences';
import { Platform } from '@ionic/angular';
import { Capacitor } from '@capacitor/core';

@Injectable({
providedIn: 'root',
})
export class PhotoService {
public photos: UserPhoto[] = [];

private PHOTO_STORAGE: string = 'photos';

private platform: Platform;

constructor(platform: Platform) {
this.platform = platform;
}

public async addNewToGallery() {
// Take a photo
const capturedPhoto = await Camera.getPhoto({
resultType: CameraResultType.Uri,
source: CameraSource.Camera,
quality: 100,
});

const savedImageFile = await this.savePicture(capturedPhoto);

this.photos.unshift(savedImageFile);

Preferences.set({
key: this.PHOTO_STORAGE,
value: JSON.stringify(this.photos),
});
}

private async savePicture(photo: Photo) {
let base64Data: string | Blob;

// "hybrid" will detect Cordova or Capacitor
if (this.platform.is('hybrid')) {
// Read the file into base64 format
const file = await Filesystem.readFile({
path: photo.path!,
});

base64Data = file.data;
} else {
// Fetch the photo, read as a blob, then convert to base64 format
const response = await fetch(photo.webPath!);
const blob = await response.blob();

base64Data = (await this.convertBlobToBase64(blob)) as string;
}

// Write the file to the data directory
const fileName = Date.now() + '.jpeg';
const savedFile = await Filesystem.writeFile({
path: fileName,
data: base64Data,
directory: Directory.Data,
});

if (this.platform.is('hybrid')) {
// Display the new image by rewriting the 'file://' path to HTTP
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,
};
}
}

private convertBlobToBase64(blob: Blob) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onerror = reject;
reader.onload = () => {
resolve(reader.result);
};
reader.readAsDataURL(blob);
});
}

public async loadSaved() {
// Retrieve cached photo array data
const { value: photoList } = await Preferences.get({ key: this.PHOTO_STORAGE });
this.photos = (photoList ? JSON.parse(photoList) : []) as UserPhoto[];

// If running on the web...
if (!this.platform.is('hybrid')) {
for (let photo of this.photos) {
const readFile = await Filesystem.readFile({
path: photo.filepath,
directory: Directory.Data,
});
// Web platform only: Load the photo as base64 data
photo.webviewPath = `data:image/jpeg;base64,${readFile.data}`;
}
}
}
}

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

接下来,就是你期待已久的部分——将应用部署到设备上。

🌐 Next up, the part you’ve been waiting for - deploying the app to a device.