Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions .github/workflows/verify.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: Verify

on:
push:
branches: [main]
pull_request:
workflow_dispatch:

jobs:
structure-and-secrets:
name: Structure & Secrets
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Required files
run: |
for f in README.md LICENSE app/build.gradle build.gradle settings.gradle gradle.properties; do
if [ ! -f "$f" ]; then
echo "::error::Missing required file: $f"
exit 1
fi
done

- name: No committed API keys (Food Safety OpenAPI uses base64-like service keys)
run: |
# Sanity check: no hardcoded service keys in source
if grep -rEn 'FOOD_API_KEY[[:space:]]*=[[:space:]]*"[A-Za-z0-9+/=]{20,}"' app/src/ 2>/dev/null; then
echo "::error::Hardcoded Food Safety API key in source"
exit 1
fi

- name: .gitignore covers known secret paths
run: |
if ! grep -Fq "local.properties" .gitignore; then
echo "::warning::local.properties should be in .gitignore"
fi

- name: README links resolve
run: |
missing=0
for link in $(grep -oE '\]\(\./[^)]+\)' README.md | sed 's|](\./||; s|)$||' | sort -u); do
if [ ! -e "$link" ]; then
echo "::warning::README links to missing path: $link"
missing=$((missing+1))
fi
done
if [ "$missing" -gt 0 ]; then
echo "::warning::$missing README link(s) point to non-existent paths"
fi
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2021 jumincho

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
155 changes: 140 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,23 @@
# Beating Yesterday (어제의 나 이기기)
<div align="center">

# beating-yesterday

**어제의 나와 경쟁하며 자기 성장을 동기부여하는 Android 앱**
**Android app that gamifies daily self-improvement against yesterday's record**

