diff --git a/android/app/capacitor.build.gradle b/android/app/capacitor.build.gradle
index bf02646..f451109 100644
--- a/android/app/capacitor.build.gradle
+++ b/android/app/capacitor.build.gradle
@@ -10,6 +10,7 @@ android {
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
dependencies {
implementation project(':capacitor-app')
+ implementation project(':capacitor-camera')
implementation project(':capacitor-preferences')
}
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index b06ddbf..bcc4816 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -38,4 +38,8 @@
+
+
+
+
diff --git a/android/capacitor.settings.gradle b/android/capacitor.settings.gradle
index 543170e..c6772c6 100644
--- a/android/capacitor.settings.gradle
+++ b/android/capacitor.settings.gradle
@@ -5,5 +5,8 @@ project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/
include ':capacitor-app'
project(':capacitor-app').projectDir = new File('../node_modules/@capacitor/app/android')
+include ':capacitor-camera'
+project(':capacitor-camera').projectDir = new File('../node_modules/@capacitor/camera/android')
+
include ':capacitor-preferences'
project(':capacitor-preferences').projectDir = new File('../node_modules/@capacitor/preferences/android')
diff --git a/package-lock.json b/package-lock.json
index f7983b3..8bf7e5e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,6 +10,7 @@
"dependencies": {
"@capacitor/android": "^8.3.4",
"@capacitor/app": "^8.1.0",
+ "@capacitor/camera": "^8.2.0",
"@capacitor/cli": "^8.3.4",
"@capacitor/core": "^8.3.4",
"@capacitor/preferences": "^8.0.1",
@@ -542,6 +543,15 @@
"@capacitor/core": ">=8.0.0"
}
},
+ "node_modules/@capacitor/camera": {
+ "version": "8.2.0",
+ "resolved": "https://registry.npmjs.org/@capacitor/camera/-/camera-8.2.0.tgz",
+ "integrity": "sha512-hYfrT6xpL936qoEkIpJzSnb0fQCaTkOux1cXzGBfH8QLOGqr6gSLiWZlZz/fqMPmMKJMNRBqlTQkj5fuMhVZog==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@capacitor/core": ">=8.0.0"
+ }
+ },
"node_modules/@capacitor/cli": {
"version": "8.3.4",
"resolved": "https://registry.npmjs.org/@capacitor/cli/-/cli-8.3.4.tgz",
diff --git a/package.json b/package.json
index ba673b0..c0c6a6e 100644
--- a/package.json
+++ b/package.json
@@ -17,6 +17,7 @@
"dependencies": {
"@capacitor/android": "^8.3.4",
"@capacitor/app": "^8.1.0",
+ "@capacitor/camera": "^8.2.0",
"@capacitor/cli": "^8.3.4",
"@capacitor/core": "^8.3.4",
"@capacitor/preferences": "^8.0.1",
diff --git a/src/views/account/AccountView.vue b/src/views/account/AccountView.vue
index 5c202e1..5773663 100644
--- a/src/views/account/AccountView.vue
+++ b/src/views/account/AccountView.vue
@@ -3,6 +3,8 @@ import { computed, onMounted, reactive, ref } from 'vue'
import { accountApi } from '@/api/accountApi'
import IconBtn from '@/components/ui/IconBtn.vue'
import { scanReceipt } from '@/utils/receiptOcr'
+import { Capacitor } from '@capacitor/core'
+import { Camera, CameraResultType, CameraSource } from '@capacitor/camera'
const now = new Date()
const year = ref(now.getFullYear())
@@ -59,21 +61,42 @@ const receiptInput = ref(null)
const ocrRunning = ref(false)
const ocrProgress = ref(0)
const ocrResult = ref(null) // { amount, date, store }
-function pickReceipt() {
+async function pickReceipt() {
ocrResult.value = null
- receiptInput.value?.click()
+ // 네이티브(앱): 카메라/갤러리 선택 프롬프트, 웹: 파일 선택
+ if (Capacitor.isNativePlatform()) {
+ try {
+ const photo = await Camera.getPhoto({
+ source: CameraSource.Prompt,
+ resultType: CameraResultType.DataUrl,
+ quality: 70,
+ correctOrientation: true,
+ promptLabelHeader: '영수증',
+ promptLabelPhoto: '갤러리에서 선택',
+ promptLabelPicture: '카메라로 촬영',
+ promptLabelCancel: '취소',
+ })
+ if (photo?.dataUrl) await runOcr(photo.dataUrl)
+ } catch {
+ // 사용자가 취소 → 무시
+ }
+ } else {
+ receiptInput.value?.click()
+ }
}
async function onReceiptFile(e) {
const file = e.target.files?.[0]
e.target.value = '' // 같은 파일 재선택 허용
- if (!file) return
+ if (file) await runOcr(file)
+}
+async function runOcr(image) {
ocrRunning.value = true
ocrProgress.value = 0
ocrResult.value = null
formError.value = null
try {
- const r = await scanReceipt(file, (p) => (ocrProgress.value = p))
- // 추출값이 있을 때만 폼에 채움 (기존 입력 보존하되, 비어있으면 채움)
+ const r = await scanReceipt(image, (p) => (ocrProgress.value = p))
+ // 추출값이 있을 때만 폼에 채움 (메모는 비어있을 때만)
if (r.amount) form.amount = r.amount
if (r.date) form.entryDate = r.date
if (r.store && !form.memo) form.memo = r.store