Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
180ff2b
[Setting] LIVD-411 - 폰트 에셋 및 dio·이미지 의존성 추가
youz2me May 22, 2026
e2b1e16
[Feat] LIVD-411 - 디자인시스템 토큰·테마·공통 위젯 추가
youz2me May 22, 2026
0d140e4
[Feat] LIVD-411 - Dio 네트워킹 토대 추가
youz2me May 22, 2026
4653883
[Chore] LIVD-411 - 이관 계획 및 트러블슈팅 문서 추가
youz2me May 22, 2026
74439d9
[Feat] LIVD-411 - 공통 응답 래퍼 및 에러 매핑 추가
youz2me May 22, 2026
f2d2c5b
[Feat] LIVD-411 - 인증·유저 도메인 모델 추가
youz2me May 22, 2026
a0b0df5
[Feat] LIVD-411 - 토큰 영속화 및 갱신 인터셉터 추가
youz2me May 22, 2026
bc68398
[Feat] LIVD-411 - 인증·유저 Service 및 Provider 추가
youz2me May 22, 2026
6689be2
[Setting] LIVD-411 - API 베이스 URL을 staging 서버로 설정
youz2me May 22, 2026
07123e9
[Feat] LIVD-411 - 소셜/선호 Service 및 Service 인터페이스 추상화
youz2me May 22, 2026
edda9d9
[Feat] LIVD-411 - 인증 상태 ViewModel 추가
youz2me May 22, 2026
6374923
[Feat] LIVD-411 - go_router 라우팅 및 로그인 화면 추가
youz2me May 22, 2026
e113591
[Feat] LIVD-411 - 온보딩 흐름 및 선호 조회 추가
youz2me May 22, 2026
24b794a
[Feat] LIVD-411 - 콘서트 모델 및 Service 추가
youz2me May 22, 2026
386b720
[Feat] LIVD-411 - 홈 화면 및 ViewModel 추가
youz2me May 22, 2026
08ff61a
[Feat] LIVD-411 - 콘서트 검색 모델 및 Service 추가
youz2me May 22, 2026
757a9b7
[Feat] LIVD-411 - 탐색 화면 및 메인 탭 구조 추가
youz2me May 22, 2026
ae3dd70
[Feat] LIVD-411 - 셋리스트·가사 모델 및 Service 추가
youz2me May 22, 2026
f67a3a8
[Feat] LIVD-411 - 콘서트 상세·가사 화면 및 카드 진입 추가
youz2me May 22, 2026
e80cd58
[Feat] LIVD-411 - 마이·설정·닉네임수정 화면 추가
youz2me May 22, 2026
cac1915
[Feat] LIVD-411 - 분석·알림 연동 인터페이스(stub) 추가
youz2me May 22, 2026
67e2fae
[Chore] LIVD-411 - 마일스톤 계획 및 가정 문서 추가
youz2me May 22, 2026
6740931
[Style] LIVD-411 - Livith 로고 에셋 적용
youz2me May 23, 2026
27c134b
[Style] LIVD-411 - 탭바 아이콘 SVG 에셋 적용
youz2me May 23, 2026
b254006
[Feat] LIVD-411 - 콘서트 커뮤니티(댓글) 탭 추가
youz2me May 23, 2026
f42891e
[Feat] LIVD-411 - 알림 설정 화면 추가
youz2me May 23, 2026
316cffa
[Feat] LIVD-411 - 콘서트 아티스트 상세 탭 추가
youz2me May 23, 2026
748432a
[Chore] LIVD-411 - 후속 작업 진행 상황 가정 문서 갱신
youz2me May 23, 2026
4ebf75d
[Fix] LIVD-411 - 실제 staging API 응답 키에 맞게 파싱 수정
youz2me May 23, 2026
c834866
[Fix] LIVD-411 - 검색 genre 파라미터를 장르명으로 수정
youz2me May 23, 2026
8289df9
[Setting] LIVD-411 - http 포스터 로딩 위해 cleartext 허용
youz2me May 23, 2026
097e083
[Chore] LIVD-411 - 개발용 토큰 주입 경로 및 트러블슈팅 기록
youz2me May 23, 2026
6c33858
[Feat] LIVD-411 - 카카오 로그인 SDK 실연동
youz2me May 23, 2026
95f30b3
[Fix] LIVD-411 - 카카오 redirect 핸들러 액티비티 클래스명 수정
youz2me May 25, 2026
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
10 changes: 10 additions & 0 deletions android/app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
import java.util.Properties

