{"id":51262436,"url":"https://github.com/prgrms-fullcycle-devcourse/webfull_9_10_ddt_mobile","last_synced_at":"2026-06-29T13:01:21.672Z","repository":{"id":362414951,"uuid":"1257057281","full_name":"prgrms-fullcycle-devcourse/webfull_9_10_DDT_Mobile","owner":"prgrms-fullcycle-devcourse","description":"DDT(디지털 디톡스 타이머) RN 모바일 앱 레포지토리 입니다.","archived":false,"fork":false,"pushed_at":"2026-06-17T08:21:55.000Z","size":6946,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-17T09:09:31.614Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/prgrms-fullcycle-devcourse.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-02T10:22:10.000Z","updated_at":"2026-06-17T07:07:23.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/prgrms-fullcycle-devcourse/webfull_9_10_DDT_Mobile","commit_stats":null,"previous_names":["prgrms-fullcycle-devcourse/webfull_9_10_ddt_mobile"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/prgrms-fullcycle-devcourse/webfull_9_10_DDT_Mobile","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/prgrms-fullcycle-devcourse%2Fwebfull_9_10_DDT_Mobile","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/prgrms-fullcycle-devcourse%2Fwebfull_9_10_DDT_Mobile/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/prgrms-fullcycle-devcourse%2Fwebfull_9_10_DDT_Mobile/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/prgrms-fullcycle-devcourse%2Fwebfull_9_10_DDT_Mobile/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/prgrms-fullcycle-devcourse","download_url":"https://codeload.github.com/prgrms-fullcycle-devcourse/webfull_9_10_DDT_Mobile/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/prgrms-fullcycle-devcourse%2Fwebfull_9_10_DDT_Mobile/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34927687,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-29T02:00:05.398Z","response_time":58,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-29T13:01:19.419Z","updated_at":"2026-06-29T13:01:21.665Z","avatar_url":"https://github.com/prgrms-fullcycle-devcourse.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 📱 DDT (디지털 디톡스 타이머) - Mobile App\n\n\u003e **\"남들이 딴짓할 때, 우리는 서로를 가두고 집중한다.\"**\n\nDDT(디지털 디톡스 타이머)는 스마트폰의 유혹에서 벗어나 목표한 시간 동안 온전히 집중할 수 있도록 돕는 서비스입니다. 본 레포지토리는 DDT 서비스의 **React Native (Expo) 기반 모바일 프론트엔드 애플리케이션**입니다. 모바일 기기의 특성을 활용하여 사용자의 앱 이탈(백그라운드 전환)을 정밀하게 감지하고, 팀원들과의 실시간 상호작용을 네이티브 환경에서 매끄럽게 제공합니다.\n\n---\n\n## 📝 프로젝트 소개\n\n스마트폰 중독과 집중력 저하를 해결하기 위해 기획된 모바일 앱 서비스입니다. 방 코드를 통해 친구들과 대기실에 모여 목표 시간과 휴식 시간을 정합니다. 앱을 백그라운드로 내리거나 다른 앱을 켜는 등 '딴짓'을 하면 즉각적으로 이탈 시간이 누적되며, 세션 종료 후 이탈 누적 시간에 따라 룰렛을 돌려 벌칙을 받게 됩니다.\n\n---\n\n## ✨ 주요 기능\n\n* **실시간 방 참여 및 게스트 로그인** : 구글 소셜 로그인 및 1회성 게스트 토큰을 발급하여 8자리 코드로 손쉽게 방에 입장합니다.\n* **계약서 동시 편집 (모바일)** : 팀원들이 실시간으로 목표 시간과 벌칙을 모바일 인터페이스에서 동시에 작성하고 수정할 수 있습니다.\n* **백그라운드 이탈 감지 타이머** : 모바일 기기의 OS 상태(AppState)를 추적하여 집중 시간 도중 앱 화면을 벗어나면 즉각적으로 서버에 이탈 신호를 전송하고 경고 알림을 띄웁니다.\n* **네이티브 벌칙 룰렛** : 세션 종료 시 남은 벌칙 횟수만큼 SVG와 네이티브 애니메이션으로 구현된 룰렛을 직접 돌려 벌칙을 확정합니다.\n* **클립보드 및 소셜 공유** : 방 초대 코드나 최종 집중 결과(리포트)를 모바일 OS의 네이티브 공유 기능을 통해 메신저로 즉시 전달할 수 있습니다.\n\n---\n\n## 🛠 기술 스택\n\n* **Core / Framework** : React Native, Expo, Expo Router\n* **Language** : TypeScript\n* **State Management** : Zustand (전역 상태), TanStack Query (서버 상태)\n* **Data Fetching \u0026 API** : Axios, Orval (OpenAPI 스펙 기반 API 클라이언트 자동 생성)\n* **Realtime Communication** : Socket.io-client, Yjs, y-websocket\n* **Styling** : NativeWind (Tailwind CSS)\n* **Native Features** : Expo SecureStore, Expo Notifications, React Native SVG\n\n---\n\n## 💡 주요 기술적 성과 및 구현 포인트\n\n* **모바일 환경의 CRDT 실시간 동시 편집 (Yjs + Polyfills)** React Native의 JavaScriptCore/Hermes 런타임에는 Node.js나 Web 브라우저의 기본 API가 내장되어 있지 않아 Yjs 구동 시 크래시가 발생합니다. 이를 해결하기 위해 `expo-crypto`와 `fast-text-encoding`을 활용하여 모바일 환경에 맞는 **Polyfill을 전역 주입**하고, 모바일 키보드 타이핑에 최적화된 동시 편집 UI를 구현했습니다.\n* **OS 라이프사이클 기반의 정밀한 이탈 감지 (AppState)**\n사용자가 집중 세션 중 홈 버튼을 누르거나 다른 앱으로 전환하는 것을 잡아내기 위해 React Native의 `AppState`를 활용했습니다. 앱이 `inactive`나 `background`로 전환되는 즉시 Socket으로 이탈 시작(`escape:start`) 패킷을 쏘고, 로컬 푸시 알림을 예약하여 사용자의 빠른 복귀를 유도했습니다.\n* **Orval을 활용한 API 계층 자동화 및 타입 안정성**\n백엔드의 OpenAPI 스펙 문서를 `Orval`로 파싱하여 프론트엔드의 API 호출 함수와 DTO(Data Transfer Object) 타입을 100% 자동 생성(`src/api/generated`)했습니다. 이를 통해 백엔드 명세가 변경될 때마다 모바일 앱의 타입이 자동으로 동기화되어 런타임 에러를 원천 차단했습니다.\n* **SVG 기반의 커스텀 인터랙티브 룰렛 구현**\n서드파티 룰렛 라이브러리에 의존하지 않고, `react-native-svg`의 Path 아크(Arc) 연산과 React Native의 `Animated` API를 활용하여 룰렛을 직접 드로잉했습니다. 네이티브 드라이버(`useNativeDriver`)를 위임하여 렌더링 병목 없이 60fps의 부드러운 스핀 연출을 달성했습니다.\n* **안전한 토큰 관리 및 네트워크 재연결 아키텍처**\nJWT 인가 토큰과 방장의 임시 비밀번호를 일반 스토리지가 아닌 OS 레벨의 암호화 저장소(`Expo SecureStore`)에 보관하여 하이재킹을 방어했습니다. 또한 앱이 백그라운드에서 오랜 시간 홀딩되었다가 켜질 때 끊어진 웹소켓 세션을 자동으로 감지하고 재연결(Reconnection)하는 복구 로직을 설계했습니다.\n\n---\n\n## 📂 프로젝트 구조\n\n```text\nddt-mobile/\n├── app/                  # Expo Router 기반의 파일 기반 라우팅 화면 (auth, mypage, room 등)\n├── src/\n│   ├── api/              # Orval 자동 생성 API 클라이언트 및 Axios 인터셉터 설정\n│   ├── components/       # 도메인별(timer, contract) 및 공통(ui) 컴포넌트\n│   ├── contexts/         # Socket.io 및 현재 방(Room) 데이터 전역 Context\n│   ├── hooks/            # useYjsContract, usePreventBack 등 커스텀 훅\n│   ├── store/            # Zustand 전역 상태 관리 (useAuthStore, useRoomStore)\n│   └── lib/              # Polyfills, 토큰 암호화, 푸시 알림 등 유틸리티 함수\n└── assets/               # 아바타 이미지, 배경, 폰트 등 정적 리소스\n\n```\n\n---\n\n## 🚀 시작하기\n\n프로젝트 루트 경로에서 아래 가이드에 따라 앱을 실행할 수 있습니다.\n\n**1. 패키지 설치**\n\n```bash\n# pnpm을 사용하여 전체 의존성을 설치합니다.\npnpm install\n\n```\n\n**2. 환경 변수 설정**\n루트 디렉토리에 `.env` 파일을 생성하고 아래 환경 변수를 기입합니다. (참고: `.env.example`)\n\n```env\nEXPO_PUBLIC_API_URL=http://your-backend-api-url.com\n\n```\n\n**3. 개발 서버 실행**\n\n```bash\n# Expo 개발 서버를 구동합니다.\npnpm start\n# 또는\nnpx expo start\n\n```\n\n* 실행 후 터미널에 나타나는 QR 코드를 **Expo Go** 앱(Android/iOS)으로 스캔하여 실기기에서 테스트할 수 있습니다.\n* 네이티브 빌드가 필요한 경우 `pnpm run android` 또는 `pnpm run ios` 명령어를 사용하세요.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fprgrms-fullcycle-devcourse%2Fwebfull_9_10_ddt_mobile","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fprgrms-fullcycle-devcourse%2Fwebfull_9_10_ddt_mobile","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fprgrms-fullcycle-devcourse%2Fwebfull_9_10_ddt_mobile/lists"}