feat(editor): implement deferred image uploading and pending state management
Refactor the image handling workflow to decouple image selection from the upload process. This improves user experience by allowing users to continue composing posts or trades while images are queued for upload, and increases reliability by using a centralized pending image utility.
- **Deferred Uploads**: Replaced immediate image uploads in `CreatePostScreen`, `PostDetailScreen`, and `CreateTradeScreen` with a "pending" state. Images are now uploaded in bulk during the final submission phase.
- **BlockEditor Enhancements**:
- Updated `useBlockEditor` to support asynchronous batch uploading of all pending images via a new `uploadPendingImages` method.
- Removed inline uploading overlays in favor of a more robust state-driven approach.
- Improved `BlockEditorHandle` to expose block retrieval and batch upload capabilities.
- **API Layer Improvements**:
- Migrated native image uploads from standard `FormData` to `expo-file-system`'s multipart upload to resolve compatibility issues with recent React Native versions.
- Maintained `fetch` + `Blob` logic for web platform compatibility.
- **New Utilities**: Introduced `src/utils/pendingImages.ts` to manage the lifecycle of local (pending) vs. remote (uploaded) images across the application.
This commit is contained in:
89
src/utils/pendingImages.ts
Normal file
89
src/utils/pendingImages.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* 通用「待上传图片」抽象
|
||||
*
|
||||
* 用于发帖、评论、发布商品等场景:
|
||||
* - 用户选择图片时仅保存本地 URI(pending)
|
||||
* - 提交时统一上传,失败可重试已上传的不会丢失
|
||||
*/
|
||||
|
||||
import type * as ImagePicker from 'expo-image-picker';
|
||||
import { uploadService } from '@/services/upload';
|
||||
|
||||
export type PendingImage = {
|
||||
id: string;
|
||||
kind: 'pending';
|
||||
localUri: string;
|
||||
mimeType?: string;
|
||||
};
|
||||
|
||||
export type RemoteImage = {
|
||||
id: string;
|
||||
kind: 'remote';
|
||||
url: string;
|
||||
};
|
||||
|
||||
export type PendingOrRemoteImage = PendingImage | RemoteImage;
|
||||
|
||||
let _imgIdCounter = 0;
|
||||
export const generatePendingImageId = (prefix = 'img'): string =>
|
||||
`${prefix}-${++_imgIdCounter}-${Date.now()}`;
|
||||
|
||||
/** 从 ImagePicker 选择结果构造 pending 图片 */
|
||||
export const makePendingImageFromAsset = (
|
||||
asset: ImagePicker.ImagePickerAsset,
|
||||
idPrefix?: string,
|
||||
): PendingImage => ({
|
||||
id: generatePendingImageId(idPrefix),
|
||||
kind: 'pending',
|
||||
localUri: asset.uri,
|
||||
mimeType: asset.mimeType,
|
||||
});
|
||||
|
||||
/** 获取图片的展示 URI(pending 用本地,remote 用远程) */
|
||||
export const getImageDisplayUri = (img: PendingOrRemoteImage): string =>
|
||||
img.kind === 'pending' ? img.localUri : img.url;
|
||||
|
||||
/** 转换为 remote 图片(保持 id 不变) */
|
||||
export const toRemoteImage = (id: string, url: string): RemoteImage => ({
|
||||
id,
|
||||
kind: 'remote',
|
||||
url,
|
||||
});
|
||||
|
||||
export type UploadAllResult =
|
||||
| { success: true; urls: string[] }
|
||||
| { success: false; urls: string[]; failedId: string };
|
||||
|
||||
/**
|
||||
* 批量上传 pending 图片,已是 remote 的会跳过。
|
||||
* 顺序串行执行,遇到失败立即中断并返回。
|
||||
*
|
||||
* @param images 图片列表
|
||||
* @param onItemUploaded 单张图片上传成功后的回调(用于增量更新 state,
|
||||
* 外部可据此将该 id 的图片标记为 remote 以便重试时跳过)
|
||||
*/
|
||||
export const uploadAllPendingImages = async (
|
||||
images: PendingOrRemoteImage[],
|
||||
onItemUploaded?: (id: string, url: string) => void,
|
||||
): Promise<UploadAllResult> => {
|
||||
const urls: string[] = [];
|
||||
|
||||
for (const img of images) {
|
||||
if (img.kind === 'remote') {
|
||||
urls.push(img.url);
|
||||
continue;
|
||||
}
|
||||
const result = await uploadService.uploadImage({
|
||||
uri: img.localUri,
|
||||
type: img.mimeType || 'image/jpeg',
|
||||
});
|
||||
if (result) {
|
||||
urls.push(result.url);
|
||||
onItemUploaded?.(img.id, result.url);
|
||||
} else {
|
||||
return { success: false, urls, failedId: img.id };
|
||||
}
|
||||
}
|
||||
|
||||
return { success: true, urls };
|
||||
};
|
||||
Reference in New Issue
Block a user