plugins {
id("com.android.application")
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id("dev.flutter.flutter-gradle-plugin")
}

val localProperties = Properties()
val localPropertiesFile = rootProject.file("local.properties")
if (localPropertiesFile.exists()) {
localPropertiesFile.inputStream().use { localProperties.load(it) }
}
val kakaoNativeAppKey: String = localProperties.getProperty("kakao.nativeAppKey") ?: ""

android {
namespace = "com.livith.livith"
compileSdk = flutter.compileSdkVersion
Expand All @@ -23,6 +32,7 @@ android {
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
manifestPlaceholders["kakaoNativeAppKey"] = kakaoNativeAppKey
}

buildTypes {
Expand Down
17 changes: 17 additions & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
<application
android:label="livith"
android:name="${applicationName}"
android:usesCleartextTraffic="true"
android:icon="@mipmap/ic_launcher">
<!-- 일부 공연 포스터가 http(kopis.or.kr)로 제공되어 cleartext를 허용한다. -->

<activity
android:name=".MainActivity"
android:exported="true"
Expand All @@ -25,6 +28,20 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- 카카오 로그인 리다이렉트 처리 (kakao_flutter_sdk_auth의 핸들러에 scheme 연결) -->
<activity
android:name="com.kakao.sdk.flutter.auth.AuthCodeHandlerActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="oauth"
android:scheme="kakao${kakaoNativeAppKey}" />
</intent-filter>
</activity>

<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
Expand Down
Binary file added assets/fonts/NotoSansKR-Bold.ttf
Binary file not shown.
Binary file added assets/fonts/NotoSansKR-Medium.ttf
Binary file not shown.
Binary file added assets/fonts/NotoSansKR-Regular.ttf
Binary file not shown.
Binary file added assets/fonts/NotoSansKR-SemiBold.ttf
Binary file not shown.
Binary file added assets/fonts/Pretendard-Black.otf
Binary file not shown.
Binary file added assets/fonts/Pretendard-Bold.otf
Binary file not shown.
Binary file added assets/fonts/Pretendard-ExtraBold.otf
Binary file not shown.
Binary file added assets/fonts/Pretendard-ExtraLight.otf
Binary file not shown.
Binary file added assets/fonts/Pretendard-Light.otf
Binary file not shown.
Binary file added assets/fonts/Pretendard-Medium.otf
Binary file not shown.
Binary file added assets/fonts/Pretendard-Regular.otf
Binary file not shown.
Binary file added assets/fonts/Pretendard-SemiBold.otf
Binary file not shown.
Binary file added assets/fonts/Pretendard-Thin.otf
Binary file not shown.
3 changes: 3 additions & 0 deletions assets/icons/home_disabled.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions assets/icons/home_enabled.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions assets/icons/my_disabled.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions assets/icons/my_enabled.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions assets/icons/search.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/images/2.0x/livith_logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/images/3.0x/livith_logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/images/livith_logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
36 changes: 36 additions & 0 deletions docs/plans/LIVD-411-assumptions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# LIVD-411 이관 중 임의 결정/가정 (M4~M8)

자율 진행 중 확실하지 않아 임의로 처리한 항목. 전체 완료 후 한 번에 확인받는다.

## API/JSON
- 서버 JSON 키는 iOS DTO 기준 camelCase로 가정(`posterUrl`, `startDate`, `daysLeft` 등). 실제 응답과 다르면 통합 시 매퍼 조정 필요.
- 페이지네이션(cursor/size)은 단순 1페이지 조회로 우선 구현하고 무한스크롤은 후속.

## UI
- iOS 커스텀 아이콘/이미지 에셋은 Material 아이콘·임시 텍스트로 대체. 정밀 에셋 교체는 별도.
- 화면별 세부 간격/모션은 iOS 근사치로 구현. 픽셀 단위 정합은 후속 QA.

## 범위
- 소셜 로그인: stub 유지(키 확보 후 교체).
- FCM/Amplitude/딥링크(M8): SDK 키·네이티브 설정 의존 → 인터페이스+stub로 흐름만.

## 후속 작업 진행(추가 완료)
- 아이콘 에셋: **로고·탭바 아이콘(SVG)** 교체 완료. 나머지(notice/back/check/caution 등)는 SVG/PNG 혼재로 후속.
- **콘서트 상세 4탭 완성**: 아티스트/정보/셋리스트/커뮤니티 모두 구현.
- **알림 설정 화면** 추가(마케팅 동의만 stub 연동, 나머지 토글 UI).

## 마일스톤별 축소/후속 처리(잔여)
- **M6 콘서트 상세**: 굿즈(MD), 콘서트 문화/팬팁 등 부가 정보 미구현.
- **M7 마이/설정**: 선호도(장르/아티스트) 수정, 공지사항은 후속.
- **M5 탐색**: 배너 캐러셀·정렬 옵션·필터 바텀시트는 생략, 키워드+장르 필터만.
- **M4 홈**: 홈 섹션 API(`/home/sections`) 대신 추천+관심 콘서트로 단순 구성.

## 검증 한계
- 소셜 로그인 stub이라 실제 로그인 후 화면(홈/탐색/상세/마이)은 staging API 응답이 있어야 통합 확인 가능. 현재는 빌드·렌더·라우팅·단위 테스트로 검증.
- JSON 키(예: `posterUrl`, `hasPreferredGenre`, `tempUserData`)는 iOS DTO 기준 추정 — 실제 응답과 대조 필요.

## 확인 필요(우선순위)
1. 실제 API JSON 키 스펙(특히 콘서트/셋리스트/유저 응답) 대조
2. 소셜 로그인 네이티브 키(카카오 앱키, 애플 설정) → SDK 실연동
3. FCM/Amplitude 키 → stub 교체
4. 후속 화면(아티스트/커뮤니티/알림설정 등) 진행 여부
102 changes: 102 additions & 0 deletions docs/plans/LIVD-411-ios-flutter-migration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# LIVD-411 iOS 프로젝트 플러터로 이관

## 배경
- Livith는 K-pop 콘서트 정보 플랫폼(콘서트 일정·셋리스트·가사번역·응원법·커뮤니티)으로, 현재 iOS(Swift/SwiftUI, Tuist 14개 모듈, ~473 파일)로 구현되어 있다.
- 이를 Flutter(Riverpod + MVVM)로 이관한다. iOS는 화면 74개, 도메인 엔티티 ~30개, API 엔드포인트 ~60개 규모이므로 단일 PR로 불가능하다.
- 따라서 전체를 마일스톤으로 분할하고, 본 문서는 **전체 로드맵 + 마일스톤 1(기반 + 디자인시스템)** 을 다룬다. 이후 마일스톤은 별도 plan 문서로 분리한다.

## 목표
- 전체 이관의 마일스톤 로드맵을 확정한다.
- **마일스톤 1 완료 시**: iOS의 디자인 토큰(색상/타이포)과 핵심 공통 위젯, 네트워킹/DI 토대가 Flutter에 구축되어, 이후 화면 이관이 이 토대 위에서 진행 가능한 상태가 된다.

## 전체 로드맵 (마일스톤)
| # | 마일스톤 | 범위 | 상태 |
|---|----------|------|------|
| **M1** | **기반 + 디자인시스템** | 색상/타이포/폰트, 공통 위젯, 네트워킹·DI 토대, 폴더 구조 | **본 문서** |
| M2 | 도메인 모델 + 네트워킹 | ~30 엔티티, API 클라이언트, ~60 엔드포인트, 토큰/인증 인터셉터, 로컬 저장소 | 예정 |
| M3 | 인증 / 온보딩 | 로그인(카카오/애플) → 약관 → 닉네임 → 선호 장르/아티스트 | 예정 |
| M4 | 홈 탭 | 홈, 관심 공연, 공지, 선호도 수정 | 예정 |
| M5 | 탐색 탭 | 탐색, 검색, 필터 바텀시트 | 예정 |
| M6 | 콘서트 상세 | 콘서트 상세(4탭), 셋리스트, 가사, 굿즈 | 예정 |
| M7 | 마이 탭 | 마이페이지, 설정, 알림설정, 회원탈퇴 | 예정 |
| M8 | 외부 연동 / 마무리 | FCM, Amplitude, 딥링크, 위젯 검토 | 예정 |

> M2~M8은 각각 시작 시점에 별도 plan 문서(`docs/plans/LIVD-XXX-*.md`)를 작성하고 확인받는다.

---

## 마일스톤 1 — 작업 항목

- [ ] **1. 폰트 에셋 도입**
- iOS 레포에서 Noto Sans KR 4종(Bold/SemiBold/Medium/Regular, ttf), Pretendard 9종(otf)을 `assets/fonts/`로 복사
- `pubspec.yaml`의 `fonts:` 섹션에 family `NotoSansKR`, `Pretendard` 등록
- [ ] **2. 색상 토큰** (`lib/core/theme/livith_colors.dart`)
- iOS `LivithColor` 12색을 `Color` 상수로 정의 (아래 표)
- [ ] **3. 타이포그래피** (`lib/core/theme/livith_typography.dart`)
- iOS `Notosans` 13스타일을 `TextStyle`로 정의 (size/weight/height/letterSpacing)
- [ ] **4. 앱 테마** (`lib/core/theme/livith_theme.dart`)
- 다크 기반 `ThemeData` 구성(배경 Black100), `app.dart`에 적용
- [ ] **5. 핵심 공통 위젯** (`lib/views/widgets/`) — iOS DesignSystem 대응
- 우선순위: 버튼 계열(LivithButton/ActionButton/TextButton), 카드(LivithCard), 칩(LivithChip), 네비게이션 헤더(LivithNavigationView), 세그먼트 탭바, 모달(LivithModal/DangerModal), 토스트, 비동기 이미지(AsyncImageView→cached_network_image), FlowLayout(자동 줄바꿈)
- 이번 마일스톤은 토큰+위젯 골격 우선. 화면별 특수 컴포넌트는 해당 화면 마일스톤에서 추가
- [ ] **6. 네트워킹 토대** (`lib/services/`, `lib/providers/`)
- Dio 기반 `ApiClient` Service + `Provider<ApiClient>` 노출
- 토큰 인터셉터/401 갱신 골격(실제 토큰 저장은 M2)
- **TDD 적용**: 요청 빌더/인터셉터 로직은 실패 테스트 → 구현
- [ ] **7. 폴더 구조 정비**
- `lib/core/theme/`, 위젯 디렉터리 정리, 기존 `home_screen.dart`의 임시 내용을 디자인시스템 미리보기로 대체(검증용)

### 색상 토큰 (iOS → Flutter)
| 토큰 | HEX | 토큰 | HEX |
|------|-----|------|-----|
| black100 | #14171B | black5 | #F2F4F6 |
| black90 | #222831 | white100 | #FFFFFF |
| black80 | #2F3745 | yellow30 | #FFFF97 |
| black50 | #808794 | yellow60 | #FFEB56 |
| black30 | #DBDCDF | caution100 | #E11936 |
| original | #CAD0FF | translation | #FFBAB4 |

### 타이포 스타일 (Noto Sans KR, kerning = size × −5%)
| 스타일 | weight | size | height(배) |
|--------|--------|------|-----------|
| title | Bold | 26 | 1.38 |
| head{Semibold/Medium/Regular} | SemiBold/Medium/Regular | 22 | 1.38 |
| body1Semibold | SemiBold | 18 | 1.38 |
| body2{Semibold/Medium/Regular} | - | 16 | 1.38 |
| body3{Semibold/Medium/Regular} | - | 15 | 1.38 |
| body4{Semibold/Medium/Regular} | - | 14 | 1.38 |
| caption1Bold/Semibold | Bold/SemiBold | 12 | 1.28 |
| caption1Regular | Regular | 12 | 1.18 |
| caption2{Semibold/Regular} | SemiBold/Regular | 10 | 1.18 |

## 영향 범위
- `pubspec.yaml` (의존성: dio, cached_network_image / fonts 섹션)
- `assets/fonts/` (신규)
- `lib/core/theme/` (신규: livith_colors / livith_typography / livith_theme)
- `lib/views/widgets/` (공통 위젯 다수)
- `lib/services/`, `lib/providers/` (ApiClient 토대)
- `lib/app.dart`, `lib/views/screens/home_screen.dart` (테마 적용/미리보기)
- `test/` (네트워킹 토대 단위 테스트)

## 기술 결정
| 결정 사항 | 선택지 | 결정 | 근거 |
|-----------|--------|------|------|
| HTTP 클라이언트 | dio / http | **dio** | 인터셉터·토큰 자동 갱신·취소 토큰 필요 (iOS의 인터셉터 구조 대응) |
| 이미지 로딩 | cached_network_image / 기타 | **cached_network_image** | iOS Kingfisher의 URL 캐싱 대응 |
| 폰트 도입 | 에셋 직접 포함 / google_fonts | **에셋 직접 포함** | iOS와 동일 ttf/otf 사용으로 렌더링 일치, Pretendard는 google_fonts 미제공 |
| 색상/타이포 표현 | 상수 클래스 / ThemeExtension | **상수 클래스 + ThemeData 병행** | iOS가 named token 직접 참조 방식이라 이행 단순. 추후 ThemeExtension 검토 |
| 라우팅 | Navigator / go_router | **go_router** (M3에서 확정) | 화면 74개·탭+스택 구조라 선언적 라우팅 유리. M1은 토대만, 실제 도입은 인증 마일스톤 |
| 테마 모드 | 다크 고정 / 라이트 지원 | **다크 고정** | iOS가 Black100 배경의 다크 단일 테마 |

## 주의 사항
- `Model`에 `flutter/material.dart`·`flutter_riverpod` import 금지. 색상/타이포는 `core/theme`(UI 레이어)에 두므로 무방.
- `Service`(ApiClient)에 `flutter_riverpod` import 금지 — Provider는 `lib/providers/`에서만.
- 네트워킹 토대는 **TDD 대상**: red → green → refactor 순서 준수. 디자인 토큰/순수 위젯은 위젯 테스트 선택.
- 폰트 라이선스(Noto Sans KR=OFL, Pretendard=OFL) 확인 — 재배포 가능. 라이선스 파일도 함께 포함.
- 마일스톤 1은 화면 동작이 아닌 "토대" 구축이므로, 검증은 디자인시스템 미리보기 화면으로 수행.
- 진행 중 실패/방향 전환 발생 시 `docs/troubleshooting/LIVD-411-*.md`에 즉시 기록.

## 검증 방법
- `flutter analyze` 무경고
- `flutter test` 통과 (네트워킹 토대 단위 테스트 포함)
- 에뮬레이터(Pixel 7 / API 36)에서 디자인시스템 미리보기 화면 실행 → 색상/타이포/공통 위젯이 iOS와 시각적으로 일치하는지 수동 확인
57 changes: 57 additions & 0 deletions docs/plans/LIVD-411-m2-domain-network.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# LIVD-411 마일스톤 2 — 도메인 모델 + 네트워킹

## 배경
- 마일스톤 1에서 디자인시스템과 `dio` 네트워킹 토대(`dioProvider`, `AuthInterceptor`, 인메모리 `TokenStore`)를 구축했다.
- 화면 이관(M3~)을 시작하려면 공통 네트워킹 인프라(응답 래퍼·에러 매핑·토큰 영속화)와 인증/유저 도메인이 먼저 필요하다.
- iOS는 도메인 엔티티 ~30개, API ~60개를 갖지만, 한 번에 전부 이식하면 사용되지 않는 코드가 대량 생긴다. 따라서 **공통 인프라 + 인증/유저 도메인까지만** M2에서 다루고, 나머지 도메인 모델·Service는 그것을 사용하는 화면 마일스톤에서 추가한다(YAGNI).

## 목표
- 모든 도메인 Service가 공유할 네트워킹 인프라(응답 디코딩, 에러 매핑, 토큰 영속/갱신)를 완성한다.
- 인증/온보딩(M3)에 필요한 인증·유저 도메인 모델과 Service를 이식한다.
- M2 완료 시 M3(로그인/온보딩) 화면을 ViewModel→Service→Model 흐름으로 구현할 수 있다.

## 작업 항목
- [ ] **1. 공통 응답/에러 모델**
- iOS `ServerResponse<T>`(status/message/data) 대응 `ApiResponse<T>` 디코딩 헬퍼
- 도메인 실패를 표현하는 `Failure` sealed class (네트워크/인증/서버/파싱)
- Dio 에러 → `Failure` 매핑 (TDD)
- [ ] **2. 토큰 영속화**
- `flutter_secure_storage` 기반 `SecureTokenStore`로 M1 `InMemoryTokenStore` 교체 (`tokenStoreProvider` override)
- 401 응답 시 refresh → 재요청하는 인터셉터 로직 (TDD)
- [ ] **3. 환경별 baseURL**
- `--dart-define`(`LIVITH_API_BASE_URL`) 기반 설정 정리, 실제 dev/prod URL 반영
- [ ] **4. 인증/유저 도메인 모델**
- `User`, `SocialProvider`, `TempUser`, `SignupInfo`, `Nickname` 등 불변 모델 + `fromJson` (TDD)
- [ ] **5. 인증/유저 Service**
- `AuthService`: apple/kakao 로그인, signup, logout, withdraw, 닉네임 중복확인
- `UserService`: `GET /users/me`, 닉네임 수정
- 응답 DTO → 도메인 Model 매핑 (TDD)
- [ ] **6. Provider 등록**
- `authServiceProvider`, `userServiceProvider`를 `lib/providers/`에 노출

## 영향 범위
- `pubspec.yaml` (flutter_secure_storage, 코드젠 도입 시 json_serializable/build_runner)
- `lib/models/` (인증/유저 도메인 모델)
- `lib/services/` (auth_service, user_service, secure_token_store, api_response, failure)
- `lib/providers/` (authServiceProvider, userServiceProvider, tokenStore override)
- `test/` (모델 fromJson, 에러 매핑, 토큰 갱신, Service 파싱 테스트)

## 기술 결정
| 결정 사항 | 선택지 | 결정 | 근거 |
|-----------|--------|------|------|
| JSON 직렬화 | 수동 fromJson / json_serializable | **수동 fromJson** | M2 범위 모델 수가 적고(인증/유저), 코드젠 의존성·빌드 단계를 미루는 편이 단순. 모델이 급증하는 화면 마일스톤에서 json_serializable 재검토 |
| 토큰 저장 | flutter_secure_storage / shared_preferences | **flutter_secure_storage** | iOS Keychain 대응, 토큰은 민감정보 |
| 도메인/DTO 분리 | 통합 / Mapper 분리 | **형식 다를 때만 분리** | architecture.md 준수. 응답 구조와 도메인이 같으면 단일 Model |
| 에러 표현 | Exception / sealed Failure | **sealed Failure** | code-convention의 Result/Either 지향과 정합, AsyncValue.error 매핑 용이 |

## 주의 사항
- `Model`에 `flutter/material.dart`·`dio`·`flutter_riverpod` import 금지.
- `Service`에 `flutter_riverpod` import 금지 (Provider 파일에서만).
- 토큰 갱신 인터셉터는 무한 재요청 방지(refresh 실패 시 로그아웃 처리) 로직 포함.
- Model `fromJson`, Mapper, 에러 매핑, 토큰 갱신은 **TDD 대상**. Dio 조립/Provider 배선은 예외.
- 실제 API 응답 키는 iOS DTO(`Projects/Data/*/Model`)를 근거로 맞춘다.

## 검증 방법
- `flutter analyze` 무경고
- `flutter test` 통과 (모델 fromJson, 에러 매핑, 토큰 갱신, Service 파싱 단위 테스트)
- M3 착수 시 실제 로그인 플로우로 통합 확인
Loading