2026/05/19

오늘의 이야기



#스치니1000프로젝트 #재미 #행운기원 #Compose #Firebase

🎯 야 너 토요일마다 로또 확인하냐?
나도 맨날 “혹시나~” 하면서 봤거든 ㅋㅋ

근데 이제는 그냥 안 해
AI한테 맡겼어 🤖✨

그것도 구글 Gemini로다가!

그래서 앱 하나 만들었지
👉 “로또 예상번호 by Gemini” 🎱

AI가 분석해서 번호 딱! 뽑아줌
그냥 보고 참고만 하면 됨

재미로 해도 좋고…
혹시 모르는 거잖아? 😏


https://play.google.com/store/apps/details?id=com.billcorea.gptlotto1127




오늘의 이야기


#스하리1000명프로젝트

오늘 내가 만든앱 하나 알려주고 싶어, 이 앱은 알림수집기 라고 이름을 붙였는 데,
내 폰에 표시 되는 알림을 읽어서 내가 지정한 단어가 들어 있고, 지출기록을 남겨야 하는 알림이
있으면 수집하고, 카카오톡으로 친구에게 전달해 주는 기능을 구현해 줄꺼야. 📲

이번 패치에서는 하루 한번 지정한 시간에 나에게 알림(노티) 하도록 기능을 추가 했어. 🙏
한번 써보고 불편한 거 있으면 말해줘.

앱 바로가기
👉 https://play.google.com/store/apps/details?id=com.nari.notify2kakao





오늘의 이야기


#스하리1000명프로젝트,
有时候和外劳说话很难,对吧?
我制作了一个简单的应用程序,可以帮助您!你用你的语言写作,其他人用他们的语言看到它。
它根据设置自动翻译。
超级方便,可以轻松聊天。有机会就来看看吧!
https://play.google.com/store/apps/details?id=com.billcoreatech.multichat416




오늘의 이야기


#billcorea #운동동아리관리앱
🏸 Schneedle,羽毛球俱乐部必备应用!
👉 比洞赛 – 记录分数并寻找对手 🎉
适合任何地方,独自一人、与朋友一起或在俱乐部! 🤝
如果你喜欢羽毛球,一定要尝试一下

前往应用程序👉 https://play.google.com/store/apps/details?id=com.billcorea.matchplay




오늘의 이야기

🧾 바코드/QR 영수증 스캐너 앱 개발기 (BarcodeVoucher0407)


앱 메인 화면



 


일상에서 쉽게 버려지는 영수증들을 스마트하게 관리할 수 있는 바코드/QR 기반 영수증 적립 및 조회 앱을 개발했습니다. 이 앱은 단순히 영수증을 저장하는 것을 넘어, AI 기반 OCR(광학 문자 인식) 로 영수증의 내용을 자동으로 파악하고, 카카오맵과 연동하여 사용처의 위치까지 저장할 수 있는 똑똑한 가계부 역할을 합니다.


🛠 1. 프로젝트 개요 및 기술 스택


🎯 제품 목표



  • 바코드/QR 스캔 및 갤러리/카메라 이미지를 통한 영수증 디지털 보관

  • Groq AI (Llama 비전 모델) 를 활용한 자동 영수증 파싱 (매장명, 금액, 결제일 등)

  • 카카오맵 API를 활용한 매장 위치 시각화 및 저장

  • 기간별(월별/일별) 및 카테고리별 지출 통계 대시보드 제공

  • Play Core를 활용한 매끄러운 앱 내 업데이트(In-app Update) 제공


💻 기술 스택



  • 언어: Kotlin

  • UI: Jetpack Compose (단방향 데이터 흐름 및 상태 관리)

  • 아키텍처: MVVM + Clean Architecture (Repository, UseCase, 점진적 DTO 분리)

  • 데이터베이스: Room (Offline-first 구조)

  • 비동기 처리: Coroutines + Flow

  • 네트워크: Ktor Client (Groq AI API 연동)

  • 의존성 주입: Dagger Hilt

  • 기타: Kakao Map API, DataStore(설정 관리), ZXing(바코드 스캔)