![Platform](https://img.shields.io/badge/platform-Android-3DDC84?logo=android&logoColor=white)
![Language](https://img.shields.io/badge/language-Java-007396?logo=java&logoColor=white)
![Min SDK](https://img.shields.io/badge/minSdk-21-blue)
![License](https://img.shields.io/badge/license-MIT-green)
![Year](https://img.shields.io/badge/year-2021-blue)

**한국어** · [English](#english)

</div>

---

## 개요

> 어제의 나와 매일 경쟁하며 자기 성장을 동기부여하는 Android 앱

Expand Down Expand Up @@ -59,17 +78,26 @@ app/src/main/
└── ...
```

## 시크릿 처리

빌드 시스템은 `local.properties` 에서 식품안전나라 OpenAPI 키를 읽어 Gradle
`buildConfigField` 를 통해 `BuildConfig.FOOD_API_KEY` 로 주입합니다. 키 문자열은
소스에 포함되지 않으며, `local.properties` 는 `.gitignore` 에 포함되어 있습니다.

| `local.properties` 키 | `BuildConfig` 필드 | 사용처 |
| --- | --- | --- |
| `FOOD_API_KEY` | `BuildConfig.FOOD_API_KEY` | 식품안전나라 칼로리 조회 |

## 빌드 방법

1. 식품안전나라 OpenAPI 키를 발급받습니다 (아래 "보안 주의사항" 참고).
2. 프로젝트 루트에 `local.properties` 파일을 두고 다음 줄을 추가합니다.
1. [식품안전나라 OpenAPI](https://various.foodsafetykorea.go.kr/nutrient/) 에서 API 키 발급
2. 프로젝트 루트의 `local.properties` 다음 줄 추가

```properties
FOOD_API_KEY=YOUR_KEY_HERE
FOOD_API_KEY=발급받은_키
```

`local.properties`는 `.gitignore`에 포함되어 있어 커밋되지 않습니다.
3. Android Studio에서 프로젝트를 열고 `Run`을 실행합니다.
3. Android Studio 에서 프로젝트 열기 → `Run`

```bash
./gradlew assembleDebug
Expand All @@ -80,15 +108,6 @@ app/src/main/
- JDK 8 이상
- Android SDK 32

## 보안 주의사항

- 이전 커밋(예: `fae2f77` 등 머지된 history)에 식품안전나라 OpenAPI 키 문자열이
소스 코드에 하드코딩되어 있었습니다. 해당 키는 이미 공개 git history에 노출되었으므로
**식품안전나라 사이트에서 즉시 키를 재발급**받기를 권장합니다.
(재발급 후 기존 키는 사용 불가 상태가 되도록 폐기 처리해야 합니다.)
- 이후로는 API 키를 `local.properties`에만 보관하며, Gradle `buildConfigField`를 통해
`BuildConfig.FOOD_API_KEY` 로 주입합니다. 소스 코드에 직접 키를 적지 마세요.

## 발표 자료

- 발표 영상: <https://youtu.be/vW4CvgCHdco>
Expand All @@ -97,3 +116,109 @@ app/src/main/
## 스크린샷

<img width="20%" src="https://user-images.githubusercontent.com/77545063/200374902-2da72615-5cf8-4d20-b950-f00962a1c795.png" alt="앱 스크린샷"/>

## 라이선스

[MIT License](./LICENSE)

---

<a name="english"></a>

## English

> Android app that gamifies daily self-improvement against yesterday's record.

Logs daily calorie intake and study time, then judges today's record against yesterday's.
Calorie scoring is BMI-aware, so this is less a diet tracker than a personalized
self-management tool that frames each day as a contest with yesterday's you.

### Features

- **User profile** — name, sex, age, height, weight → auto-computed BMI.
- **Diet** — log breakfast / lunch / dinner; the Korean Food Safety OpenAPI returns calories, and a BMI-bracket-aware (under / normal / over) calorie score is computed.
- **Study timer** — circular timer with drag-to-set hours / minutes / seconds, plus a stopwatch.
- **TODO** — SQLite-backed list with swipe-to-refresh.
- **Daily contest** — yesterday's calorie score + study time vs today's, reported as "did you beat yesterday?"

### Screens

Bottom navigation with three tabs:

| Tab | Description |
| --- | --- |
| Diet | Food entry · weight management |
| Home | Profile · daily yesterday-vs-today contest |
| Productivity | TODO · study timer |

### Tech stack

- **Language**: Java
- **Platform**: Android (minSdk 21 / targetSdk 32)
- **Architecture**: Fragment + ViewModel
- **Storage**: SharedPreferences (profile, daily records) + SQLite (TODO)
- **Network**: HttpURLConnection + Korean Food Safety OpenAPI
- **UI**: Material Components, ConstraintLayout, RecyclerView, Navigation Component

### Layout

```
app/src/main/
├── java/com/jumincho/beatingyesterday/
│ ├── MainActivity.java
│ ├── data/ # data layer
│ │ ├── FoodCalorieApi.java # Food Safety API client
│ │ ├── Note.java # TODO model
│ │ ├── NoteAdapter.java
│ │ └── NoteDatabase.java # SQLite helper
│ └── ui/
│ ├── ProfileSetupActivity.java # first-launch profile entry
│ ├── home/ # home (contest result)
│ ├── diet/ # diet / weight management
│ └── productivity/ # TODO + timer
│ ├── CircularTimerView.java # custom circular timer view
│ └── TimerMode.java
└── res/
├── layout/ # screen layouts
├── navigation/ # bottom-tab navigation graph
└── ...
```

### Secrets handling

The build system reads the Food Safety OpenAPI key from `local.properties` and
injects it via Gradle `buildConfigField` as `BuildConfig.FOOD_API_KEY`. No
secrets are present in source. `local.properties` is gitignored.

| `local.properties` key | `BuildConfig` field | Use site |
| --- | --- | --- |
| `FOOD_API_KEY` | `BuildConfig.FOOD_API_KEY` | Food Safety calorie lookup |

### Build

1. Get an API key from the [Korean Food Safety OpenAPI](https://various.foodsafetykorea.go.kr/nutrient/).
2. Add a `local.properties` entry at the repo root:

```properties
FOOD_API_KEY=your_key_here
```

3. Open the project in Android Studio → `Run`.

```bash
./gradlew assembleDebug
```

Requirements:
- Android Studio Bumblebee+
- JDK 8+
- Android SDK 32

### Materials

- Demo video: <https://youtu.be/vW4CvgCHdco>
- Slides: [`docs/presentation.pptx`](docs/presentation.pptx)

### License

[MIT License](./LICENSE)
Loading