feat: Android 폰·태블릿 다운로드·앱 내 업데이트 완료
CI에서 APK 버전 자동 bump, 앱 복귀 시 업데이트 확인·시스템 다운로드, 프론트 Android 전용 UI 정리(iPad는 향후). Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -16,6 +16,8 @@ export interface AppUpdateCheckResult {
|
||||
release?: MobileAppReleaseInfoDto | null;
|
||||
}
|
||||
|
||||
const skipKey = (remote: string) => `gc_skip_update_${remote}`;
|
||||
|
||||
export async function getCurrentAppVersion(): Promise<string> {
|
||||
if (!Capacitor.isNativePlatform()) {
|
||||
return import.meta.env.VITE_APP_VERSION ?? '0.0.0';
|
||||
@@ -40,16 +42,51 @@ export async function checkForAppUpdate(): Promise<AppUpdateCheckResult> {
|
||||
return { updateAvailable, currentVersion, remoteVersion, release };
|
||||
}
|
||||
|
||||
/** Android — 시스템 브라우저/다운로드 매니저로 APK 받기 */
|
||||
export async function openApkDownload(release?: MobileAppReleaseInfoDto | null): Promise<boolean> {
|
||||
const info = release ?? await loadMobileAppReleaseInfo();
|
||||
const url = info?.downloadUrl?.trim();
|
||||
if (!url) return false;
|
||||
|
||||
window.location.href = url;
|
||||
if (Capacitor.isNativePlatform() && Capacitor.getPlatform() === 'android') {
|
||||
try {
|
||||
await App.openUrl({ url });
|
||||
return true;
|
||||
} catch {
|
||||
/* fallback */
|
||||
}
|
||||
}
|
||||
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.rel = 'noopener';
|
||||
a.download = info?.fileName ?? 'goldenchart-android.apk';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
return true;
|
||||
}
|
||||
|
||||
export async function promptAppUpdateIfNeeded(silent = false): Promise<boolean> {
|
||||
export function isUpdateDismissed(remoteVersion: string): boolean {
|
||||
try {
|
||||
return sessionStorage.getItem(skipKey(remoteVersion)) === '1';
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function dismissUpdate(remoteVersion: string): void {
|
||||
try {
|
||||
sessionStorage.setItem(skipKey(remoteVersion), '1');
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
/** 업데이트 확인 후 confirm → APK 다운로드. skipIfDismissed면 세션 내 거부 버전 생략 */
|
||||
export async function promptAppUpdate(options?: {
|
||||
skipIfDismissed?: boolean;
|
||||
}): Promise<boolean> {
|
||||
if (!Capacitor.isNativePlatform() || Capacitor.getPlatform() !== 'android') {
|
||||
return false;
|
||||
}
|
||||
@@ -59,22 +96,26 @@ export async function promptAppUpdateIfNeeded(silent = false): Promise<boolean>
|
||||
return false;
|
||||
}
|
||||
|
||||
if (silent) {
|
||||
if (options?.skipIfDismissed && isUpdateDismissed(check.remoteVersion)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const ok = window.confirm(
|
||||
`새 버전(v${check.remoteVersion})이 있습니다.\n`
|
||||
+ `현재: v${check.currentVersion}\n\n`
|
||||
+ 'APK를 다운로드하여 설치하시겠습니까?',
|
||||
`GoldenChart v${check.remoteVersion} 업데이트가 있습니다.\n`
|
||||
+ `(현재 v${check.currentVersion})\n\n`
|
||||
+ 'APK를 다운로드하여 설치하시겠습니까?\n'
|
||||
+ '(다운로드 후 알림에서 설치를 완료하세요.)',
|
||||
);
|
||||
if (!ok) return false;
|
||||
if (!ok) {
|
||||
dismissUpdate(check.remoteVersion);
|
||||
return false;
|
||||
}
|
||||
|
||||
await openApkDownload(check.release);
|
||||
return true;
|
||||
}
|
||||
|
||||
/** 설정 화면 등 — 수동 업데이트 확인 */
|
||||
/** 설정 화면 — 수동 업데이트 확인 */
|
||||
export async function checkAndPromptAppUpdate(): Promise<AppUpdateCheckResult> {
|
||||
const result = await checkForAppUpdate();
|
||||
if (!result.updateAvailable) {
|
||||
@@ -82,7 +123,8 @@ export async function checkAndPromptAppUpdate(): Promise<AppUpdateCheckResult> {
|
||||
return result;
|
||||
}
|
||||
const ok = window.confirm(
|
||||
`새 버전 v${result.remoteVersion} 사용 가능.\nAPK를 다운로드하시겠습니까?`,
|
||||
`새 버전 v${result.remoteVersion} 사용 가능.\n`
|
||||
+ `현재 v${result.currentVersion}\n\nAPK를 다운로드하시겠습니까?`,
|
||||
);
|
||||
if (ok) {
|
||||
await openApkDownload(result.release);
|
||||
|
||||
Reference in New Issue
Block a user