🏗 2. 주요 아키텍처와 리팩토링 전략


✅ Clean Architecture 와 UseCase의 도입


앱의 덩치가 커짐에 따라 비즈니스 로직이 ViewModel에 집중되는 현상을 방지하기 위해 ObserveReceiptsUseCase, AnalyzeReceiptImageUseCase 등의 UseCase 계층을 도입했습니다. 이를 통해 로직의 재사용성을 높이고 테스트 용이성을 개선했습니다.


✅ Entity와 DTO의 분리


UI 계층에 Room의 Entity 모델이 직접 노출되는 것을 막기 위해 단계적인 리팩토링을 진행했습니다. 조회용 데이터를 ReceiptSummary, ReceiptDetail과 같은 DTO (Data Transfer Object) 로 매핑하여 도메인 경계를 확실히 구분하였습니다.




🚀 3. 핵심 구현 내용 (주요 코드)


🤖 3.1. Groq AI를 활용한 영수증 OCR과 JSON 파싱 내성 강화


가장 신경 쓴 부분 중 하나는 AI 모델 응답의 불안정성 해결입니다. LLM이 생성한 JSON이 때로는 형식이 깨져서 오거나 불필요한 마크다운 백틱(```)이 붙어오는 문제가 있었습니다. 이를 해결하기 위해 3단계 Fallback 로직을 적용했습니다.


// ReceiptOcrPayloadParser.kt 발췌
private fun parsePayload(rawContent: String): ReceiptOcrPayload {
// 1단계: 마크다운 찌꺼기(BOM, 백틱 등)를 제거하고 JSON 후보 텍스트만 추출
val jsonCandidate = runCatching { rawContent.extractJsonCandidate() }
.getOrElse { rawContent.trim() }

runCatching {
return json.parseToJsonElement(jsonCandidate).jsonObject.toPayload()
}

// 2단계: JSON 파싱 실패 시, 깨진 따옴표나 쉼표를 교정하여 재시도
val repaired = jsonCandidate.repairJsonCandidate()
runCatching {
return json.parseToJsonElement(repaired).jsonObject.toPayload()
}

// 3단계: 모두 실패할 경우 rawText만이라도 보존하여 반환
return ReceiptOcrPayload(
storeName = null, totalAmount = null, currency = null,
purchasedAtIso = null, memo = null,
rawText = rawContent.take(300).trim().ifBlank { null },
)
}

// 텍스트 기반 휴리스틱 분석 (총 금액 추출)
private fun String.extractLikelyTotalAmount(): Long? {
val totalLabelRegex = Regex(
"(\\uCD1D\\s*\\uD569\\uACC4|\\uD569\\uACC4|\\uCD1D\\uC561|\\uACB0\\uC81C\\s*\\uAE08\\uC561)[^0-9]{0,8}([0-9][0-9,]{2,})",
RegexOption.IGNORE_CASE
)
return totalLabelRegex.find(this)?.groupValues?.getOrNull(2)
?.replace(",", "")?.toLongOrNull()
}

Tip: AI가 JSON 생성을 완벽히 하지 못하는 경우를 대비해, 응답 평문에서 정규식을 이용해 영수증 금액과 상호명을 2차로 추출하는 안전장치(Fallback)를 두었습니다.


📊 3.2. Compose Canvas로 직접 그린 지출 통계 차트




서드파티 라이브러리에 의존하지 않고, Jetpack Compose의 CanvasAnimatable을 활용하여 애니메이션이 포함된 바 차트(Bar Chart) 를 직접 구현했습니다.


// StatsScreen.kt 발췌
@Composable
private fun StatBarChart(
labels: List<String>,
values: List<Long>,
primaryColor: Color,
modifier: Modifier = Modifier,
) {
val maxValue = values.max().toFloat().coerceAtLeast(1f)
// 진입 시 아래에서 위로 올라오는 700ms 애니메이션
val animProgress = remember(values) { Animatable(0f) }
LaunchedEffect(values) {
animProgress.snapTo(0f)
animProgress.animateTo(1f, animationSpec = tween(700))
}

Canvas(modifier = modifier.fillMaxWidth().height(190.dp)) {
val chartW = size.width
val barMaxH = size.height * 0.68f
val slotW = chartW / values.size

values.forEachIndexed { idx, value ->
val ratio = (value.toFloat() / maxValue) * animProgress.value
val barH = barMaxH * ratio

// 막대 그리기
drawRoundRect(
color = primaryColor,
topLeft = Offset(slotW * idx + (slotW / 4f), size.height - barH - 20f),
size = Size(slotW / 2f, barH),
cornerRadius = CornerRadius(5.dp.toPx())
)
}
}
}

💾 3.3. Room DB를 이용한 강력한 SQLite 집계 통계


앱 내에서 보여지는 월별/일별 지출 통계는 앱 단에서 계산하는 대신, Room 데이터베이스의 SQLite 쿼리를 적극 활용하여 성능을 최적화했습니다.


// ReceiptDao.kt 발췌
@Query("""
SELECT
strftime('%Y-%m', datetime(COALESCE(purchasedAt, createdAt) / 1000, 'unixepoch', 'localtime')) AS month,
SUM(COALESCE(totalAmount, 0)) AS totalAmount,
COUNT(*) AS count
FROM receipts
WHERE (:startMillis IS NULL OR COALESCE(purchasedAt, createdAt) >= :startMillis)
AND (:endMillis IS NULL OR COALESCE(purchasedAt, createdAt) <= :endMillis)
GROUP BY month
ORDER BY month DESC
""")
fun observeMonthlyStats(
startMillis: Long?,
endMillis: Long?
): Flow<List<MonthlyStatRow>>

기간 필터까지 DB 단에서 처리하고, 결과를 Flow로 반환받아 UI에 즉시 리액티브하게 반영되도록 구성했습니다.




💡 4. 마무리 및 회고


단일 모듈로 시작하여 MVP를 빠르게 완성한 프로젝트입니다. 개발 과정에서 특히 흥미로웠던 부분은 AI 기반 OCR을 연동하며 발생한 다양한 예외 처리였습니다. AI의 응답은 항상 일관되지 않기 때문에 정규식 Fallback이나 JSON 교정 로직 같은 방어적 프로그래밍이 매우 중요하다는 것을 배웠습니다.


📌 향후 고도화 계획:



  1. 사용자 맞춤형 커스텀 카테고리 기능 및 도넛(파이) 차트 시각화 추가

  2. OCR 처리 속도 및 정확도 향상을 위한 AI 모델 A/B 테스트 정교화

  3. 기능 확장에 대비한 멀티 모듈(core, feature 등) 분리 작업


Jetpack Compose와 Kotlin 최신 스택들을 활용해 클린 아키텍처를 도입해 보는 뜻깊은 경험이었습니다. 🚀





오늘의 이야기


#스하리1000명프로젝트,
In Korea verloren? Auch wenn Sie kein Koreanisch sprechen, hilft Ihnen diese App dabei, sich problemlos fortzubewegen.
Sprechen Sie einfach Ihre Sprache – es übersetzt, sucht und zeigt Ergebnisse in Ihrer Sprache an.
Ideal für Reisende! Unterstützt mehr als 10 Sprachen, darunter Englisch, Japanisch, Chinesisch, Vietnamesisch und mehr.
Probieren Sie es jetzt aus!
https://play.google.com/store/apps/details?id=com.billcoreatech.opdgang1127




2026/05/18

오늘의 이야기



#스치니1000프로젝트 #재미 #행운기원 #Compose #Firebase

🎯 야 너 토요일마다 로또 확인하냐?
나도 맨날 “혹시나~” 하면서 봤거든 ㅋㅋ

근데 이제는 그냥 안 해
AI한테 맡겼어 🤖✨

그것도 구글 Gemini로다가!

그래서 앱 하나 만들었지
👉 “로또 예상번호 by Gemini” 🎱

AI가 분석해서 번호 딱! 뽑아줌
그냥 보고 참고만 하면 됨

재미로 해도 좋고…
혹시 모르는 거잖아? 😏


https://play.google.com/store/apps/details?id=com.billcorea.gptlotto1127




오늘의 이야기


#스하리1000명프로젝트

오늘 내가 만든앱 하나 알려주고 싶어, 이 앱은 알림수집기 라고 이름을 붙였는 데,
내 폰에 표시 되는 알림을 읽어서 내가 지정한 단어가 들어 있고, 지출기록을 남겨야 하는 알림이
있으면 수집하고, 카카오톡으로 친구에게 전달해 주는 기능을 구현해 줄꺼야. 📲

이번 패치에서는 하루 한번 지정한 시간에 나에게 알림(노티) 하도록 기능을 추가 했어. 🙏
한번 써보고 불편한 거 있으면 말해줘.

앱 바로가기
👉 https://play.google.com/store/apps/details?id=com.nari.notify2kakao





오늘의 이야기


#스하리1000명프로젝트,
外国人労働者と話すのが難しいこともありますよね?
簡単に役立つアプリを作りました!あなたは自分の言語で書き、他の人は自分の言語でそれを見ます。
設定に基づいて自動翻訳します。
簡単なチャットに非常に便利です。機会があったら見てみてください!
https://play.google.com/store/apps/details?id=com.billcoreatech.multichat416




오늘의 이야기


#billcorea #운동동아리관리앱
🏸スチーニーたち、バドミントン同好会必須アプリ登場!
👉マッチプレイ - スコア記録&試合相手を探す🎉
一人で、友達、同好会どこでもぴったりです! 🤝
バドミントン好きならぜひ使ってみてください

アプリショートカット👉 https://play.google.com/store/apps/details?id=com.billcorea.matchplay




오늘의 이야기

BarcodeVoucher0407 개발일기 #07



날짜: 2026-04-19
주제: Phase 2 마무리 + 레거시 QR 스캔 UX 폴리싱



QR Scanner



 


오늘의 목표


오늘은 Phase 2의 흐름을 유지하면서, 실제 사용 중 눈에 띄는 불편 요소를 줄이는 데 집중했다.
특히 레거시 QR 스캔 화면의 시스템바 겹침 문제버튼 가시성 문제를 해결하는 것이 핵심이었다.




배경: 왜 이 작업이 필요했나


기능 자체는 동작했지만, 실제 디바이스에서 아래 문제가 반복적으로 보였다.



  • 상단의 뒤로가기/플래시 버튼이 상태바, 컷아웃 영역과 겹쳐 보이는 경우가 있음

  • 하단 스캔 안내 문구(예: "바코드를 선에 맞춰주세요")가 네비게이션 바와 시각적으로 겹침

  • ic_media_previous 아이콘이 "뒤로가기" 의미보다 "미디어 이전" 느낌에 가까워 직관성이 떨어짐


즉, 기능 완성도보다 실사용 UX 디테일을 끌어올리는 작업이 필요했다.




오늘 적용한 변경 사항


1) 레거시 스캔 화면 안전영역(insets) 보정



  • 파일: app/src/main/java/com/billcorea/barcodevoucher0407/feature/scan/LegacyQrScanActivity.kt

  • applySystemBarInsets에서 상단/하단 인셋을 분리 반영

    • 상단 인셋: 뒤로가기/플래시 버튼 margin 보정

    • 하단 인셋: 스캔 안내 문구(statusView) 패딩 보정




val topBars = insets.getInsets(
WindowInsetsCompat.Type.statusBars() or WindowInsetsCompat.Type.displayCutout(),
)
val bottomBars = insets.getInsets(WindowInsetsCompat.Type.navigationBars())

(backButton.layoutParams as FrameLayout.LayoutParams).topMargin = baseBackTop + topBars.top
(flashButton.layoutParams as FrameLayout.LayoutParams).topMargin = baseFlashTop + topBars.top
statusView.setPadding(0, 0, 0, baseStatusBottom + bottomBars.bottom)

효과:



  • 버튼/안내 문구가 시스템 UI와 겹치지 않고 안정적으로 배치됨


2) 뒤로가기/플래시 버튼 시인성 개선



  • 파일: app/src/main/res/layout/activity_legacy_qr_scan.xml

  • 변경 내용:

    • 버튼 배경을 원형 반투명으로 통일

    • padding, scaleType 조정으로 아이콘 가독성 향상



  • 추가 리소스: app/src/main/res/drawable/bg_round_translucent_button.xml


<ImageButton
android:id="@+id/btnBack"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="@drawable/bg_round_translucent_button"
android:padding="12dp"
android:scaleType="centerInside"
android:src="@drawable/ic_arrow_back_24" />

효과:



  • 카메라 프리뷰 위에서도 버튼이 더 잘 보임


3) 뒤로가기 아이콘 교체 (media_previous -> arrow_back)



  • 기존: @android:drawable/ic_media_previous

  • 신규: @drawable/ic_arrow_back_24

  • 추가 리소스: app/src/main/res/drawable/ic_arrow_back_24.xml


<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z" />
</vector>

효과:



  • 버튼 의미가 더 명확해져 사용자 혼란 감소


4) 리소스 lint 정리



  • android:tint 관련 경고/오류 포인트 정리

  • 현재 아이콘은 벡터 자체를 흰색으로 지정해 불필요 속성 제거


<!-- before -->
android:src="@drawable/ic_arrow_back_24"
android:tint="@android:color/white"

<!-- after -->
android:src="@drawable/ic_arrow_back_24"

효과:



  • 리소스 검사 에러 없이 안정 상태 유지




Phase 2 진행 맥락 정리


agent.md 기준으로 Phase 2는 체크 완료 상태이며, 오늘 작업은 그중 2-3 UX 개선 항목의 완성도 보강에 해당한다.


이미 완료된 Phase 2 주요 축:



  • 검색/필터/정렬

  • 영수증 이미지 첨부

  • 스캔 실패/중복 처리 UX 개선

  • DataStore 설정화


오늘은 위 기능들을 실제 화면 품질 관점에서 다듬는 "마감 폴리싱" 성격이었다.




작업하면서 얻은 인사이트



  • 기능 구현이 끝나도, 카메라 화면처럼 시스템 UI와 맞물리는 영역은 인셋 처리가 UX 품질을 크게 좌우한다.

  • 아이콘은 단순 미관이 아니라 행동 의미 전달 그 자체다. (media_previous vs arrow_back)

  • 작은 시각 개선(배경, 패딩, 배치)이 사용자 체감 품질을 빠르게 끌어올린다.




다음 계획


Phase 3로 넘어가며 아래 순서로 진행할 예정이다.



  1. Kakao Map 연동(상세 -> 지도)

  2. In-app Update 적용

  3. 통계/리포트(월별 금액, 카테고리)




한 줄 회고


오늘은 "새 기능 추가"보다 "이미 있는 기능을 편하게 쓰게 만드는 작업"이 얼마나 중요한지 다시 확인한 하루였다.





오늘의 이야기

#스치니1000프로젝트 #재미 #행운기원 #Compose #Firebase 🎯 야 너 토요일마다 로또 확인하냐? 나도 맨날 “혹시나~” 하면서 봤거든 ㅋㅋ 근데 이제는 그냥 안 해 AI한테 맡겼어 🤖✨ 그것도 구글 Gemini로다가! ...