diff --git a/android/app/build.gradle b/android/app/build.gradle index bceefa3..dac4953 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -1,20 +1,6 @@ apply plugin: 'com.android.application' // 버전 정보를 properties 파일에서 동적으로 로드 -def versionPropsFile = rootProject.file('version.properties') -def versionProps = new Properties() -if (versionPropsFile.exists()) { - versionPropsFile.withInputStream { stream -> versionProps.load(stream) } -} - -def versionName = versionProps['versionName'] ?: "1.1.1.2" -def versionCode = (versionProps['versionCode'] ?: "6").toInteger() -def buildNumber = (versionProps['buildNumber'] ?: "6").toInteger() - -// 버전 정보 로깅 -println "버전 정보 로드: versionName=${versionName}, versionCode=${versionCode}, buildNumber=${buildNumber}" - -// 버전 정보를 직접 설정 android { namespace "com.lovable.zellyfinance" compileSdk rootProject.ext.compileSdkVersion @@ -22,10 +8,48 @@ android { applicationId "com.lovable.zellyfinance" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode = versionCode - versionName = versionName - // 빌드 번호 추가 - BuildConfig 필드로 정의 - buildConfigField("int", "BUILD_NUMBER", String.valueOf(buildNumber)) + + // version.properties 파일 로드 + def versionPropsFile = file("${rootDir}/version.properties") + def versionProps = new Properties() + + try { + if (versionPropsFile.canRead()) { + versionProps.load(new FileInputStream(versionPropsFile)) + println "버전 정보 로드: versionName=${versionProps['versionName']}, versionCode=${versionProps['versionCode']}, buildNumber=${versionProps['buildNumber']}" + } else { + println "version.properties 파일을 읽을 수 없음, 기본값 사용" + versionProps['versionName'] = '1.1.1.2' + versionProps['versionCode'] = '6' + versionProps['buildNumber'] = '6' + } + } catch (Exception e) { + println "버전 정보 로드 오류, 기본값 사용: ${e.message}" + versionProps['versionName'] = '1.1.1.2' + versionProps['versionCode'] = '6' + versionProps['buildNumber'] = '6' + } + + // 빈 문자열이나 null 값 검사 + if (!versionProps['versionName'] || versionProps['versionName'].trim().isEmpty()) { + versionProps['versionName'] = '1.1.1.2' + } + if (!versionProps['versionCode'] || versionProps['versionCode'].trim().isEmpty()) { + versionProps['versionCode'] = '6' + } + if (!versionProps['buildNumber'] || versionProps['buildNumber'].trim().isEmpty()) { + versionProps['buildNumber'] = '6' + } + + // 빌드 정보 설정 + versionName versionProps['versionName'] + versionCode versionProps['versionCode'] ? versionProps['versionCode'].toInteger() : 6 + + // 빌드 설정에 BuildConfig 필드 추가 + buildConfigField "String", "VERSION_NAME", "\"${versionProps['versionName']}\"" + buildConfigField "int", "VERSION_CODE", "${versionProps['versionCode'] ? versionProps['versionCode'].toInteger() : 6}" + buildConfigField "int", "BUILD_NUMBER", "${versionProps['buildNumber'] ? versionProps['buildNumber'].toInteger() : 6}" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" aaptOptions { // Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps. diff --git a/android/app/src/main/java/com/lovable/zellyfinance/BuildInfoPlugin.java b/android/app/src/main/java/com/lovable/zellyfinance/BuildInfoPlugin.java index cae8a4c..d0615f9 100644 --- a/android/app/src/main/java/com/lovable/zellyfinance/BuildInfoPlugin.java +++ b/android/app/src/main/java/com/lovable/zellyfinance/BuildInfoPlugin.java @@ -21,33 +21,91 @@ public class BuildInfoPlugin extends Plugin { */ @PluginMethod public void getBuildInfo(PluginCall call) { + // 오류가 발생하더라도 앱이 강제종료되지 않도록 전체 try-catch 블록 사용 try { Log.d(TAG, "빌드 정보 요청 수신됨"); JSObject ret = new JSObject(); + String versionName = ""; + int versionCode = 0; + int buildNumber = 0; - // BuildConfig에서 동적으로 버전 정보 가져오기 - String versionName = BuildConfig.VERSION_NAME; - int versionCode = BuildConfig.VERSION_CODE; - int buildNumber; - - // 빌드 넘버는 커스텀 필드이므로 try-catch로 확인 + // 모든 필드 접근에 각각 try-catch 적용하여 실패하더라도 기본값 사용 try { - buildNumber = BuildConfig.BUILD_NUMBER; - Log.d(TAG, "BuildConfig.BUILD_NUMBER: " + buildNumber); + // Class.forName으로 BuildConfig 클래스 안전하게 접근 + Class buildConfigClass = null; + try { + buildConfigClass = Class.forName("com.lovable.zellyfinance.BuildConfig"); + Log.d(TAG, "BuildConfig 클래스 로드 성공"); + } catch (ClassNotFoundException e) { + Log.e(TAG, "BuildConfig 클래스를 찾을 수 없음", e); + } + + if (buildConfigClass != null) { + // VERSION_NAME 필드 접근 + try { + Object versionNameObj = buildConfigClass.getField("VERSION_NAME").get(null); + if (versionNameObj != null) { + versionName = versionNameObj.toString(); + Log.d(TAG, "VERSION_NAME 로드 성공: " + versionName); + } + } catch (Exception e) { + Log.e(TAG, "VERSION_NAME 필드 접근 오류", e); + } + + // VERSION_CODE 필드 접근 + try { + Object versionCodeObj = buildConfigClass.getField("VERSION_CODE").get(null); + if (versionCodeObj != null) { + versionCode = Integer.parseInt(versionCodeObj.toString()); + Log.d(TAG, "VERSION_CODE 로드 성공: " + versionCode); + } + } catch (Exception e) { + Log.e(TAG, "VERSION_CODE 필드 접근 오류", e); + } + + // BUILD_NUMBER 필드 접근 + try { + Object buildNumberObj = buildConfigClass.getField("BUILD_NUMBER").get(null); + if (buildNumberObj != null) { + buildNumber = Integer.parseInt(buildNumberObj.toString()); + Log.d(TAG, "BUILD_NUMBER 로드 성공: " + buildNumber); + } + } catch (Exception e) { + Log.e(TAG, "BUILD_NUMBER 필드 접근 오류, 버전 코드로 대체", e); + // 빌드 넘버 필드가 없으면 버전 코드와 동일하게 설정 + buildNumber = versionCode; + } + } } catch (Exception e) { - Log.e(TAG, "BUILD_NUMBER 필드 접근 오류, 기본값 사용", e); - buildNumber = versionCode; // 빌드 넘버가 없으면 버전 코드와 동일하게 설정 + Log.e(TAG, "BuildConfig 접근 중 예기치 않은 오류", e); + } + + // 버전 정보가 유효하지 않을 경우 version.properties 파일의 값이 + // 빌드 과정에서 이미 BuildConfig에 적용되었을 것이므로 별도의 하드코딩 없음 + if (versionName == null || versionName.isEmpty()) { + Log.w(TAG, "버전명이 비어있음, BuildConfig에서 값을 가져오지 못함"); + } + if (versionCode <= 0) { + Log.w(TAG, "버전 코드가 유효하지 않음, BuildConfig에서 값을 가져오지 못함"); + } + if (buildNumber <= 0) { + Log.w(TAG, "빌드 번호가 유효하지 않음, BuildConfig에서 값을 가져오지 못함"); } // 디버깅을 위한 로그 출력 - Log.d(TAG, "BuildConfig 클래스: " + BuildConfig.class.getName()); - Log.d(TAG, "버전명: " + versionName); - Log.d(TAG, "버전 코드: " + versionCode); - Log.d(TAG, "빌드 번호: " + buildNumber); + Log.d(TAG, "최종 버전명: " + versionName); + Log.d(TAG, "최종 버전 코드: " + versionCode); + Log.d(TAG, "최종 빌드 번호: " + buildNumber); - String packageName = getContext().getPackageName(); - Log.d(TAG, "패키지명: " + packageName); + String packageName = ""; + try { + packageName = getContext().getPackageName(); + Log.d(TAG, "패키지명: " + packageName); + } catch (Exception e) { + Log.e(TAG, "패키지명 가져오기 오류", e); + packageName = "com.lovable.zellyfinance"; + } // 결과 객체에 값 설정 ret.put("versionName", versionName); @@ -67,20 +125,23 @@ public class BuildInfoPlugin extends Plugin { // 오류 로깅 강화 Log.e(TAG, "빌드 정보 가져오기 오류", e); - // 오류 발생 시에도 기본 정보 반환하여 앱 중단 방지 + // 오류 발생 시에도 정보 반환하여 앱 중단 방지 + // build.gradle에서 설정한 기본값이 BuildConfig에 이미 적용되었을 것이므로 + // 별도의 하드코딩 없이 기본적인 정보만 포함 JSObject fallbackResult = new JSObject(); - fallbackResult.put("versionName", "1.1.1.2"); // 버전명 기본값 - fallbackResult.put("versionCode", 6); // 버전 코드 기본값 - fallbackResult.put("buildNumber", 6); // 빌드 번호 기본값 - fallbackResult.put("packageName", getContext().getPackageName()); - fallbackResult.put("androidVersion", Build.VERSION.RELEASE); - fallbackResult.put("androidSDK", Build.VERSION.SDK_INT); fallbackResult.put("platform", "android-fallback"); fallbackResult.put("error", e.getMessage()); - fallbackResult.put("timestamp", System.currentTimeMillis()); + fallbackResult.put("androidVersion", Build.VERSION.RELEASE); + fallbackResult.put("androidSDK", Build.VERSION.SDK_INT); - Log.d(TAG, "오류 발생으로 기본값 반환: " + fallbackResult.toString()); - call.resolve(fallbackResult); // reject 대신 기본값으로 resolve + try { + fallbackResult.put("packageName", getContext().getPackageName()); + } catch (Exception ex) { + fallbackResult.put("packageName", "com.lovable.zellyfinance"); + } + + // 응답 해결 + call.resolve(fallbackResult); } } } diff --git a/android/app/version.properties b/android/app/version.properties index 1072dca..875f85b 100644 --- a/android/app/version.properties +++ b/android/app/version.properties @@ -1,3 +1,3 @@ -buildNumber=6 -versionCode=6 -versionName=1.1.1.2 +buildNumber=7 +versionCode=7 +versionName=1.1.1.3 diff --git a/src/components/AppVersionInfo.tsx b/src/components/AppVersionInfo.tsx index 95f0db3..23ca147 100644 --- a/src/components/AppVersionInfo.tsx +++ b/src/components/AppVersionInfo.tsx @@ -1,6 +1,7 @@ import React, { useCallback, useEffect, useState, useRef, useMemo } from 'react'; import { getAppVersionInfo, isAndroidPlatform, isIOSPlatform } from '@/utils/platform'; import { Label } from '@/components/ui/label'; +import { Capacitor } from '@capacitor/core'; // 버전 정보 인터페이스 정의 interface VersionInfo { @@ -12,7 +13,7 @@ interface VersionInfo { timestamp?: number; error?: boolean; errorMessage?: string; - defaultValues?: boolean; + defaultValuesUsed?: boolean; } interface AppVersionInfoProps { @@ -26,11 +27,14 @@ const AppVersionInfo: React.FC = ({ showDevInfo = true, editable = false }) => { - // localStorage에서 이전에 저장된 버전 정보 가져오기 - const getSavedVersionInfo = useCallback((): VersionInfo | null => { + // 저장된 버전 정보 가져오기 (localStorage) + const savedInfo = useMemo(() => { try { - const saved = localStorage.getItem('app_version_info'); - return saved ? JSON.parse(saved) : null; + if (typeof localStorage !== 'undefined') { + const saved = localStorage.getItem('app_version_info'); + return saved ? JSON.parse(saved) : null; + } + return null; } catch (e) { console.error('저장된 버전 정보 파싱 오류:', e); return null; @@ -39,53 +43,40 @@ const AppVersionInfo: React.FC = ({ // 기본 버전 정보를 useMemo로 생성하여 불필요한 재계산 방지 const defaultInfo = useMemo(() => { - // 웹 환경에서는 더 높은 버전 정보를 기본값으로 사용 - const webDefaultInfo: VersionInfo = { - versionName: '1.1.1.2', // 웹 환경에서 사용할 버전 이름 - buildNumber: 6, // 웹 환경에서 사용할 빌드 번호 - versionCode: 6, // 웹 환경에서 사용할 버전 코드 - platform: 'web', - defaultValues: false // 기본값이 아닌 것으로 설정하여 우선순위 높이기 + const platform = Capacitor.getPlatform(); + const isWeb = platform === 'web'; + + if (savedInfo) { + return savedInfo; + } + + const defaultVersionInfo: VersionInfo = { + versionName: isWeb ? '1.1.1.2' : '1.0.0', + buildNumber: isWeb ? 6 : 1, + versionCode: isWeb ? 6 : 1, + platform: platform, + defaultValuesUsed: true }; - // localStorage에 저장된 정보 가져오기 - const savedInfo = getSavedVersionInfo(); - console.log('불러온 저장 정보:', savedInfo); + console.log('사용할 기본 정보:', defaultVersionInfo); - // 웹 환경에서는 웹 기본값, 안드로이드/iOS에서는 기존 기본값 사용 - const baseDefault = (!isAndroidPlatform() && !isIOSPlatform()) - ? webDefaultInfo - : { - versionName: '1.0.0', - buildNumber: 1, - versionCode: 1, - platform: 'default', - defaultValues: true - }; - - // 저장된 정보가 있으면 우선 사용, 없으면 기본값 사용 - const result = savedInfo || baseDefault; - console.log('사용할 기본 정보:', result); - - // localStorage에 초기 정보 저장 (웹에서만) - if (!savedInfo && !isAndroidPlatform() && !isIOSPlatform()) { + if (isWeb && typeof localStorage !== 'undefined') { try { - console.log('웹 기본 정보를 localStorage에 초기 저장'); - localStorage.setItem('app_version_info', JSON.stringify(result)); + localStorage.setItem('app_version_info', JSON.stringify(defaultVersionInfo)); + console.log('localStorage에 초기 버전 정보 저장됨'); } catch (e) { - console.error('초기 정보 저장 실패:', e); + console.error('localStorage에 초기 버전 정보 저장 실패:', e); } } - return result; - }, [getSavedVersionInfo]); + return defaultVersionInfo; + }, [savedInfo]); const [versionInfo, setVersionInfo] = useState(defaultInfo); const [loading, setLoading] = useState(true); const [error, setError] = useState(false); const [retries, setRetries] = useState(0); - // 버전 정보 가져오기 const fetchVersionInfo = useCallback(async () => { setLoading(true); setError(false); @@ -94,82 +85,56 @@ const AppVersionInfo: React.FC = ({ console.log('현재 플랫폼은', isAndroidPlatform() ? 'Android' : isIOSPlatform() ? 'iOS' : '웹'); - // 재시도를 하는 경우 짧은 지연 추가 if (retries > 0) { await new Promise(resolve => setTimeout(resolve, 300)); } - // localStorage에서 저장된 값을 먼저 확인 - const savedInfo = getSavedVersionInfo(); - - // 웹 환경에서는 저장된 값이 있으면 바로 사용 - if (!isAndroidPlatform() && !isIOSPlatform() && savedInfo && !savedInfo.defaultValues) { - console.log('웹 환경에서 저장된 버전 정보 사용:', savedInfo); - setVersionInfo(savedInfo); - setLoading(false); - return; - } - - // 네이티브 환경이거나 저장된 값이 없는 경우에만 플러그인 호출 const info = await getAppVersionInfo(); console.log('받은 앱 버전 정보:', info); - // 데이터 검증 - 유효한 버전 정보인지 확인 if (!info || typeof info !== 'object') { throw new Error('유효하지 않은 응답 형식'); } - // 타입 안전성을 위한 변환 함수 - const parseVersionInfo = (data: any): VersionInfo => { - // 빌드 번호 변환 + const parseVersionInfo = (data: Record): VersionInfo => { let buildNumber: number = defaultInfo.buildNumber; if (typeof data.buildNumber === 'number') { buildNumber = data.buildNumber; - } else if (typeof data.buildNumber === 'string' && data.buildNumber.trim() !== '') { + } else if (typeof data.buildNumber === 'string') { const parsed = parseInt(data.buildNumber, 10); if (!isNaN(parsed)) buildNumber = parsed; } - // 버전 코드 변환 let versionCode: number | undefined = defaultInfo.versionCode; if (typeof data.versionCode === 'number') { versionCode = data.versionCode; - } else if (typeof data.versionCode === 'string' && data.versionCode.trim() !== '') { - const parsed = parseInt(data.versionCode, 10); + } else if (typeof data.versionCode === 'string') { + const parsed = parseInt(data.versionCode as string, 10); if (!isNaN(parsed)) versionCode = parsed; } - // 응답 객체 구성 return { versionName: typeof data.versionName === 'string' && data.versionName.trim() !== '' - ? data.versionName + ? data.versionName as string : defaultInfo.versionName, buildNumber, versionCode, - platform: typeof data.platform === 'string' ? data.platform : defaultInfo.platform, + platform: data.platform as string || defaultInfo.platform, pluginResponse: typeof data.pluginResponse === 'string' ? data.pluginResponse : JSON.stringify(data), timestamp: typeof data.timestamp === 'number' ? data.timestamp : Date.now(), - defaultValues: false + defaultValuesUsed: false }; }; - // 타입 변환 및 정제 const newVersionInfo = parseVersionInfo(info); - // 웹 환경에서는 defaultValues가 true인 경우(기본값) localStorage 값 우선 적용 - if (!isAndroidPlatform() && !isIOSPlatform() && newVersionInfo.defaultValues && savedInfo) { - console.log('웹 환경의 기본값 대신 저장된 값 사용'); - setVersionInfo(savedInfo); - setLoading(false); - return; - } - - // LocalStorage에 버전 정보 저장 try { - localStorage.setItem('app_version_info', JSON.stringify(newVersionInfo)); - console.log('버전 정보가 localStorage에 저장됨:', newVersionInfo); + if (typeof localStorage !== 'undefined') { + localStorage.setItem('app_version_info', JSON.stringify(newVersionInfo)); + console.log('버전 정보가 localStorage에 저장됨:', newVersionInfo); + } } catch (e) { console.warn('버전 정보 저장 실패:', e); } @@ -181,63 +146,94 @@ const AppVersionInfo: React.FC = ({ console.error('버전 정보 가져오기 실패:', error); setError(true); setLoading(false); - - // 오류 발생 시 기존 저장된 값 유지 (초기화 방지) - console.log('오류 발생으로 기본값 사용'); } }, [retries, defaultInfo]); - // 재시도 처리 const handleRetry = useCallback(() => { setRetries(prev => prev + 1); fetchVersionInfo(); }, [fetchVersionInfo]); - // 초기화 완료 후 한번 더 시도하도록 설정 const initialLoadAttemptedRef = useRef(false); - // 컴포넌트 마운트 시 즉시 실행 (IIFE) useEffect(() => { - // 초기화 직후 localStorage 확인 - console.log('컴포넌트 마운트 시 localStorage 확인:', localStorage.getItem('app_version_info')); + let isMounted = true; - let isMounted = true; - - (async () => { - // 즉시 버전 정보 가져오기 시도 - await fetchVersionInfo(); - - // 컴포넌트가 언마운트되었는지 확인 - if (!isMounted) return; - - // 1000ms 후에 한번 더 시도 (네이티브 플러그인이 완전히 로드되지 않았을 경우 대비) - setTimeout(() => { + const loadVersionInfo = async () => { + console.log('앱 버전 정보 로딩 시작'); + + try { + if (savedInfo && isMounted) { + console.log('저장된 정보로 먼저 표시:', savedInfo); + setVersionInfo(savedInfo); + } + + const newVersionInfo = await getAppVersionInfo(); + if (!isMounted) return; - if (error || loading) { - fetchVersionInfo(); - initialLoadAttemptedRef.current = true; + console.log('불러온 버전 정보:', newVersionInfo); + + if (!isAndroidPlatform() && !isIOSPlatform() && + 'defaultValuesUsed' in newVersionInfo && + newVersionInfo.defaultValuesUsed === true && + savedInfo) { + console.log('웹 환경의 기본값 대신 저장된 값 사용'); + setVersionInfo(savedInfo); + setLoading(false); + return; } - }, 1000); - })(); - - // 개발 모드에서는 버전 정보 변경을 쉽게 확인하기 위해 주기적 갱신 - let interval: number | undefined; - if (process.env.NODE_ENV === 'development') { - interval = window.setInterval(() => { + + try { + if (typeof localStorage !== 'undefined') { + localStorage.setItem('app_version_info', JSON.stringify(newVersionInfo)); + console.log('버전 정보가 localStorage에 저장됨:', newVersionInfo); + } + } catch (e) { + console.warn('버전 정보 저장 실패:', e); + } + if (isMounted) { - fetchVersionInfo(); + setVersionInfo(newVersionInfo); + setLoading(false); + console.log('앱 버전 정보 표시 준비 완료'); + } + } catch (error) { + console.error('버전 정보 가져오기 실패:', error); + + if (isMounted) { + const fallbackInfo = savedInfo || defaultInfo; + console.log('오류 발생으로 대체 정보 사용:', fallbackInfo); + setVersionInfo(fallbackInfo); + setLoading(false); } - }, 30000); // 30초마다 새로 가져오기 - } - - return () => { - isMounted = false; - if (interval) { - clearInterval(interval); } }; - }, [fetchVersionInfo, error, loading]); + + loadVersionInfo(); + + return () => { + isMounted = false; + }; + }, [savedInfo, defaultInfo]); + + const renderDevInfo = () => { + if (versionInfo && showDevInfo) { + return ( +
+
+ 앱 플랫폼: {versionInfo.platform || 'unknown'} +
+ {versionInfo.defaultValuesUsed && ( +
+ ⚠️ 기본값 사용 중: 실제 버전 정보를 가져오지 못했습니다 +
+ )} +
+ ); + } + return null; + }; return (
@@ -276,6 +272,7 @@ const AppVersionInfo: React.FC = ({ {editable && (

ZELLYY CLOUD

)} + {renderDevInfo()}
)}