첫 커밋

This commit is contained in:
focp212@naver.com
2025-09-05 15:36:48 +09:00
commit 05238b04c1
825 changed files with 176358 additions and 0 deletions

270
src/hooks/useAppBridge.tsx Normal file
View 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
};
};