kintex/mobile/lib/imagePick.ts

100 lines
3.3 KiB
TypeScript

/*
* 프로필 사진 선택 — expo-image-picker 래퍼(갤러리/카메라, 1:1 크롭). WISE 모바일 패턴 이식.
* 레퍼런스: guardia-messenger/app/uiws/_lib/constants/imagePick.ts.
* 방어적 동적 require: 미설치 시 graceful(null 반환). 권한 요청·거부 처리 포함.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let ImagePicker: any = null;
try {
// eslint-disable-next-line @typescript-eslint/no-var-requires
ImagePicker = require('expo-image-picker');
} catch {
ImagePicker = null;
}
export interface PickedImage {
uri: string;
fileName: string;
mimeType: string;
/** base64 원본(백엔드 멀티파트 미지원 시 대체 계약용). quality 절충. */
base64?: string | null;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function toPicked(result: any): PickedImage | null {
if (!result || result.canceled) return null;
const asset = result.assets?.[0];
if (!asset?.uri) return null;
const uri: string = asset.uri;
const ext = (uri.split('.').pop() || 'jpg').split('?')[0].toLowerCase();
const mime = asset.mimeType || (ext === 'png' ? 'image/png' : 'image/jpeg');
return {
uri,
fileName: asset.fileName || `profile.${ext}`,
mimeType: mime,
base64: asset.base64 ?? null,
};
}
export function isPickerAvailable(): boolean {
return !!ImagePicker;
}
/** 갤러리에서 1:1 크롭 선택. 권한 거부/취소/미설치 시 null. */
export async function pickFromLibrary(): Promise<PickedImage | null> {
if (!ImagePicker) return null;
try {
const perm = await ImagePicker.requestMediaLibraryPermissionsAsync();
if (!perm?.granted) return null;
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions?.Images,
allowsEditing: true,
aspect: [1, 1],
quality: 0.7,
base64: true,
});
return toPicked(result);
} catch {
return null;
}
}
/** 카메라 촬영(1:1 크롭). 권한 거부/취소/미설치 시 null. */
export async function pickFromCamera(): Promise<PickedImage | null> {
if (!ImagePicker) return null;
try {
const perm = await ImagePicker.requestCameraPermissionsAsync();
if (!perm?.granted) return null;
const result = await ImagePicker.launchCameraAsync({
allowsEditing: true,
aspect: [1, 1],
quality: 0.7,
base64: true,
});
return toPicked(result);
} catch {
return null;
}
}
/** 권한 상태 조회 — 거부 시 안내 문구 분기용(요청 없이 현 상태만). */
export async function permissionState(): Promise<{ media: boolean; camera: boolean }> {
if (!ImagePicker) return { media: false, camera: false };
try {
const m = await ImagePicker.getMediaLibraryPermissionsAsync();
const c = await ImagePicker.getCameraPermissionsAsync();
return { media: !!m?.granted, camera: !!c?.granted };
} catch {
return { media: false, camera: false };
}
}
/** PickedImage → multipart FormData(field "file"). RN 멀티파트: { uri, name, type }. */
export function toPhotoFormData(img: PickedImage): FormData {
const fd = new FormData();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
fd.append('file', { uri: img.uri, name: img.fileName, type: img.mimeType } as any);
return fd;
}