첫 커밋
This commit is contained in:
270
src/hooks/useAppBridge.tsx
Normal file
270
src/hooks/useAppBridge.tsx
Normal file
@@ -0,0 +1,270 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { appBridge } from '@/utils/appBridge';
|
||||
import { DeviceInfo, LocationInfo, ContactInfo, ShareContent } from '@/types';
|
||||
|
||||
interface UseAppBridgeReturn {
|
||||
isNativeEnvironment: boolean;
|
||||
isAndroid: boolean;
|
||||
isIOS: boolean;
|
||||
deviceInfo: DeviceInfo | null;
|
||||
|
||||
// 네비게이션
|
||||
navigateBack: () => Promise<void>;
|
||||
navigateTo: (path: string) => Promise<void>;
|
||||
navigateToLogin: () => Promise<void>;
|
||||
closeWebView: () => Promise<void>;
|
||||
|
||||
// 알림
|
||||
showToast: (message: string, duration?: number) => Promise<void>;
|
||||
showAlert: (title: string, message: string) => Promise<void>;
|
||||
showConfirm: (title: string, message: string) => Promise<boolean>;
|
||||
|
||||
// 저장소
|
||||
setStorage: (key: string, value: unknown) => Promise<void>;
|
||||
// getStorage: <T = unknown>(key: string) => Promise<T | null>;
|
||||
removeStorage: (key: string) => Promise<void>;
|
||||
|
||||
/*
|
||||
// 미디어
|
||||
openCamera: (options?: { quality?: number; allowEdit?: boolean }) => Promise<string>;
|
||||
openGallery: (options?: { multiple?: boolean; maxCount?: number }) => Promise<string[]>;
|
||||
|
||||
// 위치
|
||||
getLocation: (options?: { enableHighAccuracy?: boolean; timeout?: number }) => Promise<LocationInfo>;
|
||||
|
||||
// 연락처
|
||||
getContacts: () => Promise<ContactInfo[]>;
|
||||
*/
|
||||
// 공유
|
||||
shareContent: (content: ShareContent) => Promise<void>;
|
||||
}
|
||||
|
||||
export const useAppBridge = (): UseAppBridgeReturn => {
|
||||
const [deviceInfo, setDeviceInfo] = useState<DeviceInfo | null>(null);
|
||||
|
||||
const isNativeEnvironment = appBridge.isNativeEnvironment();
|
||||
const isAndroid = appBridge.isAndroid();
|
||||
const isIOS = appBridge.isIOS();
|
||||
|
||||
useEffect(() => {
|
||||
if (isNativeEnvironment) {
|
||||
appBridge.safeCall(
|
||||
() => appBridge.getDeviceInfo(),
|
||||
undefined,
|
||||
(error) => console.warn('Failed to get device info:', error)
|
||||
).then(info => {
|
||||
if (info) setDeviceInfo(info);
|
||||
});
|
||||
}
|
||||
}, [isNativeEnvironment]);
|
||||
|
||||
const navigateBack = useCallback(async (): Promise<void> => {
|
||||
if (!isNativeEnvironment) {
|
||||
window.history.back();
|
||||
return;
|
||||
}
|
||||
return appBridge.safeCall(() => appBridge.navigateBack());
|
||||
}, [isNativeEnvironment]);
|
||||
|
||||
const navigateTo = useCallback(async (path: string): Promise<void> => {
|
||||
if (!isNativeEnvironment) {
|
||||
window.location.href = path;
|
||||
return;
|
||||
}
|
||||
return appBridge.safeCall(() => appBridge.navigateTo(path));
|
||||
}, [isNativeEnvironment]);
|
||||
|
||||
const closeWebView = useCallback(async (): Promise<void> => {
|
||||
if (!isNativeEnvironment) {
|
||||
window.close();
|
||||
return;
|
||||
}
|
||||
return appBridge.safeCall(() => appBridge.closeWebView());
|
||||
}, [isNativeEnvironment]);
|
||||
|
||||
const navigateToLogin = useCallback(async (): Promise<void> => {
|
||||
if (!isNativeEnvironment) {
|
||||
// 웹 환경에서는 로그인 페이지로 이동
|
||||
window.location.href = '/login';
|
||||
return;
|
||||
}
|
||||
// 네이티브 환경에서는 로그인 화면으로 이동
|
||||
return appBridge.safeCall(() => appBridge.navigateTo('/login'));
|
||||
}, [isNativeEnvironment]);
|
||||
|
||||
const showToast = useCallback(async (message: string, duration = 3000): Promise<void> => {
|
||||
if (!isNativeEnvironment) {
|
||||
// 웹 환경에서는 간단한 토스트 메시지 구현
|
||||
const toast = document.createElement('div');
|
||||
toast.className = 'fixed top-4 right-4 bg-gray-800 text-white px-4 py-2 rounded-md z-50 animate-fade-in';
|
||||
toast.textContent = message;
|
||||
document.body.appendChild(toast);
|
||||
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(toast);
|
||||
}, duration);
|
||||
return;
|
||||
}
|
||||
return appBridge.safeCall(() => appBridge.showToast(message, duration));
|
||||
}, [isNativeEnvironment]);
|
||||
|
||||
const showAlert = useCallback(async (title: string, message: string): Promise<void> => {
|
||||
if (!isNativeEnvironment) {
|
||||
alert(`${title}\n\n${message}`);
|
||||
return;
|
||||
}
|
||||
return appBridge.safeCall(() => appBridge.showAlert(title, message));
|
||||
}, [isNativeEnvironment]);
|
||||
|
||||
const showConfirm = useCallback(async (title: string, message: string): Promise<boolean> => {
|
||||
if (!isNativeEnvironment) {
|
||||
return confirm(`${title}\n\n${message}`);
|
||||
}
|
||||
const result = await appBridge.safeCall(() => appBridge.showConfirm(title, message), false);
|
||||
return result || false;
|
||||
}, [isNativeEnvironment]);
|
||||
|
||||
const setStorage = useCallback(async (key: string, value: unknown): Promise<void> => {
|
||||
if (!isNativeEnvironment) {
|
||||
localStorage.setItem(key, JSON.stringify(value));
|
||||
return;
|
||||
}
|
||||
return appBridge.safeCall(() => appBridge.setStorage(key, value));
|
||||
}, [isNativeEnvironment]);
|
||||
|
||||
/*
|
||||
const getStorage = useCallback(async <T = unknown>(key: string): Promise<T | null> => {
|
||||
if (!isNativeEnvironment) {
|
||||
const item = localStorage.getItem(key);
|
||||
return item ? JSON.parse(item) : null;
|
||||
}
|
||||
return appBridge.safeCall(() => appBridge.getStorage(key), null);
|
||||
}, [isNativeEnvironment]);
|
||||
*/
|
||||
const removeStorage = useCallback(async (key: string): Promise<void> => {
|
||||
if (!isNativeEnvironment) {
|
||||
localStorage.removeItem(key);
|
||||
return;
|
||||
}
|
||||
return appBridge.safeCall(() => appBridge.removeStorage(key));
|
||||
}, [isNativeEnvironment]);
|
||||
|
||||
/*
|
||||
const openCamera = useCallback(async (
|
||||
options?: { quality?: number; allowEdit?: boolean }
|
||||
): Promise<string> => {
|
||||
if (!isNativeEnvironment) {
|
||||
throw new Error('Camera access is only available in native environment');
|
||||
}
|
||||
const result = await appBridge.safeCall(() => appBridge.openCamera(options), '');
|
||||
return result || '';
|
||||
}, [isNativeEnvironment]);
|
||||
|
||||
const openGallery = useCallback(async (
|
||||
options?: { multiple?: boolean; maxCount?: number }
|
||||
): Promise<string[]> => {
|
||||
if (!isNativeEnvironment) {
|
||||
throw new Error('Gallery access is only available in native environment');
|
||||
}
|
||||
const result = await appBridge.safeCall(() => appBridge.openGallery(options), []);
|
||||
return result || [];
|
||||
}, [isNativeEnvironment]);
|
||||
|
||||
const getLocation = useCallback(async (
|
||||
options?: { enableHighAccuracy?: boolean; timeout?: number }
|
||||
): Promise<LocationInfo> => {
|
||||
if (!isNativeEnvironment) {
|
||||
// 웹 환경에서는 HTML5 Geolocation API 사용
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!navigator.geolocation) {
|
||||
reject(new Error('Geolocation is not supported'));
|
||||
return;
|
||||
}
|
||||
|
||||
navigator.geolocation.getCurrentPosition(
|
||||
(position) => {
|
||||
resolve({
|
||||
latitude: position.coords.latitude,
|
||||
longitude: position.coords.longitude,
|
||||
accuracy: position.coords.accuracy,
|
||||
timestamp: position.timestamp
|
||||
});
|
||||
},
|
||||
(error) => reject(error),
|
||||
{
|
||||
enableHighAccuracy: options?.enableHighAccuracy || false,
|
||||
timeout: options?.timeout || 10000
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
const result = await appBridge.safeCall(() => appBridge.getLocation(options));
|
||||
if (!result) {
|
||||
throw new Error('Failed to get location');
|
||||
}
|
||||
return result;
|
||||
}, [isNativeEnvironment]);
|
||||
|
||||
const getContacts = useCallback(async (): Promise<ContactInfo[]> => {
|
||||
if (!isNativeEnvironment) {
|
||||
throw new Error('Contact access is only available in native environment');
|
||||
}
|
||||
const result = await appBridge.safeCall(() => appBridge.getContacts(), []);
|
||||
return result || [];
|
||||
}, [isNativeEnvironment]);
|
||||
*/
|
||||
const shareContent = useCallback(async (content: ShareContent): Promise<void> => {
|
||||
if (!isNativeEnvironment) {
|
||||
// 웹 환경에서는 Web Share API 사용 (지원되는 경우)
|
||||
if (navigator.share) {
|
||||
try {
|
||||
await navigator.share({
|
||||
title: content.title,
|
||||
text: content.text,
|
||||
...(content.url && { url: content.url })
|
||||
});
|
||||
return;
|
||||
} catch (error) {
|
||||
console.warn('Web Share API failed:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 폴백: 클립보드에 복사
|
||||
const shareText = `${content.title}\n${content.text}${content.url ? `\n${content.url}` : ''}`;
|
||||
if (navigator.clipboard) {
|
||||
await navigator.clipboard.writeText(shareText);
|
||||
alert('클립보드에 복사되었습니다.');
|
||||
} else {
|
||||
throw new Error('Sharing is not supported');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
return appBridge.safeCall(() => appBridge.shareContent(content));
|
||||
}, [isNativeEnvironment]);
|
||||
|
||||
return {
|
||||
isNativeEnvironment,
|
||||
isAndroid,
|
||||
isIOS,
|
||||
deviceInfo,
|
||||
navigateBack,
|
||||
navigateTo,
|
||||
navigateToLogin,
|
||||
closeWebView,
|
||||
showToast,
|
||||
showAlert,
|
||||
showConfirm,
|
||||
setStorage,
|
||||
// getStorage,
|
||||
removeStorage,
|
||||
/*
|
||||
openCamera,
|
||||
openGallery,
|
||||
getLocation,
|
||||
getContacts,
|
||||
*/
|
||||
shareContent
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user