- 앱브리지에 isPushNotificationEnabled, openAppSettings 메서드 추가 - 설정 페이지에서 푸시 알림 권한 상태에 따라 알림 수신 설정 토글 표시 - 알림 수신 설정 토글 클릭 시 앱 설정 화면으로 이동 - 푸시 권한이 꺼져있으면 하위 알림 토글들(11, 21, 31, 41, 61, 62) 비활성화 - 앱이 포어그라운드로 돌아올 때 푸시 권한 상태 재확인하여 UI 업데이트 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
277 lines
8.9 KiB
TypeScript
277 lines
8.9 KiB
TypeScript
import { useState, useEffect, useCallback } from 'react';
|
|
import { appBridge } from '@/utils/appBridge';
|
|
import { DeviceInfo, ShareContent } from '@/types';
|
|
import { LoginResponse } from '@/entities/user/model/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>;
|
|
|
|
// 로그아웃
|
|
logout: () => Promise<void>;
|
|
|
|
// 토큰 요청
|
|
requestToken: () => Promise<LoginResponse>;
|
|
requestRefreshToken: () => Promise<any>;
|
|
|
|
// 간편 인증 등록
|
|
registerBiometric: () => Promise<void>;
|
|
|
|
// 간편 인증 등록 팝업 열기
|
|
openBiometricRegistrationPopup: () => Promise<boolean>;
|
|
// 간편 인증 등록 팝업 닫기
|
|
closeBiometricRegistrationPopup: () => Promise<void>;
|
|
|
|
// 푸시 알림 권한 확인
|
|
isPushNotificationEnabled: () => Promise<boolean>;
|
|
|
|
// 앱 설정 화면 열기
|
|
openAppSettings: () => 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 removeStorage = useCallback(async (key: string): Promise<void> => {
|
|
if (!isNativeEnvironment) {
|
|
localStorage.removeItem(key);
|
|
return;
|
|
}
|
|
return appBridge.safeCall(() => appBridge.removeStorage(key));
|
|
}, [isNativeEnvironment]);
|
|
|
|
const logout = useCallback(async (): Promise<void> => {
|
|
if (!isNativeEnvironment) {
|
|
throw new Error('Logout is only available in native environment');
|
|
}
|
|
return appBridge.safeCall(() => appBridge.logout());
|
|
}, [isNativeEnvironment]);
|
|
|
|
const requestToken = useCallback(async (): Promise<any> => {
|
|
if (!isNativeEnvironment) {
|
|
throw new Error('Token request is only available in native environment');
|
|
}
|
|
return appBridge.safeCall(() => appBridge.requestToken());
|
|
}, [isNativeEnvironment]);
|
|
|
|
const requestRefreshToken = useCallback(async (): Promise<any> => {
|
|
if (!isNativeEnvironment) {
|
|
throw new Error('Token refresh request is only available in native environment');
|
|
}
|
|
return appBridge.safeCall(() => appBridge.requestRefreshToken());
|
|
}, [isNativeEnvironment]);
|
|
|
|
const registerBiometric = useCallback(async (): Promise<void> => {
|
|
if (!isNativeEnvironment) {
|
|
throw new Error('Biometric auth is only available in native environment');
|
|
}
|
|
return appBridge.safeCall(() => appBridge.registerBiometric());
|
|
}, [isNativeEnvironment]);
|
|
|
|
const openBiometricRegistrationPopup = useCallback(async (): Promise<boolean> => {
|
|
if (!isNativeEnvironment) {
|
|
return false;
|
|
}
|
|
const result = await appBridge.safeCall(() => appBridge.openBiometricRegistrationPopup(), false);
|
|
return result || false;
|
|
}, [isNativeEnvironment]);
|
|
|
|
const closeBiometricRegistrationPopup = useCallback(async (): Promise<void> => {
|
|
if (!isNativeEnvironment) {
|
|
return;
|
|
}
|
|
return appBridge.safeCall(() => appBridge.closeBiometricRegistrationPopup());
|
|
}, [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]);
|
|
|
|
const isPushNotificationEnabled = useCallback(async (): Promise<boolean> => {
|
|
if (!isNativeEnvironment) {
|
|
return true;
|
|
}
|
|
const result = await appBridge.safeCall(() => appBridge.isPushNotificationEnabled(), false);
|
|
return result || false;
|
|
}, [isNativeEnvironment]);
|
|
|
|
const openAppSettings = useCallback(async (): Promise<void> => {
|
|
if (!isNativeEnvironment) {
|
|
return;
|
|
}
|
|
return appBridge.safeCall(() => appBridge.openAppSettings());
|
|
}, [isNativeEnvironment]);
|
|
|
|
return {
|
|
isNativeEnvironment,
|
|
isAndroid,
|
|
isIOS,
|
|
deviceInfo,
|
|
navigateBack,
|
|
navigateTo,
|
|
navigateToLogin,
|
|
closeWebView,
|
|
showToast,
|
|
showAlert,
|
|
showConfirm,
|
|
setStorage,
|
|
removeStorage,
|
|
shareContent,
|
|
logout,
|
|
requestToken,
|
|
requestRefreshToken,
|
|
registerBiometric,
|
|
openBiometricRegistrationPopup,
|
|
closeBiometricRegistrationPopup,
|
|
isPushNotificationEnabled,
|
|
openAppSettings
|
|
};
|
|
};
|