์ฐ์ธ๊ณผ ์๋์ ๊ณต์ ํ๋ PWA (Progressive Web App)
Love Alarm์ ์ฐ์ธ ๊ฐ ์๋์์ ๊ณต์ ํ ์ ์๋ ํ์ด๋ธ๋ฆฌ๋ ์น ์ ํ๋ฆฌ์ผ์ด์
์
๋๋ค.
์๋๋ก์ด๋ ์ฑ ์์ค์ฝ๋๋ฅผ ์น ๊ธฐ์ (HTML/CSS/JS)๋ก ์ฌ๊ตฌํํ์์ผ๋ฉฐ, PWA๋ก ๋์ํ์ฌ ํ ํ๋ฉด์ ์ค์น ๋ฐ ์ฑ์ฒ๋ผ ์ฌ์ฉํ ์ ์์ต๋๋ค.
- ์๋ ์ถ๊ฐ (์๊ฐ ์ ํ, ๋ผ๋ฒจ, ๋ฐ๋ณต ์์ผ, ์ค๋์ฆ, ์ง๋)
- ์๋ ๋ชฉ๋ก ์กฐํ (ํ์ฑ/๋นํ์ฑ ์น์ ๋ถ๋ฆฌ)
- ์๋ ํธ์ง (์นด๋ ํญ)
- ์๋ ์ญ์ (์ค์์ดํ or ํธ์ง ํ๋ฉด ์ญ์ ๋ฒํผ)
- ์๋ ํ์ฑ/๋นํ์ฑ ํ ๊ธ
- ๋ฐ๋ณต ์์ผ ์ค์ (๋งค์ผ/์ฃผ์ค/์ฃผ๋ง ์๋ ๋ผ๋ฒจ)
- ์ค๋์ฆ ๊ธฐ๋ฅ (5๋ถ ํ ์ฌ์ธ๋ฆผ)
- ์๋ ์ธ๋ฆผ ์ ์ฒดํ๋ฉด (ํ์ค ์ ๋๋ฉ์ด์ )
- Web Audio API ์๋์ ์ฌ์ (๋ก์ปฌ beep)
- ์๊ฒฉ URL ์์ ์คํธ๋ฆฌ๋ฐ ์ฌ์
- ์ค์์ดํ ์ ์ค์ฒ (์๋ก: ๋๊ธฐ, ์๋๋ก: ์ค๋์ฆ)
- ์ด๋ฉ์ผ/๋น๋ฐ๋ฒํธ ํ์๊ฐ์ /๋ก๊ทธ์ธ
- ๊ณ ์ 6์๋ฆฌ ์ฐ๊ฒฐ ์ฝ๋ ๋ฐ๊ธ
- ์ฐ๊ฒฐ ์ฝ๋๋ก ํํธ๋ ์ฐ๊ฒฐ/ํด์
- ์ฐ๊ฒฐ ์ํ ์ค์๊ฐ ํ์
- ํํธ๋ ์๋ ๋ชฉ๋ก ์ค์๊ฐ ์กฐํ
- ๋ง์ดํฌ๋ก ์ง์ ๋ น์ํ์ฌ ์ ๋ก๋
- ์ค๋์ค ํ์ผ ์ ํํ์ฌ ์ ๋ก๋
- Firebase Storage ์ ๋ก๋ (์งํ๋ฅ ํ์)
- ํํธ๋ ์๋์ ์ค์๊ฐ ๊ฐ์ง ๋ฐ ์๋ ์ ์ฉ
- "โฅ ํํธ๋ ์๋์" ๋ฐฐ์ง ํ์
- ํ ํ๋ฉด ์ค์น ๊ฐ๋ฅ (manifest.json)
- Service Worker (์คํ๋ผ์ธ ์บ์ฑ)
- ๋ฐฑ๊ทธ๋ผ์ด๋ ์๋ฆผ (Web Notification API)
- ๋คํฌ/๋ผ์ดํธ ๋ชจ๋ ์๋ ์ ํ
| ๊ตฌ๋ถ | ๊ธฐ์ |
|---|---|
| UI | HTML5 + CSS3 (iOS ์คํ์ผ) |
| JS | Vanilla JavaScript (ES2022) |
| ๋ฐฑ์๋ | Firebase v10 (Auth, Firestore, Storage) |
| ์๋ | setTimeout + Web Audio API |
| ์ ์ฅ์ | LocalStorage (์๋ ๋ฐ์ดํฐ) |
| PWA | Service Worker + Web App Manifest |
| ์์ด์ฝ | Font Awesome 6 |
| ํฐํธ | Google Fonts (Inter) |
- Firebase Console ์ ์
- ์ ํ๋ก์ ํธ ์์ฑ
- ์๋ ์๋น์ค ํ์ฑํ:
- Authentication โ ์ด๋ฉ์ผ/๋น๋ฐ๋ฒํธ ํ์ฑํ
- Firestore Database โ ์์ฑ (ํ ์คํธ ๋ชจ๋๋ก ์์)
- Storage โ ์์ฑ
- ํ๋ก์ ํธ ์ค์ โ ์ฑ ์ถ๊ฐ โ ์น (
</>) - Firebase ์ค์ ๊ฐ ๋ณต์ฌ
index.html ํ๋จ์ Firebase ์ค์ ๋ถ๋ถ์ ์์ :
const firebaseConfig = {
apiKey: "์ค์ _API_KEY",
authDomain: "ํ๋ก์ ํธID.firebaseapp.com",
projectId: "์ค์ _ํ๋ก์ ํธ_ID",
storageBucket: "ํ๋ก์ ํธID.appspot.com",
messagingSenderId: "์ค์ _SENDER_ID",
appId: "์ค์ _APP_ID"
};Firebase Console โ Firestore โ ๊ท์น ํญ์ ์๋ ๊ท์น ๋ถ์ฌ๋ฃ๊ธฐ:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read: if request.auth != null;
allow write: if request.auth.uid == userId;
match /shared_alarms/{alarmId} {
allow read: if request.auth != null;
allow write: if request.auth.uid == userId;
}
match /alarm_sounds/{alarmId} {
allow read: if request.auth.uid == userId;
allow write: if request.auth != null
&& get(/databases/$(database)/documents/users/$(request.auth.uid)).data.connectedPartnerUid == userId;
}
}
}
}
- Chrome ๋ธ๋ผ์ฐ์ ๋ก ์ฑ ์ ์
- ์ฃผ์์ฐฝ ์ค๋ฅธ์ชฝ ๋ฉ๋ด โ "ํ ํ๋ฉด์ ์ถ๊ฐ"
- ์ ์ฒดํ๋ฉด ์ฑ์ผ๋ก ์คํ ๊ฐ๋ฅ
Capacitor.js ๋๋ Cordova๋ฅผ ์ฌ์ฉํ์ฌ APK ์์ฑ ๊ฐ๋ฅ:
# Capacitor ์ฌ์ฉ ์์
npm install @capacitor/core @capacitor/cli @capacitor/android
npx cap init "Love Alarm" "com.example.lovealarm"
npx cap add android
npx cap copy
npx cap open android # Android Studio์์ ๋น๋/
โโโ index.html # ๋ฉ์ธ ์ฑ (๋ชจ๋ ํ๋ฉด ํฌํจ)
โโโ manifest.json # PWA ๋งค๋ํ์คํธ
โโโ sw.js # Service Worker
โโโ css/
โ โโโ style.css # ์ ์ฒด ์คํ์ผ (iOS ์คํ์ผ)
โโโ js/
โ โโโ app.js # ์ฑ ๋ก์ง (์๋, Firebase, UI)
โโโ icons/
โโโ icon.svg # ์ฑ ์์ด์ฝ
{
"love_alarms": [
{
"id": "์๋ID",
"hour": 7,
"minute": 0,
"label": "์ผ์ด๋!",
"isEnabled": true,
"repeatDays": [1, 2, 3, 4, 5],
"vibrate": true,
"snoozeEnabled": true,
"partnerSoundUri": "https://firebase.../sound.webm",
"soundUri": "default"
}
]
}users/{uid}
โโโ uid, email, nickname, connectionCode, connectedPartnerUid
โโโ shared_alarms/{alarmId}
โ โโโ alarmId, hour, minute, label, isEnabled
โโโ alarm_sounds/{alarmId}
โโโ soundUrl, uploadedAt, uploadedBy
- ์๋ ๊ธฐ๋ณธ ๊ธฐ๋ฅ์ Firebase ์์ด๋ ์์ ํ ๋์ํฉ๋๋ค
- ํ์๊ฐ์ /๋ก๊ทธ์ธ ์ "๋ฐ๋ชจ ๋ชจ๋"๋ก ์๋ (๋ก์ปฌ ์ ์ฅ๋ง)
- ํํธ๋ ์ฐ๊ฒฐ ๋ฐ ์๋์ ๊ณต์ ๊ธฐ๋ฅ์ Firebase ์ค์ ํ ์ฌ์ฉ ๊ฐ๋ฅ
- FCM ํธ์ ์๋ฆผ (๋ฐฑ๊ทธ๋ผ์ด๋ ์๋์ ๋ณ๊ฒฝ ์๋ฆผ)
- ์๋์ ๋ฏธ๋ฆฌ๋ฃ๊ธฐ ๊ธฐ๋ฅ
- AI ์์ฑ ์๋์ ์์ฑ (TTS)
- ํ ์์ ฏ (๋ค์ ์๋ ํ์)
- ์๋ฉด ํต๊ณ ๋ถ์
Made with โฅ Love Alarm