Compare commits
3 Commits
b39190baa2
...
60794733a1
| Author | SHA1 | Date | |
|---|---|---|---|
| 60794733a1 | |||
| b50a81028f | |||
| 6972cb5a63 |
@@ -87,11 +87,14 @@
|
|||||||
|
|
||||||
## 💡 출시 전 다듬기 후보 (대기 시간 활용, 선택)
|
## 💡 출시 전 다듬기 후보 (대기 시간 활용, 선택)
|
||||||
신규(빈손) 유저 경험 위주 — 저위험·고효율 순:
|
신규(빈손) 유저 경험 위주 — 저위험·고효율 순:
|
||||||
1. **빈 상태 → 행동 유도** (첫 내역/계좌 추가 CTA) ⭐ 임팩트 高/노력 低
|
1. ✅ **빈 상태 → 행동 유도** (완료) — 홈 '시작하기' 카드 + 가계부내역/계좌 빈 상태 CTA. 데이터 0 계정에서만 노출
|
||||||
2. **오프라인·네트워크 오류 UX 견고화** (폐쇄 테스트 대비)
|
2. ✅ **오프라인·네트워크 오류 배너** (완료) — http net:error 이벤트 + App.vue 상단 고정 배너(오프라인/서버연결실패, 새로고침 버튼)
|
||||||
3. 첫 실행 온보딩(시작하기 카드)
|
3. ✅ **앱 잠금(PIN)** (완료) — 자체 구현(해시 저장, 기본 OFF), 시작·복귀 시 잠금, 설정 토글+PIN설정 모달, 'PIN 분실→로그아웃' 갇힘 방지. 생체는 추후(네이티브 플러그인 필요)
|
||||||
4. Play 인앱 리뷰 요청
|
4. ✅ **전역 에러 폴백** (완료) — errorHandler + onErrorCaptured → 흰 화면 대신 '문제 발생 + 새로고침'
|
||||||
5. (선택) 앱 잠금(PIN/생체) — 매니페스트에 USE_BIOMETRIC 권한 존재, 구현 여부 미확인
|
5. ✅ **안드로이드 뒤로가기 개선** (완료) — 오버레이 닫기 우선 → 화면 뒤로 → 최상위 '한 번 더 누르면 종료' 토스트
|
||||||
|
6. 첫 실행 온보딩(1번과 중복이라 보류) / Play 인앱 리뷰(플러그인 필요, 출시 후) / 생체인증(출시 후)
|
||||||
|
|
||||||
|
> ⚠️ 앱 잠금은 시작 흐름을 가로채는 기능이라 **실기기 검증 권장**: 설정→앱잠금 ON→PIN 설정→백그라운드 후 복귀→잠금화면 확인→PIN 해제. (현재 설치 APK엔 미반영 — 재빌드·재설치 필요)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+3
-27
@@ -1,37 +1,13 @@
|
|||||||
// Capacitor 네이티브(안드로이드) 통합. 웹에서는 no-op.
|
// Capacitor 네이티브(안드로이드) 통합. 웹에서는 no-op.
|
||||||
import { Capacitor } from '@capacitor/core'
|
import { Capacitor } from '@capacitor/core'
|
||||||
import { App as CapApp } from '@capacitor/app'
|
import { App as CapApp } from '@capacitor/app'
|
||||||
import { useUiStore } from '@/stores/ui'
|
import { handleBack } from '@/composables/appBack'
|
||||||
import { appLock } from '@/composables/appLock'
|
|
||||||
|
|
||||||
export function setupCapacitor(router) {
|
export function setupCapacitor(router) {
|
||||||
if (!Capacitor.isNativePlatform()) return
|
if (!Capacitor.isNativePlatform()) return
|
||||||
|
|
||||||
const ui = useUiStore()
|
// 안드로이드 하드웨어 뒤로가기 → 하단 내비 뒤로 버튼과 동일한 통합 처리
|
||||||
let exitArmed = false
|
CapApp.addListener('backButton', () => handleBack(router))
|
||||||
let exitTimer = null
|
|
||||||
|
|
||||||
// 안드로이드 하드웨어 뒤로가기 우선순위:
|
|
||||||
// 1) 잠금 화면이면 우회 불가 → 종료
|
|
||||||
// 2) 열린 전역 오버레이(회원가입/로그인/비번/사이드바) 닫기
|
|
||||||
// 3) 화면 뒤로
|
|
||||||
// 4) 최상위에서 한 번 더 누르면 종료(실수 종료 방지)
|
|
||||||
CapApp.addListener('backButton', ({ canGoBack }) => {
|
|
||||||
if (appLock.state.locked) { CapApp.exitApp(); return }
|
|
||||||
|
|
||||||
if (ui.signupOpen) { ui.closeSignup(); return }
|
|
||||||
if (ui.loginOpen) { ui.closeLogin(); return }
|
|
||||||
if (ui.passwordOpen) { ui.closePassword(); return }
|
|
||||||
if (ui.sidebarOpen) { ui.closeSidebar(); return }
|
|
||||||
|
|
||||||
if (canGoBack && window.history.length > 1) { router.back(); return }
|
|
||||||
|
|
||||||
if (exitArmed) { CapApp.exitApp(); return }
|
|
||||||
exitArmed = true
|
|
||||||
window.dispatchEvent(new CustomEvent('app:toast', { detail: '한 번 더 누르면 종료됩니다' }))
|
|
||||||
clearTimeout(exitTimer)
|
|
||||||
exitTimer = setTimeout(() => { exitArmed = false }, 2000)
|
|
||||||
})
|
|
||||||
|
|
||||||
// 네이티브 표시용 클래스 (safe-area 등 스타일 분기)
|
// 네이티브 표시용 클래스 (safe-area 등 스타일 분기)
|
||||||
document.documentElement.classList.add('is-native')
|
document.documentElement.classList.add('is-native')
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { useRouter, useRoute } from 'vue-router'
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
|
import { handleBack } from '@/composables/appBack'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
@@ -9,9 +10,8 @@ const isHome = computed(() => route.path === '/')
|
|||||||
const isSettings = computed(() => route.path.startsWith('/settings'))
|
const isSettings = computed(() => route.path.startsWith('/settings'))
|
||||||
|
|
||||||
function goBack() {
|
function goBack() {
|
||||||
// 히스토리가 있으면 뒤로, 없으면 홈으로 (앱 첫 화면에서 빠져나가지 않게)
|
// 하드웨어 뒤로가기와 동일 동작(오버레이 닫기 → 화면 뒤로 → 최상위 종료 안내)
|
||||||
if (window.history.length > 1) router.back()
|
handleBack(router)
|
||||||
else router.push('/')
|
|
||||||
}
|
}
|
||||||
function goForward() {
|
function goForward() {
|
||||||
router.forward()
|
router.forward()
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { Capacitor } from '@capacitor/core'
|
||||||
|
import { App as CapApp } from '@capacitor/app'
|
||||||
|
import { useUiStore } from '@/stores/ui'
|
||||||
|
import { appLock } from '@/composables/appLock'
|
||||||
|
|
||||||
|
// 통합 뒤로가기 처리 — 하드웨어 뒤로(capacitor.js)와 하단 내비 뒤로 버튼이 동일 동작.
|
||||||
|
// 우선순위:
|
||||||
|
// 1) 잠금 화면이면 우회 불가 → (네이티브) 종료
|
||||||
|
// 2) 열린 전역 오버레이(회원가입/로그인/비번/사이드바) 닫기
|
||||||
|
// 3) 화면 뒤로(히스토리)
|
||||||
|
// 4) 최상위에서 '한 번 더 누르면 종료'(네이티브만, 실수 종료 방지)
|
||||||
|
let exitArmed = false
|
||||||
|
let exitTimer = null
|
||||||
|
|
||||||
|
export function handleBack(router) {
|
||||||
|
const native = Capacitor.isNativePlatform()
|
||||||
|
const ui = useUiStore()
|
||||||
|
|
||||||
|
if (appLock.state.locked) {
|
||||||
|
if (native) CapApp.exitApp()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ui.signupOpen) { ui.closeSignup(); return }
|
||||||
|
if (ui.loginOpen) { ui.closeLogin(); return }
|
||||||
|
if (ui.passwordOpen) { ui.closePassword(); return }
|
||||||
|
if (ui.sidebarOpen) { ui.closeSidebar(); return }
|
||||||
|
|
||||||
|
if (window.history.length > 1) { router.back(); return }
|
||||||
|
|
||||||
|
// 최상위(더 갈 곳 없음)
|
||||||
|
if (native) {
|
||||||
|
if (exitArmed) { CapApp.exitApp(); return }
|
||||||
|
exitArmed = true
|
||||||
|
window.dispatchEvent(new CustomEvent('app:toast', { detail: '한 번 더 누르면 종료됩니다' }))
|
||||||
|
clearTimeout(exitTimer)
|
||||||
|
exitTimer = setTimeout(() => { exitArmed = false }, 2000)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user