100 lines
3.3 KiB
TypeScript
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;
|
|
}
|