diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 4dd60d8..0ce525c 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -1,372 +1,102 @@ -# File Sync Rule (CRITICAL) -- **This file (`.claude/CLAUDE.md`) and `.cursorrules` MUST stay in sync.** -- When modifying either file, apply the same changes to both files. -- **Flexible Sync**: Cursor와 Claude Code의 특성 차이로 인한 부분은 각 도구에 맞게 융통성 있게 적용: - - Cursor 전용: Custom Commands (`/doc`, `/test` 등), Rule Management 섹션 - - Claude Code 전용: 이모지 미사용, 간결한 톤 유지 - - 공통: 아키텍처, 코딩 표준, 테스트, Git 브랜치 전략 등 핵심 규칙 -- If you notice discrepancies in **core rules**, immediately synchronize them. - -# Role -You are a Senior Android Developer at a Hackathon. -Prioritize build speed and crash prevention. -If a solution is complex, suggest the simplest working alternative first. -When stuck, provide a working workaround before investigating the perfect solution. - -# Project Context & Tech Stack -- Project Type: Android Native App -- Language: Kotlin (Latest Stable) -- UI Framework: Jetpack Compose (Material 3) -- Build System: Gradle (Kotlin DSL / .kts) -- Dependency Management: Version Catalog (libs.versions.toml) - -# User Preferences -- Language: Korean (모든 설명과 주석은 한국어로 작성) -- Tone: Concise and Technical (간결하고 전문적으로) - -# Architecture Guidelines -- Pattern: MVVM (Model-View-ViewModel) - - **Model**: Entity, Repository (Domain + Data layers) - - **View**: Compose Screen (Presentation layer) - - **ViewModel**: State management and business logic - - **Rule**: Logic goes to ViewModel, not UI. UI only observes StateFlow. -- Architecture: Clean Architecture (Presentation -> Domain -> Data) - - **data**: Data sources (API, Local DB) - - **domain**: Business logic (Entities, Repository interfaces) - - **presentation**: UI layer (Screens, ViewModels) -- State Management: Unidirectional Data Flow (UDF) using StateFlow/SharedFlow -- DI: Hilt (Dagger) -- Async: Coroutines & Flow - - Use `viewModelScope` in ViewModels. - - Use `suspend` functions for I/O operations. - -# Key Libraries & Versions -- Image Loading: Coil 3.x (Note: Use `coil3` package, NOT `coil` package) - - Import: `import coil3.compose.AsyncImage` - - Do NOT use: `import coil.compose.AsyncImage` (Coil 2, deprecated) -- Networking: Retrofit2 + OkHttp + Kotlinx Serialization -- Serialization: Kotlin Serialization (not Gson) - -## Do NOT Use (금지 라이브러리) -- **Gson**: Use Kotlin Serialization instead -- **Kotlin Synthetics**: Deprecated, use View Binding or Compose -- **AsyncTask**: Deprecated, use Coroutines -- **RxJava**: Use Coroutines & Flow instead (simpler for hackathon) -- **Coil 2.x (`coil` package)**: Use Coil 3.x (`coil3` package) -- **LiveData in new code**: Use StateFlow/SharedFlow (UDF pattern) - -# Coding Standards - -## Compose UI -1. Always use `@Composable` annotation properly. -2. Use `Modifier` as the first optional parameter in Composable functions. -3. Separate UI state from UI implementation (State Hoisting). -4. For Experimental Material3 APIs (TopAppBar, Scaffold topBar, etc.), always add: - ```kotlin - @OptIn(ExperimentalMaterial3Api::class) - @Composable - fun MyScreen(...) { ... } - ``` -5. **File Structure Rule (CRITICAL)**: - - **One Composable function per file**: Each Composable file MUST contain only ONE `@Composable` function (plus its preview). - - **Preview is mandatory**: Every Composable file MUST have exactly ONE `@Preview` function. - - **File splitting**: If a file exceeds ~300 lines or contains multiple logical sections, split it into separate component files. - - **Component extraction**: Extract reusable UI sections into separate component files in the `component/` directory. - - **File naming**: Component files should be named descriptively (e.g., `ImageUploadSection.kt`, `HashTagInputSection.kt`). - - **Example structure**: - ```kotlin - // MyScreen.kt - ONLY contains MyScreen function and its preview - @Composable - fun MyScreen(...) { ... } - - @Preview(showBackground = true) - @Composable - private fun MyScreenPreview() { ... } - ``` -6. **KtLint Compliance (CRITICAL - Prevents Build Failures)**: - - **Max Line Length**: Keep lines under 120 characters. If a line exceeds this, break it into multiple lines. - - **Import Order**: Group imports: 1) Kotlin stdlib, 2) Android/AndroidX, 3) Third-party, 4) Project. Separate groups with blank lines. - - **Indentation**: Use 4 spaces (not tabs). - - **Trailing Commas**: Use trailing commas in multi-line parameter lists, function calls, and data classes. - - **Spacing Rules**: - - No space before `:` in type declarations: `val name: String` (not `val name : String`) - - Space after `:` in when expressions: `when (x) { ... }` - - Space around operators: `a + b` (not `a+b`) - - **Function Formatting**: If parameters exceed line length, put each on a new line with proper indentation and trailing commas. - - **String Formatting**: Prefer string templates: `"Hello $name"` (not `"Hello " + name`) - - **Before Any Code Change**: Always ensure code follows KtLint rules. If unsure, break long lines and use proper spacing. -7. **Performance Optimization**: - - Use `remember` and `derivedStateOf` to avoid unnecessary recompositions. - - Always use standard modifiers ordering: `Modifier.size().padding().background().clickable()`. - - Avoid hardcoding values; use `MaterialTheme.typography` and `MaterialTheme.colorScheme`. - - Prefer `LazyColumn` over `Column` for lists to ensure recycling. -8. **State Collection**: - - Use `collectAsStateWithLifecycle()` instead of `collectAsState()` when observing StateFlow in Compose screens. - - Import: `import androidx.lifecycle.compose.collectAsStateWithLifecycle` - -## Kotlin Conventions -1. Prefer `val` over `var`. -2. Use `when` expressions for exhaustiveness. -3. Avoid `!!` (double-bang) operator; use safe calls (`?.`) or `let`. -4. **KtLint Pre-Compliance (WRITE CODE CORRECTLY FROM THE START)**: - - **NEVER write code that violates KtLint rules. Check BEFORE writing, not after.** - - **Comments in parameter lists**: ALWAYS put comments on a SEPARATE LINE above the parameter, never inline. - ```kotlin - // Wrong - causes build failure - fun example( - param: String, // this is a comment - ) - - // Correct - fun example( - // this is a comment - param: String, - ) - ``` -5. **KtLint Compliance (CRITICAL - Prevents Build Failures)**: - - **IMPORTANT**: KtLint violations cause build failures. Always follow these rules when writing code. - - **Max Line Length**: Keep lines under 120 characters. If a line exceeds this, break it into multiple lines: - ```kotlin - // Wrong (too long) - val result = repository.getData().fold(onSuccess = { data -> updateState(data) }, onFailure = { error -> showError(error) }) - - // Correct (broken into multiple lines) - val result = repository.getData().fold( - onSuccess = { data -> updateState(data) }, - onFailure = { error -> showError(error) }, - ) - ``` - - **Import Order**: Group imports: 1) Kotlin stdlib, 2) Android/AndroidX, 3) Third-party, 4) Project. Separate groups with blank lines. - - **Indentation**: Use 4 spaces (not tabs). - - **Trailing Commas**: Always use trailing commas in multi-line parameter lists, function calls, and data classes. - - **Spacing Rules**: - - No space before `:` in type declarations: `val name: String` (not `val name : String`) - - Space after `:` in when expressions: `when (x) { ... }` - - Space around operators: `a + b` (not `a+b`) - - **Function Formatting**: If parameters exceed line length, put each on a new line with proper indentation and trailing commas. - - **String Formatting**: Prefer string templates: `"Hello $name"` (not `"Hello " + name`) - - **Code Writing**: When writing code, proactively check line length and break long lines before they cause build failures. -6. **Smart Cast for Delegated Properties**: - - Do NOT rely on smart casts for delegated properties (e.g., `by collectAsState()`, `by viewModels()`). - - Always assign to a local variable (snapshot) before checking nullability: - ```kotlin - // Wrong - if (uiState.error != null) { - Text(text = uiState.error) // Smart cast impossible - } - - // Correct - val errorMessage = uiState.error ?: "오류가 발생했습니다" - if (errorMessage != null) { - Text(text = errorMessage) - } - // OR - uiState.error?.let { error -> - Text(text = error) - } - ``` -7. **Interface Override Rule (CRITICAL - Prevents Compilation Errors)**: - - **NEVER** specify default parameter values in override functions. - - Default values should ONLY be in the interface/abstract class, NOT in the implementation. - ```kotlin - // Wrong - causes compilation error - interface MyRepository { - suspend fun getData(page: Int = 1): Result> - } - class MyRepositoryImpl : MyRepository { - override suspend fun getData(page: Int = 1): Result> { // ERROR! - // ... - } - } - - // Correct - default value only in interface - interface MyRepository { - suspend fun getData(page: Int = 1): Result> - } - class MyRepositoryImpl : MyRepository { - override suspend fun getData(page: Int): Result> { // No default value - // ... - } - } - ``` - - **Always check**: When overriding interface functions, remove ALL default parameter values from the override function signature. -8. Follow [Kotlin Official Coding Conventions](https://kotlinlang.org/docs/coding-conventions.html). -9. Use Android Studio auto-formatting: `Ctrl + Alt + L` (Windows) before committing. - -## Naming Conventions -- **File/Class names**: PascalCase (e.g., `HomeScreen.kt`, `CombinationCard.kt`, `HomeViewModel`) -- **Variable names**: camelCase (e.g., `selectedCategory`, `isLoading`) -- **Function names**: camelCase (e.g., `loadCombinations()`, `updateSearchQuery()`) -- **Constants**: UPPER_SNAKE_CASE (e.g., `BASE_URL`, `MAX_RETRY_COUNT`) - -## Dependency Management -1. Do NOT hardcode versions in `build.gradle.kts`. -2. Add new libraries to `gradle/libs.versions.toml` first: - ```toml - [versions] - myLibrary = "1.0.0" - - [libraries] - my-library = { group = "com.example", name = "mylib", version.ref = "myLibrary" } - ``` -3. Then reference them in `build.gradle.kts` using `libs.my.library`. - -## File Naming & Structure -- Screen files: `*Screen.kt` (e.g., `HomeScreen.kt`, `DetailScreen.kt`) -- ViewModel files: `*ViewModel.kt` (e.g., `HomeViewModel.kt`) -- Component files: `*Card.kt`, `*Chip.kt`, etc. -- Use descriptive names, avoid abbreviations like "Diary" for "Detail". - -## Package Structure (Clean Architecture) -``` -com.example.hackathon/ -├── data/ # 데이터 레이어 -│ ├── dto/ # Request/Response 모델 -│ ├── mapper/ # Entity <-> DTO 변환 -│ ├── repositoryimpl/ # Repository 구현 -│ └── service/ # API 인터페이스 -├── domain/ # 도메인 레이어 -│ ├── entity/ # 비즈니스 엔티티 -│ └── repository/ # Repository 인터페이스 -├── presentation/ # 프레젠테이션 레이어 -│ ├── navigation/ # 네비게이션 -│ ├── route/ # 라우트 정의 -│ ├── screen/ # 화면 컴포저블 -│ └── viewmodel/ # ViewModel -├── core/ # 공통 -│ └── component/ # 공통 컴포넌트 -├── di/ # 의존성 주입 -└── ui/theme/ # 테마 설정 +# Android Project Conventions + +## Project Context +- 모듈 구조: **멀티모듈** — `:app` + `:core:*`(model·designsystem·datastore·network·domain·data) + `:feature:*`(auth·home·combination·onboarding·profile) + `build-logic` 컨벤션 플러그인. Clean Architecture + MVVM. +- 패키지: `com.example.hackathon`. +- minSdk / targetSdk / compileSdk: 모두 **36** (JVM 17). 정의 위치: `app/build.gradle.kts`. +- 빌드 툴체인: AGP 9.2.1 + Kotlin 2.3.21 + KSP 2.3.9, Compose BOM 2026.05.01. Version Catalog(`gradle/libs.versions.toml`) 강제. +- 정적 분석: **ktlint** — CI `.github/workflows/lint.yml` 의 ScaCap/action-ktlint 1.8.0, `.editorconfig` 의 `ktlint_code_style=android_studio` (call-site trailing comma 미사용). + Android Lint(`./gradlew lintDebug`). (`config/detekt*` 은 미사용 잔재 — `detekt` Gradle 태스크 없음.) +- 핵심 도메인: **음식 조합·레시피 공유 앱** — 사용자가 카테고리(하이디라오·서브웨이·편의점)별로 재료·사진·해시태그를 담은 '조합'을 등록·탐색·검색하고 좋아요·랭킹으로 큐레이션. 인증·온보딩·프로필 포함. 서버 연동 전까지 `BuildConfig.USE_MOCK_API` 로 Mock/실 API 토글, `BuildConfig.BASE_URL` 로 엔드포인트 주입. + +## 작업 시작/마무리 규약 (위반 금지) +- **이슈 우선 + 브랜치 링크**: 새 브랜치는 메타데이터 완비된 GitHub 이슈에 정식 링크된 상태여야 한다. 절차 자동화는 `android-issue-branch` skill 에 위임 — 사용자가 "브랜치 파"라고만 해도 skill 진입 후 이슈부터 작성. `git checkout -b` 직행은 `git-branch-guard.sh` 가 차단. +- **자율 커밋 금지**: 변경사항을 임의로 `git commit` 하지 말 것. 빌드/lint/test 검증 결과만 보고하고 멈춤. 사용자가 Android Studio 커밋 탭에서 직접 검토·커밋한다. +- **git state 변경은 명시 지시 시에만**: `git push --force`, `git reset --hard`, `git rebase`, `git clean -f`, `git checkout -- `, `git restore ` 등 손실 위험 동작은 사용자의 명시 지시가 있을 때만 수행. (일반 `git push` 는 `/create-pr` 같은 명시 워크플로에서 허용.) +- 위 규약은 hook으로 강제(`.claude/settings.local.json` + `.claude/hooks/`). 우회·예외 처리 시도 금지. + +## 작업 원칙 +- 기억으로 답하지 말 것. 라이브러리 좌표·버전·API 시그니처는 매번 검증. +- 검증 우선순위: + 1. developer.android.com (Architecture Guide, Library 공식 문서) + 2. AndroidX 릴리즈 노트 (`developer.android.com/jetpack/androidx/releases/*`) + 3. mvnrepository.com / androidx.tech (최신 Stable Maven 좌표) + 4. 필요 시 android.googlesource.com / AndroidX GitHub (시그니처·소스) +- 핵심 문서는 `web_fetch`로 원문 확인. 답변·커밋 메시지에 출처 URL 명시. +- 공식 문서와 다른 판단을 내릴 땐 근거와 트레이드오프를 명시한 뒤 진행. + +## Architecture (Google 'Guide to app architecture'만) +- Layer: **UI → Domain(선택) → Data** +- SSOT: 각 데이터 타입은 단일 소스에서만 흐름. +- UDF: 상태는 위→아래, 이벤트는 아래→위. +- Data Layer 진입점은 **Repository**로 한정. ViewModel/UseCase는 DataSource(네트워크/DB/센서)에 직접 의존 금지. +- **금지 용어/패턴**: Hexagonal Architecture, Ports & Adapters, Port, Interactor 등 안드로이드 비표준 Clean Architecture 용어. + +### ViewModel ↔ Repository ↔ UseCase +- ViewModel은 Repository를 직접 주입받아 호출. +- Repository를 1:1로 감싸는 프록시 UseCase는 **만들지 말 것**. +- UseCase는 다음 중 하나일 때만 도입: + 1. 여러 Repository를 조합하는 비즈니스 로직 + 2. 여러 ViewModel에서 재사용되는 로직 + 3. ViewModel 복잡도가 임계치를 넘었을 때 +- UseCase 네이밍: `동사(현재형) + 명사 + UseCase` + 예: `GetLatestNewsWithAuthorsUseCase`, `LogOutUserUseCase`, `FormatDateUseCase` +- **Stateless** — UseCase 클래스 멤버 필드로 mutable 데이터(`var`, `MutableStateFlow`, mutable collection 등) 보유 금지. mutable 상태는 UI / Data 레이어가 보유. 공식 가이드: *"각 사용 사례에서는 기능 하나만 담당해야 하고, 변경 가능한 데이터를 포함해서는 안 됩니다"*. +- **Main-safe** — UseCase 는 main thread 에서 호출돼도 안전해야 함. 차단 작업(파일 I/O, 무거운 계산 등)은 `withContext(defaultDispatcher) { ... }` 로 background 이동. Repository 호출이 이미 suspend 면 그대로 위임 가능. +- **UseCase → UseCase 호출 허용** — 재사용 단위라 다른 UseCase 를 종속 항목으로 받아 호출 가능 (예: `GetLatestNewsWithAuthorsUseCase(... formatDateUseCase)`). 다층 도메인 정상. +- **데이터 레이어 캐싱 우선** — 복잡한 계산이라도 도메인으로 무조건 빼지 말 것. *재사용·캐싱이 더 자연스러우면 Repository / DataSource 에 두는 게 우선*. 공식 가이드: *"복잡한 계산은 재사용이나 캐싱을 유도하기 위해 데이터 레이어에서 이루어집니다"*. +- 출처: https://developer.android.com/topic/architecture/domain-layer?hl=ko + +## UI Layer +- **한 화면당 단일 UI State 객체** (data class 또는 sealed class). loading/error/data 독립 스트림 분리 금지. +- 상태 노출은 `StateFlow`. `MutableStateFlow`는 반드시 `private` 캡슐화. +- 상태 수집은 `collectAsStateWithLifecycle()`. `collectAsState()` 신규 사용 금지. +- 신규 화면은 `@Composable` destination. Fragment 신규 생성은 원칙적 지양(불가피한 경우만 사유 명시 후 허용). +- 일회성 이벤트도 UI state 에 흡수 (Google 공식 권고 — "ViewModel events should always result in a UI state update"). `UiState` 의 nullable 필드(`userMessage`·`navigateTo` 등) + 화면이 소비 후 VM `onConsumed()` 콜백으로 null 처리. `Channel`/`SharedFlow` 신규 도입 금지. + +## 필수 라이브러리 +- DI: **Hilt** (`@HiltViewModel`). 수동 `ViewModelProvider.Factory`·Service Locator 금지. +- Navigation: **Compose Navigation** (Navigation 3 우선 검토), type-safe routes. +- 비동기: **Coroutines + Flow**. +- 직렬화: **kotlinx-serialization** (Gson 금지). +- 네트워킹: **Retrofit + KotlinxSerializationConverterFactory + OkHttp**. +- 이미지 로딩: **Coil 3.x** — `coil3` 패키지 사용 (`import coil3.compose.AsyncImage`). Coil 2.x(`coil` 패키지) 금지. +- 로컬 저장소: **DataStore (Preferences)**. 관계형 DB 필요 시 Room. +- 어노테이션 처리: **KSP only**. + +## 신규 도입 금지(구버전·비표준 차단) +- LiveData → `StateFlow` / `SharedFlow` +- kapt → KSP +- findViewById / XML UI → Jetpack Compose +- AsyncTask, RxJava → Coroutines + Flow +- Deprecated Fragment 인자 전달 → `by navArgs()` 또는 `SavedStateHandle` +- GsonConverterFactory → KotlinxSerializationConverterFactory +- Coil 2.x(`coil` 패키지) → Coil 3.x(`coil3` 패키지) +- `collectAsState()` → `collectAsStateWithLifecycle()` +- Manual `ViewModelProvider.Factory` → `@HiltViewModel` +- Bare annotation on constructor `val` (`@StringRes val x: Int`) → use-site target 명시 (`@param:StringRes val x: Int`). KT-73255 deprecation warning 회피, 본 repo 컨벤션은 param-only. + +## 의존성 작성 규칙 +- 모든 의존성은 `libs.versions.toml`에 등록 후 모듈 `build.gradle.kts`에서 alias로 참조. +- 라이브러리 추가/업데이트 시 릴리즈 노트 URL을 PR 설명에 첨부. +- BOM이 존재하는 라이브러리(Compose·Firebase 등)는 BOM 우선 사용. + +## 출력 형식 +- Kotlin only. 불필요한 주석·서론·맺음말 배제. +- 코드 외 설명은 핵심 의도와 트레이드오프만 간결히. +- 라이브러리 버전 언급 시 Maven 좌표 + 출처 URL 동봉. +- 정당한 예외(레거시 통합 등)는 완화 사유를 먼저 설명한 뒤 진행. + +## 빌드 / 테스트 명령 +```bash +./gradlew assembleDebug +./gradlew testDebugUnitTest # 또는 ./gradlew :app:testDebugUnitTest +./gradlew lintDebug # Android Lint (CI lint.yml). ktlint 은 CI(ScaCap/action-ktlint)로 실행 — 로컬 Gradle 태스크 없음 +./gradlew :app:connectedDebugAndroidTest # 인스트루멘티드 테스트 ``` -## Error Handling -- Wrap network calls in `runCatching` or `try-catch` blocks within Repository. -- Use `Result` type for repository methods: - ```kotlin - suspend fun getData(): Result> { - return runCatching { - // API call - } - } - ``` -- Expose UI states as data classes with nullable error field: - ```kotlin - data class UiState( - val data: List = emptyList(), - val isLoading: Boolean = false, - val error: String? = null - ) - ``` -- Handle errors in ViewModel using `fold()`: - ```kotlin - repository.getData().fold( - onSuccess = { data -> /* update state */ }, - onFailure = { error -> /* set error state */ } - ) - ``` - -## Documentation -- Write all comments and KDoc in Korean (한국어). -- Explain 'Why' we did this, not just 'What' the code does. -- Add KDoc for complex business logic. -- Keep function names descriptive. -- Add `// 담당자: 이름` comment at the top of files for hackathon collaboration. -- Add `// TODO: 내용` for future improvements. - -## Testing Guidelines -- **Testing Strategy**: Focus on ViewModel unit tests (business logic validation). - - ViewModel tests are fast and catch most bugs. - - UI tests are optional for hackathon (time-consuming). -- **Testing Libraries**: - - **MockK**: Mocking dependencies (`io.mockk`) - - **Turbine**: Flow testing (`app.cash.turbine`) - - **Kotlinx Coroutines Test**: Coroutine testing (`kotlinx.coroutines.test`) -- **Test Structure**: - - Test files: `*Test.kt` (e.g., `CreateCombinationViewModelTest.kt`) - - Test utilities: `util/` package (e.g., `MainDispatcherRule.kt`) - - Use `@get:Rule val mainDispatcherRule = MainDispatcherRule()` for coroutine tests. -- **Test Naming**: Use descriptive test names with backticks: - ```kotlin - @Test - fun `updateTitle updates the title in uiState`() = runTest { ... } - ``` -- **Test Execution**: - - **Automatic**: Tests run automatically on commit (Git Hook) and code quality checks. - - **Manual**: `./gradlew test` or `.\scripts\run-tests.ps1` - - **Specific test**: `./gradlew test --tests "*ViewModelTest"` -- **Test Best Practices**: - - Mock all external dependencies (Repository, API services). - - Test state changes using Turbine: `viewModel.uiState.test { ... }` - - Use `runTest` for coroutine-based tests. - - Test both success and failure scenarios. - - Keep tests fast and isolated (no shared state). - -# Project-Specific Rules -- This is a hackathon project (2 days prep + 1 day event). -- Focus on working code over perfect code. -- Use dummy data when server API is not ready. -- All team members work on both design and development. - -## Troubleshooting: File Lock Issues (Android Studio + Cursor) -- **Problem**: `R.jar: The process cannot access the file because it is being used by another process` -- **Cause**: Cursor's file indexing conflicts with Android Studio's Gradle build process. -- **Solution**: - 1. **Primary**: `.cursorignore` file already excludes `build/` folders (prevents indexing). - 2. **If still occurs**: Run `./gradlew --stop` (or `.\gradlew.bat --stop` on Windows) to gracefully stop Gradle daemon. - 3. **Alternative**: Use Android Studio's `Build > Clean Project` menu. - 4. **Windows Defender**: Consider excluding project folder from real-time scanning (Settings > Virus & threat protection > Exclusions). -- **Never**: Force-kill JDK processes via Task Manager (risks data corruption). - -## Hackathon Mindset -- **Working code first**: `// TODO: Refactor later for hackathon` 주석을 적극 허용 -- **Speed over perfection**: 완벽한 클린 코드보다 작동하는 코드 우선 -- **Quick workarounds**: 복잡한 해결책보다 간단한 우회책 먼저 제시 - -## Figma AI 프롬프트 작성 규칙 -- **중요**: Figma AI는 프로젝트 컨텍스트를 참조할 수 없습니다. - - 팀원 이름, 기존 디자인 스타일, 프로젝트 히스토리 등을 모릅니다. - - 프롬프트에 모든 필요한 정보를 명시적으로 포함해야 합니다. -- **프롬프트 작성 시 필수 포함 사항**: - - 디자인 시스템 (색상, 타이포그래피, 간격 등)을 완전히 명시 - - 화면 구성 요소를 구체적으로 설명 (위치, 크기, 스타일) - - 인터랙션 및 상태 관리 방법 설명 - - 기존 화면과의 일관성을 위한 스타일 가이드 포함 - - 컨텍스트 없이도 이해할 수 있도록 독립적으로 작성 -- **금지 사항**: - - "예원이 스타일", "기존 디자인 참고" 등 컨텍스트 의존적 표현 사용 금지 - - 팀원 이름이나 프로젝트 히스토리 언급 금지 - - 다른 파일이나 디자인 참조 없이 독립적으로 이해 가능하도록 작성 - -# Git Branch Strategy -- **CRITICAL**: Always work on your own branch, NOT on main branch. -- **Branch naming**: Use your name as branch name (e.g., `ilhyuk`, `yewon`). -- **Role-based branches**: - - **ilhyuk (일혁)**: - - CreateCombinationScreen (조합 등록 화면) - - MyScreen (마이페이지) - - Related ViewModels (CreateCombinationViewModel, MyPageViewModel) - - **yewon (예원)**: - - HomeScreen (홈 화면) - - DetailScreen (조합 상세 화면) - - Related ViewModels (HomeViewModel, DetailViewModel) -- **MANDATORY: Check current branch before starting work**: - - **ALWAYS** check current branch with `git branch --show-current` or `git status` before making any code changes. - - If you're working on ilhyuk's tasks (CreateCombinationScreen, MyScreen), you MUST be on `ilhyuk` branch. - - If you're working on yewon's tasks (HomeScreen, DetailScreen), you MUST be on `yewon` branch. - - If on wrong branch, switch to correct branch first: `git checkout ilhyuk` or `git checkout yewon`. - - **NEVER** work on `main` branch for personal tasks. -- **Workflow**: - 1. **FIRST**: Check current branch with `git branch --show-current` - 2. If wrong branch, switch: `git checkout ilhyuk` (or your branch name) - 3. Pull latest changes: `git pull origin main` (if needed) - 4. Make changes on your branch - 5. Commit and push to your branch: `git push origin ilhyuk` - 6. Merge to main only after review/approval -- **NEVER commit directly to main branch** for personal work. -- **Common code** (shared components, theme, navigation) can go to main after team discussion. -- **Commit Policy (CRITICAL)**: - - **Documentation files only**: Only commit documentation files (`.md`, `.txt`, `.cursorrules`, etc.) automatically without user approval. - - **Documentation merge to main**: After committing documentation files, merge them to remote main branch automatically. - - **Code files**: NEVER commit code changes (`.kt`, `.kts`, `.xml`, etc.) without explicit user approval. - - **Always ask**: Before committing any non-documentation files, always ask the user for permission first. - - **Exception**: Documentation updates can be committed and merged to main automatically, but code changes require explicit confirmation. +## 코드 변경 시 체크리스트 +- [ ] 새 라이브러리 좌표·버전을 검색으로 검증했는가 +- [ ] `libs.versions.toml`에 등록했는가 +- [ ] UI 상태가 단일 객체 + `StateFlow` + `collectAsStateWithLifecycle()` 패턴인가 +- [ ] Repository를 우회해 DataSource에 직접 의존하지 않는가 +- [ ] 새로 만든 UseCase가 위 3가지 조건 중 하나를 충족하는가 diff --git a/.claude/backup/CLAUDE.md b/.claude/backup/CLAUDE.md index 6e568db..0ce525c 100644 --- a/.claude/backup/CLAUDE.md +++ b/.claude/backup/CLAUDE.md @@ -1,12 +1,12 @@ # Android Project Conventions ## Project Context -- 모듈 구조: **단일 `:app` 모듈**. Clean Architecture 를 패키지로 구성 — `data` / `domain` / `presentation` (+ `di`, `core`, `ui/theme`). 멀티모듈·`build-logic` 컨벤션 플러그인 없음. +- 모듈 구조: **멀티모듈** — `:app` + `:core:*`(model·designsystem·datastore·network·domain·data) + `:feature:*`(auth·home·combination·onboarding·profile) + `build-logic` 컨벤션 플러그인. Clean Architecture + MVVM. - 패키지: `com.example.hackathon`. - minSdk / targetSdk / compileSdk: 모두 **36** (JVM 17). 정의 위치: `app/build.gradle.kts`. -- 빌드 툴체인: AGP 8.13.2 + Kotlin 2.0.21 + KSP, Compose BOM 2024.09.00. Version Catalog(`gradle/libs.versions.toml`) 강제. -- 정적 분석: **Detekt** (`config/detekt.yml`, baseline `config/detekt-baseline.xml`). ktlint 아님. -- 핵심 도메인: 해커톤용 Android 앱. 서버 연동 전까지 `BuildConfig.USE_MOCK_API` 로 Mock/실 API 토글, `BuildConfig.BASE_URL` 로 엔드포인트 주입. +- 빌드 툴체인: AGP 9.2.1 + Kotlin 2.3.21 + KSP 2.3.9, Compose BOM 2026.05.01. Version Catalog(`gradle/libs.versions.toml`) 강제. +- 정적 분석: **ktlint** — CI `.github/workflows/lint.yml` 의 ScaCap/action-ktlint 1.8.0, `.editorconfig` 의 `ktlint_code_style=android_studio` (call-site trailing comma 미사용). + Android Lint(`./gradlew lintDebug`). (`config/detekt*` 은 미사용 잔재 — `detekt` Gradle 태스크 없음.) +- 핵심 도메인: **음식 조합·레시피 공유 앱** — 사용자가 카테고리(하이디라오·서브웨이·편의점)별로 재료·사진·해시태그를 담은 '조합'을 등록·탐색·검색하고 좋아요·랭킹으로 큐레이션. 인증·온보딩·프로필 포함. 서버 연동 전까지 `BuildConfig.USE_MOCK_API` 로 Mock/실 API 토글, `BuildConfig.BASE_URL` 로 엔드포인트 주입. ## 작업 시작/마무리 규약 (위반 금지) - **이슈 우선 + 브랜치 링크**: 새 브랜치는 메타데이터 완비된 GitHub 이슈에 정식 링크된 상태여야 한다. 절차 자동화는 `android-issue-branch` skill 에 위임 — 사용자가 "브랜치 파"라고만 해도 skill 진입 후 이슈부터 작성. `git checkout -b` 직행은 `git-branch-guard.sh` 가 차단. @@ -90,7 +90,7 @@ ```bash ./gradlew assembleDebug ./gradlew testDebugUnitTest # 또는 ./gradlew :app:testDebugUnitTest -./gradlew detekt # 정적 분석 (Detekt) +./gradlew lintDebug # Android Lint (CI lint.yml). ktlint 은 CI(ScaCap/action-ktlint)로 실행 — 로컬 Gradle 태스크 없음 ./gradlew :app:connectedDebugAndroidTest # 인스트루멘티드 테스트 ``` diff --git a/.claude/hooks/git-state-guard.sh b/.claude/hooks/git-state-guard.sh index 1d39798..6786fdc 100755 --- a/.claude/hooks/git-state-guard.sh +++ b/.claude/hooks/git-state-guard.sh @@ -2,13 +2,15 @@ # PreToolUse Bash hook: block dangerous git state changes without explicit user instruction. # # 차단 대상: -# - git push (모든 형태) — force push 포함 +# - git push --force (force push 만) # - git reset --hard # - git rebase -# - git branch -D # - git clean -f # - git checkout -- (작업 파일 폐기) # - git restore (작업 파일 폐기) +# +# 자율 허용 (사용자 지시 2026-06-01): git branch -D / -d, git checkout 는 +# Claude 가 명시 지시 없이 수행 가능. 브랜치 삭제·전환은 손실 위험 낮고 머지된 브랜치 정리에 필요. set -uo pipefail input="$(cat)" @@ -22,7 +24,6 @@ deny() { } # git push — force push 만 차단. 일반 push 와 --delete 는 통과. -# /create-pr 같은 명시 워크플로우에서 일반 push 는 안전한 작업 (기존 commit 추가만, 손실 없음). # Force push (-f, --force, --force-with-lease, +refspec) 는 commit 손실 위험 → 차단 유지. if [[ "$cmd" =~ (^|[[:space:]]|\;|\&|\|)git[[:space:]]+push([[:space:]]|$) ]]; then if [[ "$cmd" =~ git[[:space:]]+push[[:space:]].*(-f([[:space:]]|$)|--force([[:space:]]|=|$)|--force-with-lease) ]] \ @@ -41,29 +42,14 @@ if [[ "$cmd" =~ (^|[[:space:]]|\;|\&|\|)git[[:space:]]+rebase([[:space:]]|$) ]]; deny "git rebase" fi -# git branch -D (force delete) -# 예외: 단일 feat/<숫자> 브랜치 + 머지된 PR (head 매칭) 1건 이상이면 통과. -# memory feedback_cleanup_merged_branches.md — PR 머지 확인 시점 자동 삭제 영구 허가. -# 모호 케이스 (multi-branch, 옵션 섞임, PR 없음/OPEN/CLOSED, head 매칭 안 됨) 는 deny 유지. -if [[ "$cmd" =~ git[[:space:]]+branch[[:space:]]+-D([[:space:]]|$) ]]; then - args="${cmd#*git branch -D}" - read -ra tokens <<< "$args" - if [[ ${#tokens[@]} -eq 1 && "${tokens[0]}" =~ ^feat/[0-9]+$ ]]; then - branch="${tokens[0]}" - if merged=$(gh pr list --head "$branch" --state merged --json number --jq 'length' 2>/dev/null) \ - && [[ "$merged" -gt 0 ]]; then - exit 0 - fi - fi - deny "git branch -D" -fi +# git branch -D / -d (브랜치 삭제) — 사용자 지시로 자율 허용 (2026-06-01). 차단하지 않음. # git clean -f / -fd / -ffd 등 if [[ "$cmd" =~ git[[:space:]]+clean[[:space:]].*-[a-zA-Z]*f ]]; then deny "git clean -f" fi -# git checkout -- (작업 파일 폐기) +# git checkout -- (작업 파일 폐기). 브랜치 전환(git checkout )은 허용. if [[ "$cmd" =~ git[[:space:]]+checkout[[:space:]]+-- ]]; then deny "git checkout --" fi diff --git a/.cursorrules b/.cursorrules deleted file mode 100644 index 5f4919e..0000000 --- a/.cursorrules +++ /dev/null @@ -1,385 +0,0 @@ -# File Sync Rule (CRITICAL) -- **This file (`.cursorrules`) and `.claude/CLAUDE.md` MUST stay in sync.** -- When modifying either file, apply the same changes to both files. -- **Flexible Sync**: Cursor와 Claude Code의 특성 차이로 인한 부분은 각 도구에 맞게 융통성 있게 적용: - - Cursor 전용: Custom Commands (`/doc`, `/test` 등), Rule Management 섹션 - - Claude Code 전용: 이모지 미사용, 간결한 톤 유지 - - 공통: 아키텍처, 코딩 표준, 테스트, Git 브랜치 전략 등 핵심 규칙 -- If you notice discrepancies in **core rules**, immediately synchronize them. - -# Role -You are a Senior Android Developer at a Hackathon. -Prioritize build speed and crash prevention. -If a solution is complex, suggest the simplest working alternative first. -When stuck, provide a working workaround before investigating the perfect solution. - -# Project Context & Tech Stack -- Project Type: Android Native App -- Language: Kotlin (Latest Stable) -- UI Framework: Jetpack Compose (Material 3) -- Build System: Gradle (Kotlin DSL / .kts) -- Dependency Management: Version Catalog (libs.versions.toml) - -# User Preferences -- Language: Korean (모든 설명과 주석은 한국어로 작성) -- Tone: Concise and Technical (간결하고 전문적으로) - -# Architecture Guidelines -- Pattern: MVVM (Model-View-ViewModel) - - **Model**: Entity, Repository (Domain + Data layers) - - **View**: Compose Screen (Presentation layer) - - **ViewModel**: State management and business logic - - **Rule**: Logic goes to ViewModel, not UI. UI only observes StateFlow. -- Architecture: Clean Architecture (Presentation -> Domain -> Data) - - **data**: Data sources (API, Local DB) - - **domain**: Business logic (Entities, Repository interfaces) - - **presentation**: UI layer (Screens, ViewModels) -- State Management: Unidirectional Data Flow (UDF) using StateFlow/SharedFlow -- DI: Hilt (Dagger) -- Async: Coroutines & Flow - - Use `viewModelScope` in ViewModels. - - Use `suspend` functions for I/O operations. - -# Key Libraries & Versions -- Image Loading: Coil 3.x (Note: Use `coil3` package, NOT `coil` package) - - Import: `import coil3.compose.AsyncImage` - - Do NOT use: `import coil.compose.AsyncImage` (Coil 2, deprecated) -- Networking: Retrofit2 + OkHttp + Kotlinx Serialization -- Serialization: Kotlin Serialization (not Gson) - -## Do NOT Use (금지 라이브러리) -- **Gson**: Use Kotlin Serialization instead -- **Kotlin Synthetics**: Deprecated, use View Binding or Compose -- **AsyncTask**: Deprecated, use Coroutines -- **RxJava**: Use Coroutines & Flow instead (simpler for hackathon) -- **Coil 2.x (`coil` package)**: Use Coil 3.x (`coil3` package) -- **LiveData in new code**: Use StateFlow/SharedFlow (UDF pattern) - -# Coding Standards - -## Compose UI -1. Always use `@Composable` annotation properly. -2. Use `Modifier` as the first optional parameter in Composable functions. -3. Separate UI state from UI implementation (State Hoisting). -4. For Experimental Material3 APIs (TopAppBar, Scaffold topBar, etc.), always add: - ```kotlin - @OptIn(ExperimentalMaterial3Api::class) - @Composable - fun MyScreen(...) { ... } - ``` -5. **File Structure Rule (CRITICAL)**: - - **One Composable function per file**: Each Composable file MUST contain only ONE `@Composable` function (plus its preview). - - **Preview is mandatory**: Every Composable file MUST have exactly ONE `@Preview` function. - - **File splitting**: If a file exceeds ~300 lines or contains multiple logical sections, split it into separate component files. - - **Component extraction**: Extract reusable UI sections into separate component files in the `component/` directory. - - **File naming**: Component files should be named descriptively (e.g., `ImageUploadSection.kt`, `HashTagInputSection.kt`). - - **Example structure**: - ```kotlin - // MyScreen.kt - ONLY contains MyScreen function and its preview - @Composable - fun MyScreen(...) { ... } - - @Preview(showBackground = true) - @Composable - private fun MyScreenPreview() { ... } - ``` -6. **KtLint Compliance (CRITICAL - Prevents Build Failures)**: - - **Max Line Length**: Keep lines under 120 characters. If a line exceeds this, break it into multiple lines. - - **Import Order**: Group imports: 1) Kotlin stdlib, 2) Android/AndroidX, 3) Third-party, 4) Project. Separate groups with blank lines. - - **Indentation**: Use 4 spaces (not tabs). - - **Trailing Commas**: Use trailing commas in multi-line parameter lists, function calls, and data classes. - - **Spacing Rules**: - - No space before `:` in type declarations: `val name: String` (not `val name : String`) - - Space after `:` in when expressions: `when (x) { ... }` - - Space around operators: `a + b` (not `a+b`) - - **Function Formatting**: If parameters exceed line length, put each on a new line with proper indentation and trailing commas. - - **String Formatting**: Prefer string templates: `"Hello $name"` (not `"Hello " + name`) - - **Before Any Code Change**: Always ensure code follows KtLint rules. If unsure, break long lines and use proper spacing. -7. **Performance Optimization**: - - Use `remember` and `derivedStateOf` to avoid unnecessary recompositions. - - Always use standard modifiers ordering: `Modifier.size().padding().background().clickable()`. - - Avoid hardcoding values; use `MaterialTheme.typography` and `MaterialTheme.colorScheme`. - - Prefer `LazyColumn` over `Column` for lists to ensure recycling. -8. **State Collection**: - - Use `collectAsStateWithLifecycle()` instead of `collectAsState()` when observing StateFlow in Compose screens. - - Import: `import androidx.lifecycle.compose.collectAsStateWithLifecycle` - -## Kotlin Conventions -1. Prefer `val` over `var`. -2. Use `when` expressions for exhaustiveness. -3. Avoid `!!` (double-bang) operator; use safe calls (`?.`) or `let`. -4. **KtLint Pre-Compliance (WRITE CODE CORRECTLY FROM THE START)**: - - **NEVER write code that violates KtLint rules. Check BEFORE writing, not after.** - - **Comments in parameter lists**: ALWAYS put comments on a SEPARATE LINE above the parameter, never inline. - ```kotlin - // ❌ Wrong - causes build failure - fun example( - param: String, // this is a comment - ) - - // ✅ Correct - fun example( - // this is a comment - param: String, - ) - ``` -5. **KtLint Compliance (CRITICAL - Prevents Build Failures)**: - - **⚠️ IMPORTANT**: KtLint violations cause build failures. Always follow these rules when writing code. - - **Max Line Length**: Keep lines under 120 characters. If a line exceeds this, break it into multiple lines: - ```kotlin - // ❌ Wrong (too long) - val result = repository.getData().fold(onSuccess = { data -> updateState(data) }, onFailure = { error -> showError(error) }) - - // ✅ Correct (broken into multiple lines) - val result = repository.getData().fold( - onSuccess = { data -> updateState(data) }, - onFailure = { error -> showError(error) }, - ) - ``` - - **Import Order**: Group imports: 1) Kotlin stdlib, 2) Android/AndroidX, 3) Third-party, 4) Project. Separate groups with blank lines. - - **Indentation**: Use 4 spaces (not tabs). - - **Trailing Commas**: Always use trailing commas in multi-line parameter lists, function calls, and data classes. - - **Spacing Rules**: - - No space before `:` in type declarations: `val name: String` (not `val name : String`) - - Space after `:` in when expressions: `when (x) { ... }` - - Space around operators: `a + b` (not `a+b`) - - **Function Formatting**: If parameters exceed line length, put each on a new line with proper indentation and trailing commas. - - **String Formatting**: Prefer string templates: `"Hello $name"` (not `"Hello " + name`) - - **Code Writing**: When writing code, proactively check line length and break long lines before they cause build failures. -5. **Smart Cast for Delegated Properties**: - - Do NOT rely on smart casts for delegated properties (e.g., `by collectAsState()`, `by viewModels()`). - - Always assign to a local variable (snapshot) before checking nullability: - ```kotlin - // ❌ Wrong - if (uiState.error != null) { - Text(text = uiState.error) // Smart cast impossible - } - - // ✅ Correct - val errorMessage = uiState.error ?: "오류가 발생했습니다" - if (errorMessage != null) { - Text(text = errorMessage) - } - // OR - uiState.error?.let { error -> - Text(text = error) - } - ``` -6. **Interface Override Rule (CRITICAL - Prevents Compilation Errors)**: - - **NEVER** specify default parameter values in override functions. - - Default values should ONLY be in the interface/abstract class, NOT in the implementation. - ```kotlin - // ❌ Wrong - causes compilation error - interface MyRepository { - suspend fun getData(page: Int = 1): Result> - } - class MyRepositoryImpl : MyRepository { - override suspend fun getData(page: Int = 1): Result> { // ERROR! - // ... - } - } - - // ✅ Correct - default value only in interface - interface MyRepository { - suspend fun getData(page: Int = 1): Result> - } - class MyRepositoryImpl : MyRepository { - override suspend fun getData(page: Int): Result> { // No default value - // ... - } - } - ``` - - **Always check**: When overriding interface functions, remove ALL default parameter values from the override function signature. -5. Follow [Kotlin Official Coding Conventions](https://kotlinlang.org/docs/coding-conventions.html). -6. Use Android Studio auto-formatting: `Ctrl + Alt + L` (Windows) before committing. - -## Naming Conventions -- **File/Class names**: PascalCase (e.g., `HomeScreen.kt`, `CombinationCard.kt`, `HomeViewModel`) -- **Variable names**: camelCase (e.g., `selectedCategory`, `isLoading`) -- **Function names**: camelCase (e.g., `loadCombinations()`, `updateSearchQuery()`) -- **Constants**: UPPER_SNAKE_CASE (e.g., `BASE_URL`, `MAX_RETRY_COUNT`) - -## Dependency Management -1. Do NOT hardcode versions in `build.gradle.kts`. -2. Add new libraries to `gradle/libs.versions.toml` first: - ```toml - [versions] - myLibrary = "1.0.0" - - [libraries] - my-library = { group = "com.example", name = "mylib", version.ref = "myLibrary" } - ``` -3. Then reference them in `build.gradle.kts` using `libs.my.library`. - -## File Naming & Structure -- Screen files: `*Screen.kt` (e.g., `HomeScreen.kt`, `DetailScreen.kt`) -- ViewModel files: `*ViewModel.kt` (e.g., `HomeViewModel.kt`) -- Component files: `*Card.kt`, `*Chip.kt`, etc. -- Use descriptive names, avoid abbreviations like "Diary" for "Detail". - -## Package Structure (Clean Architecture) -``` -com.example.hackathon/ -├── data/ # 데이터 레이어 -│ ├── dto/ # Request/Response 모델 -│ ├── mapper/ # Entity ↔ DTO 변환 -│ ├── repositoryimpl/ # Repository 구현 -│ └── service/ # API 인터페이스 -├── domain/ # 도메인 레이어 -│ ├── entity/ # 비즈니스 엔티티 -│ └── repository/ # Repository 인터페이스 -├── presentation/ # 프레젠테이션 레이어 -│ ├── navigation/ # 네비게이션 -│ ├── route/ # 라우트 정의 -│ ├── screen/ # 화면 컴포저블 -│ └── viewmodel/ # ViewModel -├── core/ # 공통 -│ └── component/ # 공통 컴포넌트 -├── di/ # 의존성 주입 -└── ui/theme/ # 테마 설정 -``` - -## Error Handling -- Wrap network calls in `runCatching` or `try-catch` blocks within Repository. -- Use `Result` type for repository methods: - ```kotlin - suspend fun getData(): Result> { - return runCatching { - // API call - } - } - ``` -- Expose UI states as data classes with nullable error field: - ```kotlin - data class UiState( - val data: List = emptyList(), - val isLoading: Boolean = false, - val error: String? = null - ) - ``` -- Handle errors in ViewModel using `fold()`: - ```kotlin - repository.getData().fold( - onSuccess = { data -> /* update state */ }, - onFailure = { error -> /* set error state */ } - ) - ``` - -## Documentation -- Write all comments and KDoc in Korean (한국어). -- Explain 'Why' we did this, not just 'What' the code does. -- Add KDoc for complex business logic. -- Keep function names descriptive. -- Add `// 담당자: 이름` comment at the top of files for hackathon collaboration. -- Add `// TODO: 내용` for future improvements. - -## Testing Guidelines -- **Testing Strategy**: Focus on ViewModel unit tests (business logic validation). - - ViewModel tests are fast and catch most bugs. - - UI tests are optional for hackathon (time-consuming). -- **Testing Libraries**: - - **MockK**: Mocking dependencies (`io.mockk`) - - **Turbine**: Flow testing (`app.cash.turbine`) - - **Kotlinx Coroutines Test**: Coroutine testing (`kotlinx.coroutines.test`) -- **Test Structure**: - - Test files: `*Test.kt` (e.g., `CreateCombinationViewModelTest.kt`) - - Test utilities: `util/` package (e.g., `MainDispatcherRule.kt`) - - Use `@get:Rule val mainDispatcherRule = MainDispatcherRule()` for coroutine tests. -- **Test Naming**: Use descriptive test names with backticks: - ```kotlin - @Test - fun `updateTitle updates the title in uiState`() = runTest { ... } - ``` -- **Test Execution**: - - **Automatic**: Tests run automatically on commit (Git Hook) and code quality checks. - - **Manual**: `./gradlew test` or `.\scripts\run-tests.ps1` - - **Specific test**: `./gradlew test --tests "*ViewModelTest"` -- **Test Best Practices**: - - Mock all external dependencies (Repository, API services). - - Test state changes using Turbine: `viewModel.uiState.test { ... }` - - Use `runTest` for coroutine-based tests. - - Test both success and failure scenarios. - - Keep tests fast and isolated (no shared state). - -# Project-Specific Rules -- This is a hackathon project (2 days prep + 1 day event). -- Focus on working code over perfect code. -- Use dummy data when server API is not ready. -- All team members work on both design and development. - -## Troubleshooting: File Lock Issues (Android Studio + Cursor) -- **Problem**: `R.jar: The process cannot access the file because it is being used by another process` -- **Cause**: Cursor's file indexing conflicts with Android Studio's Gradle build process. -- **Solution**: - 1. **Primary**: `.cursorignore` file already excludes `build/` folders (prevents indexing). - 2. **If still occurs**: Run `./gradlew --stop` (or `.\gradlew.bat --stop` on Windows) to gracefully stop Gradle daemon. - 3. **Alternative**: Use Android Studio's `Build > Clean Project` menu. - 4. **Windows Defender**: Consider excluding project folder from real-time scanning (Settings > Virus & threat protection > Exclusions). -- **Never**: Force-kill JDK processes via Task Manager (risks data corruption). - -## Rule Management -- **When making code changes**: If the changes introduce new patterns, conventions, or require specific guidelines, suggest updates to `.cursorrules` at the end of the conversation. -- **Format**: "`.cursorrules`에 추가할 내용: [제안 내용]" 형식으로 제안하면 개발자가 복사-붙여넣기할 수 있음. -- **Purpose**: Keep `.cursorrules` up-to-date with project practices so that future changes follow established patterns. - -## Hackathon Mindset -- **Working code first**: `// TODO: Refactor later for hackathon` 주석을 적극 허용 -- **Speed over perfection**: 완벽한 클린 코드보다 작동하는 코드 우선 -- **Quick workarounds**: 복잡한 해결책보다 간단한 우회책 먼저 제시 - -## Figma AI 프롬프트 작성 규칙 -- **중요**: Figma AI는 프로젝트 컨텍스트를 참조할 수 없습니다. - - 팀원 이름, 기존 디자인 스타일, 프로젝트 히스토리 등을 모릅니다. - - 프롬프트에 모든 필요한 정보를 명시적으로 포함해야 합니다. -- **프롬프트 작성 시 필수 포함 사항**: - - 디자인 시스템 (색상, 타이포그래피, 간격 등)을 완전히 명시 - - 화면 구성 요소를 구체적으로 설명 (위치, 크기, 스타일) - - 인터랙션 및 상태 관리 방법 설명 - - 기존 화면과의 일관성을 위한 스타일 가이드 포함 - - 컨텍스트 없이도 이해할 수 있도록 독립적으로 작성 -- **금지 사항**: - - "예원이 스타일", "기존 디자인 참고" 등 컨텍스트 의존적 표현 사용 금지 - - 팀원 이름이나 프로젝트 히스토리 언급 금지 - - 다른 파일이나 디자인 참조 없이 독립적으로 이해 가능하도록 작성 - -# Custom Commands -- `/doc`: 해당 파일의 모든 public 함수에 한국어 KDoc 주석을 작성하라. -- `/test`: 현재 ViewModel에 대한 Unit Test 코드를 작성하라 (MockK, Turbine 사용). -- `/clean`: 사용하지 않는 import와 리소스를 정리하라. -- `/fix`: 현재 에러를 분석하고 가장 간단한 해결책을 제시하라. -- `/refactor`: 해커톤 후 리팩토링이 필요한 부분에 `// TODO: Refactor later` 주석을 추가하라. - -# Git Branch Strategy -- **CRITICAL**: Always work on your own branch, NOT on main branch. -- **Branch naming**: Use your name as branch name (e.g., `ilhyuk`, `yewon`). -- **Role-based branches**: - - **ilhyuk (일혁)**: - - CreateCombinationScreen (조합 등록 화면) - - MyScreen (마이페이지) - - Related ViewModels (CreateCombinationViewModel, MyPageViewModel) - - **yewon (예원)**: - - HomeScreen (홈 화면) - - DetailScreen (조합 상세 화면) - - Related ViewModels (HomeViewModel, DetailViewModel) -- **MANDATORY: Check current branch before starting work**: - - **ALWAYS** check current branch with `git branch --show-current` or `git status` before making any code changes. - - If you're working on ilhyuk's tasks (CreateCombinationScreen, MyScreen), you MUST be on `ilhyuk` branch. - - If you're working on yewon's tasks (HomeScreen, DetailScreen), you MUST be on `yewon` branch. - - If on wrong branch, switch to correct branch first: `git checkout ilhyuk` or `git checkout yewon`. - - **NEVER** work on `main` branch for personal tasks. -- **Workflow**: - 1. **FIRST**: Check current branch with `git branch --show-current` - 2. If wrong branch, switch: `git checkout ilhyuk` (or your branch name) - 3. Pull latest changes: `git pull origin main` (if needed) - 4. Make changes on your branch - 5. Commit and push to your branch: `git push origin ilhyuk` - 6. Merge to main only after review/approval -- **NEVER commit directly to main branch** for personal work. -- **Common code** (shared components, theme, navigation) can go to main after team discussion. -- **Commit Policy (CRITICAL)**: - - **Documentation files only**: Only commit documentation files (`.md`, `.txt`, `.cursorrules`, etc.) automatically without user approval. - - **Documentation merge to main**: After committing documentation files, merge them to remote main branch automatically. - - **Code files**: NEVER commit code changes (`.kt`, `.kts`, `.xml`, etc.) without explicit user approval. - - **Always ask**: Before committing any non-documentation files, always ask the user for permission first. - - **Exception**: Documentation updates can be committed and merged to main automatically, but code changes require explicit confirmation. - diff --git a/ACTION_PLAN.md b/ACTION_PLAN.md deleted file mode 100644 index e447cc4..0000000 --- a/ACTION_PLAN.md +++ /dev/null @@ -1,114 +0,0 @@ -# 🚀 지금 당장 해야 할 일 (우선순위) - -## ⚡ 빠른 시작 가이드 (시간 부족 상황) - -### Step 1: 최소 기획 (30분) - 함께 -- [x] 서비스 컨셉: 음식점 꿀조합 공유 앱 (이미 정해짐) -- [ ] 화면 확정: 홈, 상세, 등록, 로그인, 회원가입, 온보딩, 검색 -- [ ] 역할 분담 확정: - - 예원: 홈 화면 + 조합 상세 화면 - - 일혁: 로그인, 회원가입, 온보딩, 조합 등록, 검색 화면 - -### Step 2: Figma 바로 시작 (지금!) - -**공통 작업 (30분):** -1. Figma 파일 생성 및 공유 -2. 참고 파일 import - - `KUIT 5기 해커톤 기획디자인 참고자료.fig` (54.4MB) -3. 필수 플러그인 설치 - - Material Symbols - - 한글입숨 - - Autoflow -4. 프레임 생성: 390x844 (Android 기본) - -**병렬 작업 시작 (각자 2개씩):** -- **예원**: 홈 화면 + 조합 상세 화면 와이어프레임 -- **일혁**: 로그인, 회원가입, 온보딩, 조합 등록, 검색 와이어프레임 - -### Step 3: 서버팀 협의 (디자인하면서 병행) -- 디자인 하면서 필요한 API 생각해두기 -- 서버팀과 간단히 협의 (카카오톡/슬랙으로도 가능) -- Base URL만 확정해두면 됨 - ---- - -## 📋 각 화면 필수 요소 (와이어프레임에 포함할 것) - -### 홈 화면 (예원) -- 검색바 -- 카테고리 필터 칩 (서브웨이/하이디라오/편의점) -- 조합 카드 리스트 - - 이미지 썸네일 - - 제목 - - 간단 설명 - - 좋아요 수 - - 작성자 - -### 조합 상세 화면 (예원) -- 이미지 (상단) -- 제목 -- 작성자 정보 -- 재료/비율 리스트 -- 만드는 방법 (스텝) -- 좋아요 버튼 -- 댓글 섹션 - -### 조합 등록 화면 (일혁) -- 이미지 업로드 버튼 -- 제목 입력 -- 카테고리 선택 -- 재료 입력 (태그 형태) -- 설명 입력 -- 등록 버튼 - - ---- - -## 🎨 Figma 작업 팁 (빠르게 하기) - -### 오토 레이아웃 활용 (Shift + A) -- 카드 만들 때 필수! -- 프레임 길이 조절하면 자동으로 반응형 - -### 단축키 외우기 -``` -V - 선택 -R - 사각형 (카드/버튼) -T - 텍스트 -Shift + A - 오토 레이아웃 -Ctrl + D - 복제 -``` - -### 더미 텍스트 -- 한글입숨 플러그인으로 빠르게 생성 - -### 아이콘 -- Material Symbols 플러그인 사용 - ---- - -## ⏰ 오늘 목표 - -**오늘 끝까지:** -- [ ] Figma 기본 설정 완료 -- [ ] 와이어프레임 4개 화면 완성 -- [ ] 프로토타입 연결 (Autoflow) -- [ ] UI Style Guide 기본 정리 (색상, 폰트 크기만) - -**내일 (Day 2):** -- 개발 시작 - ---- - -## 💡 시간 절약 팁 - -1. **완벽하지 않아도 됨**: 로우-피델리티로 빠르게 구조만 잡기 -2. **참고 파일 활용**: KUIT 5기 참고자료에서 레이아웃/컴포넌트 가져오기 -3. **Material Design 참고**: https://m3.material.io/ 에서 컴포넌트 참고 -4. **서버 협의는 나중에**: 디자인 먼저 하고 API는 개발하면서 협의 - ---- - -**지금 바로 Figma 열고 시작하세요!** 🚀 - - diff --git a/CHECKLIST.md b/CHECKLIST.md deleted file mode 100644 index 7008adb..0000000 --- a/CHECKLIST.md +++ /dev/null @@ -1,146 +0,0 @@ -# 해커톤 작업 체크리스트 - -## 사전 작업 (Day 1-2) - -### 기획 & 디자인 -- [x] 서비스 컨셉 확정 -- [x] MVP 기능 정의 -- [ ] Figma 와이어프레임 완성 - - [ ] 홈 화면 (예원) - - [ ] 조합 상세 화면 (예원) - - [ ] 로그인 화면 (일혁) - - [ ] 조합 등록 화면 (일혁) - - [ ] 검색 화면 (일혁) -- [ ] UI Style Guide 작성 -- [ ] 프로토타입 연결 - -### 프로젝트 셋업 -- [x] 프로젝트 생성 -- [x] Git Repository 설정 -- [x] .gitignore 설정 -- [x] 의존성 설정 (Hilt, Retrofit, Compose 등) -- [x] 테마 설정 (Color, Typography) -- [ ] 폰트 설정 (디자이너 확인 후) - -### 기본 구조 -- [x] 데이터 모델 정의 -- [x] Repository 인터페이스 및 구현 -- [x] API 인터페이스 정의 -- [x] ViewModel 기본 구조 -- [x] 화면 기본 구조 -- [x] 네비게이션 설정 - -### 컨벤션 -- [x] Git 전략 문서화 -- [x] 코드 컨벤션 문서화 -- [x] 역할 분담 명확화 - ---- - -## 해커톤 당일 - -### 기능 구현 - -#### 홈 화면 (예원) -- [x] 기본 UI 구조 -- [x] 검색 기능 -- [x] 카테고리 필터 -- [x] 조합 목록 표시 -- [ ] 디자인 적용 -- [ ] API 연동 - -#### 조합 상세 화면 (예원) -- [x] 기본 UI 구조 -- [x] 조합 정보 표시 -- [x] 좋아요 기능 -- [ ] 디자인 적용 -- [ ] API 연동 -- [ ] 댓글 기능 (선택) - -#### 로그인 화면 (일혁) -- [ ] 기본 UI 구조 -- [ ] 입력 폼 -- [ ] 디자인 적용 -- [ ] API 연동 - -#### 조합 등록 화면 (일혁) -- [x] 기본 UI 구조 -- [x] 입력 폼 -- [x] 카테고리 선택 -- [ ] 이미지 업로드 -- [ ] 디자인 적용 -- [ ] API 연동 - - -### 공통 작업 -- [x] 공통 컴포넌트 (CombinationCard, CategoryChip, SearchBar) -- [x] 테마 설정 -- [ ] 에러 처리 개선 -- [ ] 로딩 상태 개선 - -### API 연동 -- [ ] 서버팀과 API 스펙 최종 확인 -- [ ] Base URL 설정 -- [ ] 조합 목록 API 연동 -- [ ] 조합 상세 API 연동 -- [ ] 조합 등록 API 연동 -- [ ] 내 조합 목록 API 연동 -- [ ] 좋아요 API 연동 (선택) - -### 테스트 -- [ ] 각 화면 수동 테스트 -- [ ] 네비게이션 테스트 -- [ ] API 연동 테스트 -- [ ] 에러 케이스 테스트 - -### 발표 준비 -- [ ] 페인포인트 정리 -- [ ] 서비스 Overview -- [ ] UI Style Guide 스크린샷 -- [ ] Flow Chart -- [ ] 프로토타입 시연 영상 - ---- - -## 우선순위 - -### 1순위 (필수) -- [ ] 홈 화면 완성 -- [ ] 조합 상세 화면 완성 -- [ ] 조합 등록 기능 완성 -- [ ] 마이페이지 완성 -- [ ] API 연동 (기본 CRUD) - -### 2순위 (시간 남으면) -- [ ] 이미지 업로드 -- [ ] 좋아요 기능 -- [ ] 댓글 기능 -- [ ] 검색 기능 고도화 - -### 3순위 (여유 있으면) -- [ ] 사용자 인증 -- [ ] 알림 기능 - ---- - -## 진행 상황 공유 - -### 예원 작업 -- [ ] 홈 화면 작업 시작 -- [ ] 홈 화면 작업 완료 -- [ ] 조합 상세 화면 작업 시작 -- [ ] 조합 상세 화면 작업 완료 - -### 일혁 작업 -- [x] 로그인 화면 기본 구조 완료 -- [x] 회원가입 화면 기본 구조 완료 -- [x] 온보딩 화면 기본 구조 완료 -- [x] 조합 등록 화면 기본 구조 완료 -- [x] 검색 화면 기본 구조 완료 -- [ ] 이미지 업로드 구현 -- [ ] API 연동 - ---- - -**마지막 업데이트**: 작업 진행 시 실시간 업데이트 - diff --git a/CONVENTION.md b/CONVENTION.md deleted file mode 100644 index 0abaf90..0000000 --- a/CONVENTION.md +++ /dev/null @@ -1,149 +0,0 @@ -# 프로젝트 컨벤션 - -## Git 컨벤션 - -### 커밋 메시지 규칙 -``` -: - -예시: -feat: 조합 등록 화면 UI 구현 -fix: 네비게이션 버그 수정 -refactor: Repository 구조 개선 -style: 코드 포맷팅 -chore: 의존성 업데이트 -``` - -**Type 종류:** -- `feat`: 새로운 기능 추가 -- `fix`: 버그 수정 -- `refactor`: 코드 리팩토링 -- `style`: 코드 포맷팅, 세미콜론 누락 등 -- `chore`: 빌드 업무 수정, 패키지 매니저 설정 등 -- `docs`: 문서 수정 - -### 브랜치 전략 -- **main**: 공통 코드 및 완성된 기능 -- **yeowon**: 예원의 작업 브랜치 -- **ilhyeok**: 일혁의 작업 브랜치 - -자세한 내용은 `GIT_STRATEGY.md` 참고 - ---- - -## 코드 컨벤션 - -### Kotlin 코딩 컨벤션 -- [Kotlin 공식 코딩 컨벤션](https://kotlinlang.org/docs/coding-conventions.html) 준수 -- Android Studio 자동 포맷팅 사용: `Ctrl + Alt + L` (Windows) - -### 네이밍 규칙 - -#### 파일/클래스 이름 -- **파일명**: PascalCase - - 예: `HomeScreen.kt`, `CombinationCard.kt` -- **클래스명**: PascalCase - - 예: `HomeViewModel`, `CombinationRepository` - -#### 변수/함수 이름 -- **변수명**: camelCase - - 예: `selectedCategory`, `isLoading` -- **함수명**: camelCase - - 예: `loadCombinations()`, `updateSearchQuery()` -- **상수**: UPPER_SNAKE_CASE - - 예: `BASE_URL`, `MAX_RETRY_COUNT` - -#### 패키지 구조 -``` -com.example.hackathon/ -├── data/ # 데이터 레이어 -│ ├── dto/ # Request/Response 모델 -│ ├── mapper/ # Entity ↔ DTO 변환 -│ ├── repositoryimpl/ # Repository 구현 -│ └── service/ # API 인터페이스 -├── domain/ # 도메인 레이어 -│ ├── entity/ # 비즈니스 엔티티 -│ └── repository/ # Repository 인터페이스 -├── presentation/ # 프레젠테이션 레이어 -│ ├── navigation/ # 네비게이션 -│ ├── route/ # 라우트 정의 -│ ├── screen/ # 화면 컴포저블 -│ └── viewmodel/ # ViewModel -├── core/ # 공통 -│ └── component/ # 공통 컴포넌트 -├── di/ # 의존성 주입 -└── ui/theme/ # 테마 설정 -``` - ---- - -## 아키텍처 - -### MVVM 패턴 사용 -- **Model**: Entity, Repository -- **View**: Compose Screen -- **ViewModel**: 상태 관리 및 비즈니스 로직 - -### Clean Architecture -- **data**: 데이터 소스 (API, Local DB) -- **domain**: 비즈니스 로직 -- **presentation**: UI 레이어 - ---- - -## 작업 규칙 - -### 역할 분담 -- **예원**: 홈 화면, 조합 상세 화면 -- **일혁**: 로그인 화면, 회원가입 화면, 온보딩 화면, 조합 등록 화면, 검색 화면 - -### 파일 담당자 표시 -각 파일 상단에 담당자 주석 추가: -```kotlin -// 담당자: 예원 -// TODO: 디자인 확인 후 UI 조정 필요 -``` - -### 충돌 방지 -- 작업 전 항상 `git pull origin main` -- 각자 담당 파일만 수정 -- 공통 파일 수정 시 팀원과 사전 협의 - ---- - -## 코드 리뷰 - -### 해커톤 특성상 -- 시간 부족으로 상세한 코드 리뷰는 생략 -- 커밋은 기능별로 나눠서 진행 (롤백 용이) -- 큰 변경사항은 팀원과 사전 논의 - ---- - -## TODO 관리 - -### TODO 주석 규칙 -```kotlin -// TODO: [담당자] [내용] -// TODO: 예원 - 디자인 확인 후 UI 조정 -// TODO: 일혁 - 이미지 업로드 기능 추가 -// TODO: 서버 API 연동 필요 -``` - ---- - -## 테스트 - -### 해커톤 특성상 -- 단위 테스트는 생략 -- 수동 테스트로 기능 확인 -- 주요 기능만 동작 확인 - ---- - -## 참고 자료 - -- [Kotlin 코딩 컨벤션](https://kotlinlang.org/docs/coding-conventions.html) -- [Android 개발 가이드](https://developer.android.com/) -- [Material Design 3](https://m3.material.io/) - diff --git a/FIGMA_AI_PROMPT_CREATE_COMBINATION.md b/FIGMA_AI_PROMPT_CREATE_COMBINATION.md deleted file mode 100644 index bdd8f53..0000000 --- a/FIGMA_AI_PROMPT_CREATE_COMBINATION.md +++ /dev/null @@ -1,112 +0,0 @@ -안드로이드 모바일 앱의 "조합 등록" 화면을 디자인해줘. - -**디자인 시스템 (기존 스타일 가이드 준수):** -- Primary 색상: #E10818 (빨간색) -- 배경: White (#FFFFFF) -- 텍스트: Black (#000000), Gray700 (#383838) -- 폰트: Pretendard (Bold, SemiBold, Medium) -- 타이포그래피: - - Head1_bold: 24px, Bold, lineHeight 28px - - Head2_bold: 22px, Bold, lineHeight 24px - - Sub1_semibold: 18px, SemiBold, lineHeight 20px - - Body_semibold: 14px, SemiBold, lineHeight 16px - - Caption_medium: 12px, Medium, lineHeight 14px - -**화면 구성 요소:** -1. 상단 바 (Top Bar) - - 배경색: Primary 색상 (#E10818) 전체 배경 - - 제목: "조합 등록" (Head2_bold, 22px, White 텍스트) - - 뒤로가기 아이콘 (왼쪽, White 아이콘) - - 상태 바(상태 표시줄)도 Primary 색상으로 통일 - -2. 이미지 업로드 영역 - - 회색 배경 박스 (Gray50 또는 Gray700) - - 중앙에 카메라 아이콘 - - "이미지 등록" 텍스트 (Body_medium) - - 둥근 모서리 (15px radius) - -3. 입력 필드들 (OutlinedTextField 스타일) - - 제목 입력: 단일 라인 - - 카테고리/태그 입력: 해시태그 시스템 - - 텍스트 입력 필드에서 해시태그 입력 (예: #서브웨이, #하이디라오, #편의점) - - 입력된 해시태그는 칩 형태로 표시 - - 해시태그 칩: Primary 색상 배경 (#E10818), White 텍스트, 15px 둥근 모서리 - - 각 칩에 X 버튼으로 삭제 가능 - - 여러 개의 해시태그 추가 가능 (가로 스크롤 또는 줄바꿈) - - 플레이스홀더: "#태그를 입력하세요" 또는 "예: #서브웨이 #하이디라오" - - 공개 여부: 토글 또는 선택 버튼 - - "공개" / "비공개" 두 개의 버튼 - - 선택된 버튼: Primary 색상 배경 (#E10818), White 텍스트 - - 선택되지 않은 버튼: White 배경, Primary 테두리, Primary 텍스트 - - 재료 입력: 멀티라인 텍스트 영역 - - 플레이스홀더: "재료" - - 여러 줄 입력 가능 - - 만드는 방법 입력: 멀티라인 텍스트 영역 - - 플레이스홀더: "만드는 방법" - - 여러 줄 입력 가능 - - 설명 입력: 멀티라인 텍스트 영역 - - 플레이스홀더: "조합에 대한 설명을 입력하세요" - -4. 하단 버튼 - - 등록하기 버튼: Primary 색상 배경, White 텍스트, 전체 너비 - - 취소 버튼: White 배경, Primary 테두리, Primary 텍스트 - -**레이아웃:** -- 화면 크기: 390x844 (Android 기본) -- 패딩: 16dp (좌우) -- 요소 간 간격: 16dp -- 섹션 간 간격: 24dp -- 스크롤 가능한 세로 레이아웃 - -**스타일 가이드:** -- 카드/박스: White 배경, Primary 테두리 (1px), 둥근 모서리 (15px) -- 그림자: 4px 4px 4px rgba(0,0,0,0.25) -- 입력 필드: White 배경, Gray700 테두리, Primary 포커스 색상 -- 버튼: Primary 배경, 15px 둥근 모서리, 적절한 패딩 -- 해시태그 칩: Primary 배경, White 텍스트, 12px 폰트, 20px 높이, X 아이콘 포함 - -**해시태그 시스템 상세:** -- 사용자가 자유롭게 해시태그 입력 (예: #서브웨이, #하이디라오, #편의점, #기타 등) -- 입력 필드에서 Enter 또는 쉼표로 태그 추가 -- 추가된 태그는 Primary 색상 칩으로 표시 -- 각 칩에 작은 X 버튼으로 삭제 가능 -- 여러 태그를 가로로 나열 (필요시 줄바꿈) -- 기존 홈 화면에서 보이는 해시태그 칩 스타일과 동일하게 디자인 - -**예원이 디자인한 기존 화면 스타일 (반드시 참고하여 동일한 톤 유지):** - -**홈 화면 디자인:** -- 상단 바: Primary 색상 (#E10818) 전체 배경, 높이 약 56dp - - 왼쪽: "쩝쩝박사" 로고 텍스트 (White, Head2_bold 또는 Head1_bold) - - 오른쪽: 검색 아이콘 (White, Material Icons 스타일) - - 상태 바(상태 표시줄)도 Primary 색상으로 통일 -- 탭 영역: 상단 바 바로 아래 - - "전체" / "추천" 두 개의 탭 (TabRow 스타일) - - 선택된 탭: Primary 색상 언더라인 또는 배경 - - 미선택 탭: Gray700 텍스트 -- 조합 카드 리스트: - - 각 카드: White 배경, Primary 테두리 (1px), 둥근 모서리 (15px) - - 카드 내용: 상단에 이미지 썸네일, 제목 (Head2_bold 또는 Sub1_semibold), 설명 (Body_medium), 하단에 해시태그 칩들 - - 해시태그 칩: Primary 배경 (#E10818), White 텍스트, 작은 크기 (약 20px 높이), 15px 둥근 모서리, 예시: "#서브웨이", "#편의점" - - 카드 간 간격: 12dp -- 하단 네비게이션: 5개 아이콘 (홈, 검색, 플러스, 하트, 마이페이지), 선택된 아이콘은 Primary 색상 - -**상세 화면 디자인:** -- 상단 바: Primary 색상 (#E10818) 전체 배경, 높이 약 56dp - - 왼쪽: 뒤로가기 아이콘 (White, Material Icons ArrowBack) - - 중앙: 제목 텍스트 (White, Head2_bold, 예: "건희 소스") - - 오른쪽: 하트 아이콘 (White, Material Icons Favorite) - - 상태 바(상태 표시줄)도 Primary 색상으로 통일 -- 탭 영역: 상단 바 바로 아래 - - "설명" / "사진" 두 개의 탭 (TabRow 스타일) - - 선택된 탭: Primary 색상 언더라인 또는 배경 - - 미선택 탭: Gray700 텍스트 -- 내용 영역: White 배경, 스크롤 가능 - - 제목: "사전" (Head2_bold 또는 Head1_bold) - - 불릿 포인트 리스트: Body_medium 또는 Body_semibold, 각 항목 앞에 불릿(•) 또는 번호 - -**스타일 일관성:** -- 모든 상단 바는 Primary 색상 배경, White 텍스트/아이콘 -- 탭은 TabRow 스타일로 통일 -- 카드는 White 배경 + Primary 테두리 + 15px 둥근 모서리 -- 해시태그 칩은 Primary 배경 + White 텍스트로 통일 diff --git a/FIGMA_AI_PROMPT_EDIT_PROFILE.md b/FIGMA_AI_PROMPT_EDIT_PROFILE.md deleted file mode 100644 index 475ad21..0000000 --- a/FIGMA_AI_PROMPT_EDIT_PROFILE.md +++ /dev/null @@ -1,111 +0,0 @@ -안드로이드 모바일 앱의 "프로필 수정" 화면을 디자인해줘. - -**디자인 시스템:** -- Primary 색상: #E10818 (빨간색) -- 배경: White (#FFFFFF) -- 텍스트: Black (#000000), Gray700 (#383838) -- Error 색상: Material 3 기본 Error 색상 (빨간색 계열) -- 폰트: Pretendard (Bold, SemiBold, Medium) -- 타이포그래피: - - Head1_bold: 24px, Bold, lineHeight 28px - - Head2_bold: 22px, Bold, lineHeight 24px - - Sub1_semibold: 18px, SemiBold, lineHeight 20px - - Body_semibold: 14px, SemiBold, lineHeight 16px - - Caption_medium: 12px, Medium, lineHeight 14px - - DisplaySmall: Material 3 기본 크기 (큰 텍스트용) - -**화면 구성 요소 (위에서 아래 순서):** - -1. 상단 바 (TopAppBar) - - 배경색: Primary (#E10818) 전체 배경, 높이 56dp - - 제목: "프로필 수정" (중앙 정렬, White, Head2_bold) - - 왼쪽: 뒤로가기 아이콘 (White, Material Icons ArrowBack) - - 상태 바(상태 표시줄)도 Primary 색상으로 통일 - -2. 화면 내용 영역 (상단 바 아래, 16dp 패딩) - - 상태에 따라 다른 UI 표시: - - a) 로딩 상태 - - 중앙 정렬 CircularProgressIndicator - - 전체 화면 사용 - - b) 에러 상태 (프로필 로드 실패) - - 에러 메시지 텍스트 (Error 색상, 중앙 정렬, BodyMedium) - - "다시 시도" 버튼 (Primary 배경, White 텍스트, 중앙 정렬) - - 메시지와 버튼 간격: 16dp - - 전체 화면 중앙 정렬 - - c) 정상 상태 (프로필 수정 폼) - - 세로 스크롤 가능한 Column 레이아웃 - - 요소 간 세로 간격: 24dp - - 좌우 패딩: 16dp - - 구성 요소 (위에서 아래 순서): - - (1) 프로필 이미지 영역 - - 중앙 정렬 - - 프로필 이미지: 원형 120dp, Primary Container 색상 배경 - - 이미지 없을 때: 닉네임의 첫 글자 표시 (DisplaySmall 타이포그래피, 중앙 정렬) - - 이미지 선택 기능은 추후 추가 가능 (현재는 표시만) - - (2) 닉네임 입력 필드 - - OutlinedTextField 스타일 - - 전체 너비 사용 - - 라벨: "닉네임" - - 단일 라인 입력 - - 포커스 시: Primary 색상 테두리 및 라벨 - - 비활성화 상태: 저장 중일 때 비활성화 (enabled = false) - - White 배경, Gray700 기본 테두리 - - (3) 저장 버튼 - - 전체 너비 사용 - - Primary 색상 배경, White 텍스트 - - Material 3 버튼 스타일, 둥근 모서리 - - 비활성화 조건: 저장 중이거나 닉네임이 비어있을 때 - - 저장 중 상태: - - 버튼 내부에 CircularProgressIndicator (20dp, White 색상) - - 인디케이터와 텍스트 간격: 8dp - - 텍스트: "저장 중..." - - 일반 상태: - - 텍스트: "저장" - - (4) 에러 메시지 (저장 실패 시) - - 저장 버튼 아래 표시 - - Error 색상 텍스트 - - BodySmall 타이포그래피 - - 저장 성공 시 숨김 - -**레이아웃:** -- 화면 크기: 390x844 (Android 기본) -- 전체: Scaffold 레이아웃 -- 내부 패딩: 16dp (좌우 및 상하) -- 요소 간 세로 간격: 24dp (정상 상태) -- 스크롤: 세로 스크롤 가능 (키보드가 올라올 때 대비) - -**스타일 가이드:** -- 상단 바: Primary 배경, White 텍스트/아이콘 -- 프로필 이미지: 원형, Primary Container 배경 (이미지 없을 때) -- 입력 필드: White 배경, Gray700 기본 테두리, Primary 포커스 색상 -- 버튼: Material 3 스타일, Primary 배경, 둥근 모서리 -- 에러 메시지: Error 색상, BodySmall 타이포그래피 - -**인터랙션:** -- 뒤로가기 아이콘 클릭: 이전 화면으로 이동 -- 닉네임 입력: 실시간으로 상태 업데이트 -- 저장 버튼 클릭: - - 저장 중: 로딩 인디케이터 표시, 버튼 비활성화 - - 저장 성공: 이전 화면(마이페이지)으로 자동 이동 - - 저장 실패: 에러 메시지 표시 -- "다시 시도" 버튼 클릭: 프로필 정보 다시 로드 - -**상태 관리:** -- 화면 진입 시: 프로필 정보 자동 로드 -- 로딩 중: CircularProgressIndicator 표시 -- 에러 발생: 에러 메시지 + 재시도 버튼 표시 -- 저장 중: 입력 필드 비활성화, 저장 버튼에 로딩 인디케이터 표시 -- 저장 실패: 에러 메시지 표시 (저장 버튼 아래) -- 저장 성공: 자동으로 이전 화면으로 이동 - -**유효성 검사:** -- 닉네임이 비어있으면 저장 버튼 비활성화 -- 저장 중에는 모든 입력 필드 비활성화 diff --git a/FIGMA_AI_PROMPT_LOGIN.md b/FIGMA_AI_PROMPT_LOGIN.md deleted file mode 100644 index a806c30..0000000 --- a/FIGMA_AI_PROMPT_LOGIN.md +++ /dev/null @@ -1,122 +0,0 @@ -안드로이드 모바일 앱의 "로그인" 화면을 디자인해줘. - -**디자인 시스템 (기존 스타일 가이드 준수):** -- Primary 색상: #E10818 (빨간색) -- 배경: White (#FFFFFF) -- 텍스트: Black (#000000), Gray700 (#383838) -- 폰트: Pretendard (Bold, SemiBold, Medium) -- 타이포그래피: - - Head1_bold: 24px, Bold, lineHeight 28px - - Head2_bold: 22px, Bold, lineHeight 24px - - Sub1_semibold: 18px, SemiBold, lineHeight 20px - - Body_semibold: 14px, SemiBold, lineHeight 16px - - Caption_medium: 12px, Medium, lineHeight 14px - -**화면 구성 요소:** -1. 상단 바 (Top Bar) - - 배경색: Primary 색상 (#E10818) 전체 배경 - - 제목: "로그인" 또는 앱 로고 "쩝쩝박사" (Head2_bold, 22px, White 텍스트) - - 뒤로가기 아이콘 (왼쪽, White 아이콘) - 선택 사항 (스플래시 화면에서 진입 시 제외) - - 상태 바(상태 표시줄)도 Primary 색상으로 통일 - -2. 로고/타이틀 영역 (선택 사항) - - 앱 로고 또는 "쩝쩝박사" 텍스트 (Head1_bold 또는 Head2_bold) - - 중앙 정렬 또는 상단 정렬 - - Primary 색상 또는 Black 텍스트 - -3. 입력 필드 영역 - - 아이디/이메일 입력 필드 (OutlinedTextField 스타일) - - 플레이스홀더: "아이디 또는 이메일" - - White 배경, Gray700 테두리, Primary 포커스 색상 - - 둥근 모서리 (15px) - - 비밀번호 입력 필드 (OutlinedTextField 스타일) - - 플레이스홀더: "비밀번호" - - 비밀번호 표시/숨김 토글 아이콘 (오른쪽) - - White 배경, Gray700 테두리, Primary 포커스 색상 - - 둥근 모서리 (15px) - - 입력 필드 간 간격: 16dp - -4. 로그인 버튼 - - Primary 색상 배경 (#E10818), White 텍스트 - - 전체 너비 - - 둥근 모서리 (15px) - - 적절한 패딩 (세로 16dp) - - 텍스트: "로그인" (Body_semibold 또는 Sub1_semibold) - - 비활성화 상태: Gray700 배경, Gray700 텍스트 (입력 필드가 비어있을 때) - -5. 추가 옵션 - - "비밀번호 찾기" 링크 (Body_medium, Primary 색상, 오른쪽 정렬) - - 또는 "아이디 찾기" / "비밀번호 찾기" 두 개의 링크 (가로 배치) - -6. 회원가입 섹션 - - 구분선 또는 텍스트: "또는" (Body_medium, Gray700, 중앙 정렬) - - 회원가입 버튼: White 배경, Primary 테두리 (1px), Primary 텍스트 - - 전체 너비 - - 둥근 모서리 (15px) - - 텍스트: "회원가입" (Body_semibold 또는 Sub1_semibold) - -7. 소셜 로그인 (선택 사항) - - 구분선 또는 텍스트: "간편 로그인" (Body_medium, Gray700, 중앙 정렬) - - 소셜 로그인 버튼들 (가로 배치 또는 세로 배치) - - 카카오 로그인: 카카오 브랜드 색상 (#FEE500) - - 네이버 로그인: 네이버 브랜드 색상 (#03C75A) - - 구글 로그인: 구글 브랜드 색상 또는 White 배경 + 테두리 - - 각 버튼: 아이콘 + 텍스트 또는 아이콘만 - - 둥근 모서리 (15px) - -**레이아웃:** -- 화면 크기: 390x844 (Android 기본) -- 패딩: 16dp (좌우) -- 요소 간 간격: 16dp -- 섹션 간 간격: 24dp -- 중앙 정렬 또는 상단 정렬 (로그인 폼은 화면 중앙 또는 상단 1/3 지점에 배치) -- 스크롤 가능한 세로 레이아웃 (키보드가 올라올 때 대비) - -**스타일 가이드:** -- 입력 필드: White 배경, Gray700 테두리 (1px), Primary 포커스 색상, 15px 둥근 모서리 -- 버튼: Primary 배경, 15px 둥근 모서리, 적절한 패딩 -- 링크: Primary 색상, Body_medium 또는 Caption_medium -- 그림자: 버튼에 4px 4px 4px rgba(0,0,0,0.25) (선택 사항) - -**예원이 디자인한 기존 화면 스타일 (반드시 참고하여 동일한 톤 유지):** - -**홈 화면 디자인:** -- 상단 바: Primary 색상 (#E10818) 전체 배경, 높이 약 56dp - - 왼쪽: "쩝쩝박사" 로고 텍스트 (White, Head2_bold 또는 Head1_bold) - - 오른쪽: 검색 아이콘 (White, Material Icons 스타일) - - 상태 바(상태 표시줄)도 Primary 색상으로 통일 -- 탭 영역: 상단 바 바로 아래 - - "전체" / "추천" 두 개의 탭 (TabRow 스타일) - - 선택된 탭: Primary 색상 언더라인 또는 배경 - - 미선택 탭: Gray700 텍스트 -- 조합 카드 리스트: - - 각 카드: White 배경, Primary 테두리 (1px), 둥근 모서리 (15px) - - 카드 내용: 상단에 이미지 썸네일, 제목 (Head2_bold 또는 Sub1_semibold), 설명 (Body_medium), 하단에 해시태그 칩들 - - 해시태그 칩: Primary 배경 (#E10818), White 텍스트, 작은 크기 (약 20px 높이), 15px 둥근 모서리, 예시: "#서브웨이", "#편의점" - - 카드 간 간격: 12dp -- 하단 네비게이션: 5개 아이콘 (홈, 검색, 플러스, 하트, 마이페이지), 선택된 아이콘은 Primary 색상 - -**상세 화면 디자인:** -- 상단 바: Primary 색상 (#E10818) 전체 배경, 높이 약 56dp - - 왼쪽: 뒤로가기 아이콘 (White, Material Icons ArrowBack) - - 중앙: 제목 텍스트 (White, Head2_bold, 예: "건희 소스") - - 오른쪽: 하트 아이콘 (White, Material Icons Favorite) - - 상태 바(상태 표시줄)도 Primary 색상으로 통일 -- 탭 영역: 상단 바 바로 아래 - - "설명" / "사진" 두 개의 탭 (TabRow 스타일) - - 선택된 탭: Primary 색상 언더라인 또는 배경 - - 미선택 탭: Gray700 텍스트 -- 내용 영역: White 배경, 스크롤 가능 - - 제목: "사전" (Head2_bold 또는 Head1_bold) - - 불릿 포인트 리스트: Body_medium 또는 Body_semibold, 각 항목 앞에 불릿(•) 또는 번호 - -**스타일 일관성:** -- 모든 상단 바는 Primary 색상 배경, White 텍스트/아이콘 -- 입력 필드는 White 배경 + Gray700 테두리 + 15px 둥근 모서리 -- 버튼은 Primary 배경 + White 텍스트 + 15px 둥근 모서리 -- 모든 텍스트는 Pretendard 폰트 사용 - -**추가 고려 사항:** -- 키보드가 올라올 때 입력 필드가 가려지지 않도록 레이아웃 조정 -- 로그인 실패 시 에러 메시지 표시 영역 (빨간색 텍스트, Body_medium 또는 Caption_medium) -- 로딩 상태 표시 (로그인 버튼 클릭 시 CircularProgressIndicator 또는 버튼 내부에 표시) diff --git a/FIGMA_AI_PROMPT_MY_PAGE.md b/FIGMA_AI_PROMPT_MY_PAGE.md deleted file mode 100644 index fd5463e..0000000 --- a/FIGMA_AI_PROMPT_MY_PAGE.md +++ /dev/null @@ -1,84 +0,0 @@ -안드로이드 모바일 앱의 "마이페이지" 화면을 디자인해줘. - -**디자인 시스템 (기존 스타일 가이드 준수):** -- Primary 색상: #E10818 (빨간색) -- 배경: White (#FFFFFF) -- 텍스트: Black (#000000), Gray700 (#383838) -- 폰트: Pretendard (Bold, SemiBold, Medium) -- 타이포그래피: - - Head1_bold: 24px, Bold, lineHeight 28px - - Head2_bold: 22px, Bold, lineHeight 24px - - Sub1_semibold: 18px, SemiBold, lineHeight 20px - - Body_semibold: 14px, SemiBold, lineHeight 16px - - Caption_medium: 12px, Medium, lineHeight 14px - -**화면 구성 요소:** -1. 상단 바 (Top Bar) - - 배경색: Primary (#E10818) - - 제목: "마이페이지" 또는 앱 로고 (White 텍스트) - - 설정 아이콘 (오른쪽, White) - -2. 프로필 섹션 - - 프로필 이미지: 원형 (64dp), Primary 배경 또는 이미지 - - 사용자 이름: Head2_bold (22px) - - 통계 정보: "내가 등록한 조합 N개" (Body_medium, Gray700) - - 레이아웃: 프로필 이미지 + 텍스트 (왼쪽), 설정 아이콘 (오른쪽) - -3. 내가 등록한 조합 목록 - - 섹션 제목: "내가 등록한 조합" (Head2_bold) - - 조합 카드 리스트 (LazyColumn) - - 카드 스타일: White 배경, Primary 테두리 (1px), 둥근 모서리 (15px) - - 카드 내용: 썸네일 이미지, 제목, 설명, 좋아요 수, 작성일 - - 카드 간 간격: 12dp - - 빈 상태: "등록한 조합이 없습니다" 메시지 (중앙 정렬) - -**레이아웃:** -- 화면 크기: 390x844 (Android 기본) -- 패딩: 16dp (좌우) -- 요소 간 간격: 16dp -- 섹션 간 간격: 24dp -- 스크롤 가능한 세로 레이아웃 - -**스타일 가이드:** -- 카드/박스: White 배경, Primary 테두리 (1px), 둥근 모서리 (15px) -- 그림자: 4px 4px 4px rgba(0,0,0,0.25) -- 프로필 이미지: 원형, Primary 배경 또는 그라데이션 -- 아이콘: Material Icons 스타일, 적절한 크기 - -**예원이 디자인한 기존 화면 스타일 (반드시 참고하여 동일한 톤 유지):** - -**홈 화면 디자인:** -- 상단 바: Primary 색상 (#E10818) 전체 배경, 높이 약 56dp - - 왼쪽: "쩝쩝박사" 로고 텍스트 (White, Head2_bold 또는 Head1_bold) - - 오른쪽: 검색 아이콘 (White, Material Icons 스타일) - - 상태 바(상태 표시줄)도 Primary 색상으로 통일 -- 탭 영역: 상단 바 바로 아래 - - "전체" / "추천" 두 개의 탭 (TabRow 스타일) - - 선택된 탭: Primary 색상 언더라인 또는 배경 - - 미선택 탭: Gray700 텍스트 -- 조합 카드 리스트: - - 각 카드: White 배경, Primary 테두리 (1px), 둥근 모서리 (15px) - - 카드 내용: 상단에 이미지 썸네일, 제목 (Head2_bold 또는 Sub1_semibold), 설명 (Body_medium), 하단에 해시태그 칩들 - - 해시태그 칩: Primary 배경 (#E10818), White 텍스트, 작은 크기 (약 20px 높이), 15px 둥근 모서리, 예시: "#서브웨이", "#편의점" - - 카드 간 간격: 12dp -- 하단 네비게이션: 5개 아이콘 (홈, 검색, 플러스, 하트, 마이페이지), 선택된 아이콘은 Primary 색상 - -**상세 화면 디자인:** -- 상단 바: Primary 색상 (#E10818) 전체 배경, 높이 약 56dp - - 왼쪽: 뒤로가기 아이콘 (White, Material Icons ArrowBack) - - 중앙: 제목 텍스트 (White, Head2_bold, 예: "건희 소스") - - 오른쪽: 하트 아이콘 (White, Material Icons Favorite) - - 상태 바(상태 표시줄)도 Primary 색상으로 통일 -- 탭 영역: 상단 바 바로 아래 - - "설명" / "사진" 두 개의 탭 (TabRow 스타일) - - 선택된 탭: Primary 색상 언더라인 또는 배경 - - 미선택 탭: Gray700 텍스트 -- 내용 영역: White 배경, 스크롤 가능 - - 제목: "사전" (Head2_bold 또는 Head1_bold) - - 불릿 포인트 리스트: Body_medium 또는 Body_semibold, 각 항목 앞에 불릿(•) 또는 번호 - -**스타일 일관성:** -- 모든 상단 바는 Primary 색상 배경, White 텍스트/아이콘 -- 탭은 TabRow 스타일로 통일 -- 카드는 White 배경 + Primary 테두리 + 15px 둥근 모서리 -- 해시태그 칩은 Primary 배경 + White 텍스트로 통일 diff --git a/GIT_STRATEGY.md b/GIT_STRATEGY.md deleted file mode 100644 index 75b5653..0000000 --- a/GIT_STRATEGY.md +++ /dev/null @@ -1,177 +0,0 @@ -# Git 브랜치 전략 (해커톤용) - -## 기본 원칙 - -### 공통 코드 → main에 바로 푸시 -- 데이터 모델 (Entity, DTO) -- 테마 설정 (Color, Typography, Theme) -- 공통 컴포넌트 (CombinationCard, CategoryChip, SearchBar) -- 네비게이션 설정 -- 의존성 주입 모듈 - -### 개인 작업 → 브랜치 사용 -- 각자 담당 화면 구현 -- 관련 ViewModel, Repository - ---- - -## 브랜치 네이밍 전략 - -### 옵션 1: 이름만 사용 (추천 - 가장 간단) -``` -main -├── yeowon # 예원의 모든 작업 -└── ilhyeok # 일혁의 모든 작업 -``` - -**장점:** -- 가장 간단함 -- 브랜치 전환 빨리 가능 -- 해커톤에 적합 - -**단점:** -- 여러 기능을 한 브랜치에 작업 - -### 옵션 2: 이름 + 기능 (권장) -``` -main -├── yeowon/home-screen -├── yeowon/detail-screen -├── ilhyeok/create-screen -└── ilhyeok/my-page -``` - -**장점:** -- 기능별로 명확 -- 나중에 리뷰하기 쉬움 - -**단점:** -- 브랜치가 많아짐 - -### 옵션 3: 이름 + 간단한 기능 (절충안) -``` -main -├── yeowon/feature -└── ilhyeok/feature -``` - ---- - -## 추천 전략 (해커톤용) - -### 브랜치 구조 -``` -main (공통 코드) -├── yeowon (예원 작업) -└── ilhyeok (일혁 작업) -``` - -### 작업 흐름 - -1. **공통 작업 시작 전** - ```bash - git checkout main - git pull origin main - ``` - -2. **공통 코드 작업 후** - ```bash - git add . - git commit -m "feat: 공통 컴포넌트 추가" - git push origin main - ``` - -3. **개인 작업 시작** - ```bash - git checkout -b ilhyeok # 또는 yeowon - # 작업... - git add . - git commit -m "feat: 조합 등록 화면 구현" - git push origin ilhyeok - ``` - -4. **개인 작업 완료 후 main에 머지** - ```bash - git checkout main - git merge ilhyeok - git push origin main - ``` - ---- - -## 역할별 브랜치 - -### 예원 (yeowon) -- 홈 화면 (HomeScreen) -- 조합 상세 화면 (DiaryScreen) -- 관련 ViewModel, Repository - -### 일혁 (ilhyeok) -- 조합 등록 화면 (CreateCombinationScreen) -- 마이페이지 (MyScreen) -- 공통 컴포넌트 (이미 main에 있음) - ---- - -## 충돌 방지 팁 - -1. **작업 전 항상 pull** - ```bash - git checkout main - git pull origin main - ``` - -2. **브랜치 작업 전 main 최신화** - ```bash - git checkout ilhyeok - git merge main # 또는 rebase - ``` - -3. **작업 영역 분리** - - 예원: `presentation/screen/HomeScreen.kt`, `DiaryScreen.kt` - - 일혁: `presentation/screen/CreateCombinationScreen.kt`, `MyScreen.kt` - ---- - -## 커밋 메시지 컨벤션 (간단하게) - -``` -feat: 기능 추가 -fix: 버그 수정 -refactor: 리팩토링 -style: 스타일 변경 -chore: 기타 -``` - -예시: -- `feat: 조합 등록 화면 UI 구현` -- `feat: 마이페이지 프로필 섹션 추가` -- `fix: 네비게이션 버그 수정` - ---- - -## 최종 추천 - -**해커톤에서는 옵션 1 (이름만) 추천** - -이유: -- 시간 부족 -- 브랜치 전환 최소화 -- 충돌 가능성 낮음 (작업 영역이 명확히 분리됨) - -```bash -# 초기 설정 -git checkout -b yeowon -git checkout -b ilhyeok - -# 평소 작업 -git checkout ilhyeok -# 작업 후 -git push origin ilhyeok - -# 완료되면 main에 머지 -git checkout main -git merge ilhyeok -git push origin main -``` - diff --git a/PROJECT_MEMO.md b/PROJECT_MEMO.md deleted file mode 100644 index 77b9bb2..0000000 --- a/PROJECT_MEMO.md +++ /dev/null @@ -1,340 +0,0 @@ -# 해커톤 프로젝트 메모 - -## 📋 프로젝트 개요 - -**프로젝트명**: 음식점 꿀조합 공유 앱 -**기간**: -- 사전 작업: 2일 (준비 기간) -- 해커톤 본 대회: 1일 (18:00 입장 → 08:00 발표) -**팀 구성**: 안드로이드 개발자 2명 - -### 서비스 컨셉 -- 사용자들이 직접 레시피 등록 -- 서브웨이 조합, 하이디라오 소스 조합, 편의점 꿀조합 등 공유 -- 예시: 서브웨이 조합, 하이디라오 소스 조합, 편의점 꿀조합 - ---- - -## 👥 팀 상황 - -### 전체 팀 구성 (총 5명) -- **안드로이드**: 예원, 일혁 2명 - 디자인 + 프론트엔드 개발 -- **서버**: 2명 - API 개발 -- **PM**: 1명 - 기획 및 프로젝트 관리 - -### 작업 방식 -- 디자인: Figma로 와이어프레임 제작 (둘 다 참여) -- 개발: Jetpack Compose로 안드로이드 앱 개발 -- 협업: 병렬 작업 + 주기적 동기화 - ---- - -## 🛠 기술 스택 - -### 현재 프로젝트 상태 -- **언어**: Kotlin -- **UI**: Jetpack Compose (Material3) -- **아키텍처**: Clean Architecture (data/domain/presentation) -- **의존성 주입**: Hilt -- **네트워크**: Retrofit + OkHttp + Kotlinx Serialization -- **이미지 로딩**: Coil -- **네비게이션**: Navigation Compose - -### 프로젝트 구조 -``` -app/src/main/java/com/example/hackathon/ -├── core/component/ # 공통 컴포넌트 -├── data/ # 데이터 레이어 -│ ├── dto/ # Request/Response 모델 -│ ├── mapper/ # Entity ↔ DTO 변환 -│ ├── repositoryimpl/ # Repository 구현 -│ └── service/ # API 인터페이스 -├── domain/ # 도메인 레이어 -│ ├── entity/ # 비즈니스 엔티티 -│ └── repository/ # Repository 인터페이스 -├── presentation/ # 프레젠테이션 레이어 -│ ├── navigation/ # 네비게이션 설정 -│ ├── route/ # 라우트 정의 -│ └── screen/ # 화면 컴포저블 -├── di/ # 의존성 주입 모듈 -└── ui/theme/ # 테마 설정 -``` - -### 현재 구현된 화면 -- ✅ MainActivity (하단 네비게이션 포함) -- ✅ HomeScreen (기본 구조만) -- ✅ DiaryScreen (기본 구조만) - **변경 필요: 조합 상세 화면으로** -- ✅ Navigation 설정 완료 -- ✅ NetworkModule, ApiModule 설정 완료 - ---- - -## 📱 MVP 기능 정의 - -### 필수 기능 -1. **홈 화면**: 조합 목록 보기 - - 카테고리 필터 (서브웨이/하이디라오/편의점) - - 검색 기능 - - 조합 카드 리스트 - -2. **조합 상세 화면**: 조합 상세 정보 - - 이미지, 제목, 설명 - - 재료/비율 - - 만드는 방법 - - 좋아요, 댓글 - -3. **조합 등록 화면**: 새 조합 등록 - - 이미지 업로드 - - 제목, 카테고리, 재료 입력 - - 설명 입력 - - -### 선택 기능 (시간 남으면) -- 검색 기능 고도화 -- 좋아요 기능 -- 댓글 기능 -- 이미지 업로드 - ---- - -## 🎨 디자인 작업 가이드 - -### 핵심 원칙 -> **짧은 해커톤 기간 동안 모든 내용을 알고 있을 필요는 없습니다. 실제 해커톤에서는 Figma를 활용한 간단한 와이어프레임 작업 및 프로토타입 연결 정도만 알고 있어도 충분합니다!** - -### 1. 와이어프레임 개념 -- **로우-피델리티(Low Fidelity)** 상태로 재현 -- 간단한 모양만을 사용하여 인터페이스를 시각적으로 묘사 -- 구조, 콘텐츠, 기능 설명 -- **대부분의 디자인 요소(이미지, 비디오, 텍스트 등)가 포함되기 전 상태** -- 선, 자리 표시자 및 회색 음영 팔레트로 정보 아키텍처 표현 - -### 2. Figma 필수 단축키 -``` -V - 선택 도구 -R - 사각형 -T - 텍스트 -Ctrl + D - 복제 -Shift + A - 오토 레이아웃 (핵심!) -Ctrl + G - 그룹화 -Space + 드래그 - 화면 이동 -``` - -### 3. 오토 레이아웃 (Auto Layout) - 핵심 기능 -**단축키**: `Shift + A` - -**기능**: 오토 레이아웃 설정에 따라 레이아웃이 자동 변경 - -**사용법**: -1. 프레임 클릭 후 우측 패널의 **Auto layout** 클릭 -2. 방향, 도형 사이 간격, 가로 및 세로 여백 지정 -3. 프레임 길이 조절에 따라 이미지, 텍스트 길이도 유동적으로 조절되도록 하려면 **Fill로 설정** - -**반응형 카드 만들기**: 프레임 길이 조절에 따라 이미지, 텍스트 길이도 유동적으로 조절되도록 하려면 **Fill로 설정** - -### 4. 필수 플러그인 - -#### 필수 3개 -- **Material Symbols**: UI 제작에 필요한 아이콘 (메뉴, 이전 버튼, 닫기 버튼, 화살표 등) -- **한글입숨**: 더미텍스트 생성 -- **Autoflow**: 도형을 화살표로 연결 (Shift 누른 후 다음 도형 클릭하면, 화살표로 두 도형이 이어짐) - -#### 선택 2개 -- **Unsplash**: 고화질 무료 이미지 -- **Coolers**: 색상 정하기 - -### 5. 제공 파일 활용 - -#### 연습용 (선택) -- **KUIT PM 3주차_와이어프레임 sample.fig** (10.6MB) -- 1-2개 정도 클론 와이어프레임 만들어보면서 UI 위계, 버튼/박스 디자인, 폰트 크기 감 잡기 -- 실습은 **필수는 아님** - -#### 참고용 (필수) -- **KUIT 5기 해커톤 기획디자인 참고자료.fig** (54.4MB) -- Figma에 import해서 레이아웃/컴포넌트/컬러 참고 - -### 6. Figma 파일 Import 방법 -1. Figma 실행 (PC 앱 설치 권장) -2. Project에 들어가기 -3. 우측 상단 **[Import]** 클릭 -4. **[From your computer]** 선택 -5. 파일 선택 - -### 7. Material Design 활용 (안드로이드 개발자 권장) -- **URL**: https://m3.material.io/ -- "Build beautiful, usable products faster" -- 안드로이드 기본 디자인 시스템 -- 컴포넌트, 컬러, 타이포그래피 가이드 제공 - -### 8. 디자인 작업 체크리스트 -- [ ] Figma 기본 설정 (프레임 생성: 390x844 Android 기본) -- [ ] 참고 파일 import -- [ ] 필수 플러그인 설치 -- [ ] UI Style Guide 작성 -- [ ] 홈 화면 와이어프레임 -- [ ] 조합 상세 화면 와이어프레임 -- [ ] 조합 등록 화면 와이어프레임 -- [ ] 마이페이지 와이어프레임 -- [ ] 프로토타입 연결 (Autoflow 사용) - -### 9. UI Style Guide (예정) -``` -색상: -- Primary: #FF8A3D (주황) -- Secondary: #1F2A44 (다크 네이비) -- Background: #FFF4E8 (베이지) -- Text: #1F2A44 -- Success: #2ECC71 - -타이포그래피: -- 제목: 24sp, Bold -- 본문: 16sp, Regular -- 캡션: 12sp, Regular - -간격: -- 카드 패딩: 16dp -- 요소 간격: 8dp -- 섹션 간격: 24dp -``` - -### 10. 개발 소통 용어 -- **WireFrame**: 모크업, 레이아웃 초안 -- **Storyboard**: 디자이너/개발자가 참고하는 최종 산출 문서 (UI 및 개발 이슈 포함) -- **Prototype**: 실행 가능한 데모 - -### 11. 네이티브 앱 특징 -> "특정 모바일 플랫폼을 대상으로 하는 모든 앱을 네이티브 앱이라고 한다." - -> "동급 최고의 사용자 인터페이스 모듈을 통합한다. 이는 더 나은 성능, 일관성 및 우수한 사용자 경험을 설명한다." - ---- - -## 🔌 API 스펙 (서버팀과 협의 필요) - -### 필수 API 엔드포인트 -- [ ] `GET /combinations` - 조합 목록 조회 -- [ ] `GET /combinations/{id}` - 조합 상세 조회 -- [ ] `POST /combinations` - 조합 등록 -- [ ] `GET /users/me/combinations` - 내 조합 목록 -- [ ] `POST /combinations/{id}/like` - 좋아요 (선택) -- [ ] `GET /combinations/{id}/comments` - 댓글 조회 (선택) -- [ ] `POST /combinations/{id}/comments` - 댓글 작성 (선택) - -### Base URL -- 현재: `https://api.example.com/` (변경 필요) - ---- - -## 📅 작업 계획 - -### 사전 작업 Day 1: 기획 + 디자인 -**오전 (3-4시간)** -- [ ] 페인포인트 & MVP 기능 정의 -- [ ] 서버팀과 API 스펙 협의 -- [ ] Flow Chart 작성 -- [ ] 화면별 담당자 분담 - -**오후 (3-4시간)** -- [ ] Figma 기본 설정 (프레임 생성, 참고 파일 import) -- [ ] 필수 플러그인 설치 (Material Symbols, 한글입숨, Autoflow) -- [ ] UI Style Guide 작성 -- [ ] 와이어프레임 제작 (병렬 작업) -- [ ] 프로토타입 연결 (Autoflow 사용) - -### 사전 작업 Day 2: 개발 구현 -**오전 (4-5시간)** -- [ ] 데이터 모델 정의 -- [ ] API 인터페이스 정의 -- [ ] 테마 설정 (Material Design 3 참고) -- [ ] Repository + ViewModel 구현 -- [ ] 공통 컴포넌트 제작 - -**오후 (4-5시간)** -- [ ] 각 화면 UI 구현 -- [ ] ViewModel과 UI 연결 -- [ ] API 연동 -- [ ] 네비게이션 연결 - -**저녁 (1-2시간)** -- [ ] 통합 테스트 -- [ ] 버그 수정 -- [ ] 발표 자료 준비 - -### 해커톤 본 대회 (1일) -- 18:00 입장 -- 최종 통합 및 테스트 -- 버그 수정 -- 발표 준비 -- 08:00 발표 장소 이동 - ---- - -## 📝 역할 분담 (예정) - -### 예원 -- 홈 화면 (조합 목록) -- 조합 상세 화면 -- 관련 ViewModel, Repository - -### 일혁 -- 로그인 화면 -- 회원가입 화면 -- 온보딩 화면 -- 조합 등록 화면 -- 검색 화면 -- 공통 컴포넌트 (카드, 칩, 검색바 등) - ---- - -## 🚨 주의사항 - -1. **DiaryScreen 변경 필요**: 현재 "일기" 화면인데, "조합 상세" 화면으로 변경해야 함 -2. **하단 네비게이션 수정**: 현재 "홈/일기/마이페이지" → "홈/등록"으로 변경 필요 -3. **API Base URL**: 서버팀과 협의 후 변경 필요 -4. **더미 데이터**: API 준비 전까지 더미 데이터로 UI 먼저 완성 - ---- - -## 📚 참고 자료 - -### Figma 관련 -- **KUIT 5기 해커톤 기획디자인 참고자료.fig** (54.4MB) - 필수 참고 -- **KUIT PM 3주차_와이어프레임 sample.fig** (10.6MB) - 연습용 (선택) -- 필수 플러그인: Material Symbols, 한글입숨, Autoflow -- 선택 플러그인: Unsplash, Coolers - -### 디자인 시스템 참고 -- **Material Design 3**: https://m3.material.io/ (안드로이드 개발자 권장) -- LINE Design System -- millie Design Library 2.0 - ---- - -## ✅ 체크리스트 - -### 필수 작업 -- [ ] 서버팀과 API 스펙 확정 -- [ ] Figma 와이어프레임 완성 -- [ ] UI Style Guide 작성 -- [ ] 홈 화면 구현 -- [ ] 조합 상세 화면 구현 -- [ ] 조합 등록 기능 구현 -- [ ] 마이페이지 구현 -- [ ] API 연동 완료 - -### 발표 준비 (필수 항목) -- [ ] 페인포인트 (As-Is/To-Be) -- [ ] 서비스 Overview/MVP 설명 -- [ ] **UI Style Guide** (필수) -- [ ] **서비스 IA or Flow Chart** (필수) -- [ ] 프로토타입 설명 (시연 영상으로 대체 가능) - ---- - -## 📄 참고 문서 - -- `CONVENTION.md`: 코드 컨벤션 및 작업 규칙 -- `GIT_STRATEGY.md`: Git 브랜치 전략 -- `CHECKLIST.md`: 해커톤 작업 체크리스트 -- `ACTION_PLAN.md`: 지금 당장 해야 할 일 diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 18cca39..d5a982c 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -51,13 +51,16 @@ dependencies { implementation(libs.androidx.core.ktx) implementation(libs.androidx.lifecycle.runtime.ktx) implementation(libs.androidx.activity.compose) - implementation(libs.androidx.compose.material.icons.extended) implementation(libs.androidx.compose.foundation.layout) // Navigation implementation(libs.androidx.navigation.compose) implementation(libs.hilt.navigation.compose) + // 이미지 로딩 — Coil 네트워크 페처(OkHttp)는 런타임 ServiceLoader 로 자동 등록된다. + // UI 레이어(designsystem/feature)가 아닌 app 진입점에서만 클래스패스에 둬 의존 전파를 차단. + implementation(libs.coil.network.okhttp) + // Kotlin Metadata (메타데이터 버전 호환성 해결) implementation(libs.kotlin.metadata.jvm) diff --git a/app/src/main/java/com/example/hackathon/MainActivity.kt b/app/src/main/java/com/example/hackathon/MainActivity.kt index bfe8fed..dd5eeae 100644 --- a/app/src/main/java/com/example/hackathon/MainActivity.kt +++ b/app/src/main/java/com/example/hackathon/MainActivity.kt @@ -9,15 +9,7 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Add -import androidx.compose.material.icons.filled.Home -import androidx.compose.material.icons.filled.Person -import androidx.compose.material3.Icon -import androidx.compose.material3.NavigationBar -import androidx.compose.material3.NavigationBarItem import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.shadow @@ -29,11 +21,9 @@ import androidx.navigation.compose.rememberNavController import com.example.hackathon.core.designsystem.theme.HackathonTheme import com.example.hackathon.presentation.navigation.AppNavGraph import com.example.hackathon.presentation.navigation.BottomNavBar -import com.example.hackathon.presentation.navigation.BottomNavItem import com.example.hackathon.presentation.navigation.NavTab import com.example.hackathon.presentation.route.Route import dagger.hilt.android.AndroidEntryPoint -import javax.inject.Inject @AndroidEntryPoint class MainActivity : ComponentActivity() { diff --git a/app/src/main/java/com/example/hackathon/presentation/navigation/NavTab.kt b/app/src/main/java/com/example/hackathon/presentation/navigation/NavTab.kt index 600a6ca..08b03b0 100644 --- a/app/src/main/java/com/example/hackathon/presentation/navigation/NavTab.kt +++ b/app/src/main/java/com/example/hackathon/presentation/navigation/NavTab.kt @@ -1,6 +1,5 @@ package com.example.hackathon.presentation.navigation -import androidx.compose.material.icons.filled.Home import com.example.hackathon.core.designsystem.R import com.example.hackathon.presentation.route.Route diff --git a/build-logic/src/main/kotlin/AndroidFeatureConventionPlugin.kt b/build-logic/src/main/kotlin/AndroidFeatureConventionPlugin.kt index 94c1513..e4882c4 100644 --- a/build-logic/src/main/kotlin/AndroidFeatureConventionPlugin.kt +++ b/build-logic/src/main/kotlin/AndroidFeatureConventionPlugin.kt @@ -26,14 +26,9 @@ class AndroidFeatureConventionPlugin : Plugin { add("implementation", libs.findLibrary("androidx-core-ktx").get()) add("implementation", libs.findLibrary("androidx-lifecycle-runtime-ktx").get()) add("implementation", libs.findLibrary("androidx-activity-compose").get()) - add( - "implementation", - libs.findLibrary("androidx-compose-material-icons-extended").get() - ) add("implementation", libs.findLibrary("androidx-navigation-compose").get()) add("implementation", libs.findLibrary("hilt-navigation-compose").get()) add("implementation", libs.findLibrary("coil-compose").get()) - add("implementation", libs.findLibrary("coil-network-okhttp").get()) add("testImplementation", libs.findLibrary("junit").get()) add("testImplementation", libs.findLibrary("mockk").get()) diff --git a/core/designsystem/build.gradle.kts b/core/designsystem/build.gradle.kts index 9db114a..a4c4dcf 100644 --- a/core/designsystem/build.gradle.kts +++ b/core/designsystem/build.gradle.kts @@ -12,8 +12,7 @@ dependencies { implementation(libs.androidx.core.ktx) implementation(libs.androidx.compose.material3) - implementation(libs.androidx.compose.material.icons.extended) + implementation(libs.androidx.compose.material.icons.core) implementation(libs.androidx.compose.foundation.layout) implementation(libs.coil.compose) - implementation(libs.coil.network.okhttp) } diff --git a/core/designsystem/src/main/java/com/example/hackathon/core/designsystem/component/CombinationCard.kt b/core/designsystem/src/main/java/com/example/hackathon/core/designsystem/component/CombinationCard.kt index ad7c51a..af3ef98 100644 --- a/core/designsystem/src/main/java/com/example/hackathon/core/designsystem/component/CombinationCard.kt +++ b/core/designsystem/src/main/java/com/example/hackathon/core/designsystem/component/CombinationCard.kt @@ -6,9 +6,7 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Favorite -import androidx.compose.material.icons.outlined.FavoriteBorder +import com.example.hackathon.core.designsystem.icon.HackathonIcons import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.HorizontalDivider @@ -237,9 +235,9 @@ fun CombinationCard( Icon( imageVector = if (combination.isLiked) { - Icons.Filled.Favorite + HackathonIcons.Favorite } else { - Icons.Outlined.FavoriteBorder + HackathonIcons.FavoriteBorder }, contentDescription = "좋아요", tint = Primary, diff --git a/core/designsystem/src/main/java/com/example/hackathon/core/designsystem/component/SearchBar.kt b/core/designsystem/src/main/java/com/example/hackathon/core/designsystem/component/SearchBar.kt index 2ba7103..4de1c38 100644 --- a/core/designsystem/src/main/java/com/example/hackathon/core/designsystem/component/SearchBar.kt +++ b/core/designsystem/src/main/java/com/example/hackathon/core/designsystem/component/SearchBar.kt @@ -2,8 +2,7 @@ package com.example.hackathon.core.designsystem.component import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Search +import com.example.hackathon.core.designsystem.icon.HackathonIcons import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -25,7 +24,7 @@ fun SearchBar( placeholder = { Text(placeholder) }, leadingIcon = { Icon( - imageVector = Icons.Default.Search, + imageVector = HackathonIcons.Search, contentDescription = "검색" ) }, diff --git a/core/designsystem/src/main/java/com/example/hackathon/core/designsystem/icon/HackathonIcons.kt b/core/designsystem/src/main/java/com/example/hackathon/core/designsystem/icon/HackathonIcons.kt new file mode 100644 index 0000000..914303c --- /dev/null +++ b/core/designsystem/src/main/java/com/example/hackathon/core/designsystem/icon/HackathonIcons.kt @@ -0,0 +1,135 @@ +package com.example.hackathon.core.designsystem.icon + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.filled.Add +import androidx.compose.material.icons.filled.Close +import androidx.compose.material.icons.filled.Favorite +import androidx.compose.material.icons.filled.Search +import androidx.compose.material.icons.outlined.FavoriteBorder +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.unit.dp + +/** + * 앱 전역 아이콘 단일 소스. + * + * 수천 개의 미사용 아이콘으로 APK 를 키우는 material-icons-extended 를 제거하고, + * 실제 사용하는 아이콘만 이 객체로 모았다. feature/app 모듈은 material-icons 에 + * 직접 의존하지 않고 이 객체만 참조한다. + * + * - core 세트(material-icons-core)에 포함된 아이콘은 그대로 위임한다. + * - ArrowBack 은 RTL 자동 미러링되는 AutoMirrored 변형으로 통일(deprecated Filled.ArrowBack 회피). + * - core 에 없는 Visibility / VisibilityOff 만 원본(material-icons 1.7.8) path 를 + * public ImageVector DSL 로 재현해 직접 보유한다. + */ +object HackathonIcons { + val Add: ImageVector get() = Icons.Default.Add + val Search: ImageVector get() = Icons.Default.Search + val Close: ImageVector get() = Icons.Default.Close + val Favorite: ImageVector get() = Icons.Default.Favorite + val FavoriteBorder: ImageVector get() = Icons.Outlined.FavoriteBorder + val ArrowBack: ImageVector get() = Icons.AutoMirrored.Filled.ArrowBack + val Visibility: ImageVector get() = visibilityIcon + val VisibilityOff: ImageVector get() = visibilityOffIcon +} + +// ── 커스텀 ImageVector 2종 (material-icons-core 에 없는 Visibility/VisibilityOff) ── +// extended 1.7.8 의 Filled.Visibility/VisibilityOff path 를 그대로 옮겨 렌더 결과는 동일하다. +// (extended 의 materialIcon{}/materialPath{} 는 internal 이라 동등한 public API 로 재현) +// +// 읽는 법 — SVG 의 를 함수 호출로 1:1 옮긴 것이다: +// - viewportWidth/Height(24) = 좌표 격자. 아래 숫자들은 이 24x24 격자 안의 좌표다. +// defaultWidth/Height(24.dp) = 기본 표시 크기(실제론 Icon 의 Modifier.size 가 덮음). +// - path(fill = Black) = 도형을 채움. 화면 색은 Icon(tint=...) 이 덮으므로 검정은 형식상 기본값. +// - moveTo(x,y) = 펜을 (x,y) 로 이동(선 없이). 새 닫힌 도형(subpath) 시작. +// - lineTo / curveTo = 직선 / 3차 베지어 곡선 (대문자 = 절대 좌표). +// - ...Relative 접미사 = 현재 위치 기준 상대 좌표(dx,dy) (SVG 소문자 명령). +// - reflectiveCurve = 직전 곡선 제어점을 대칭 재사용해 매끄럽게 잇는 곡선 (SVG 의 s). +// - close() = 시작점으로 직선을 그어 도형을 닫음. +private val visibilityIcon: ImageVector = + ImageVector.Builder( + name = "HackathonIcons.Visibility", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + // 닫힌 도형 3개로 눈 모양을 그린다 (moveTo~close 가 3쌍). + path(fill = SolidColor(Color.Black)) { + // 도형 1: 바깥 눈 윤곽 + moveTo(12.0f, 4.5f) + curveTo(7.0f, 4.5f, 2.73f, 7.61f, 1.0f, 12.0f) + curveToRelative(1.73f, 4.39f, 6.0f, 7.5f, 11.0f, 7.5f) + reflectiveCurveToRelative(9.27f, -3.11f, 11.0f, -7.5f) + curveToRelative(-1.73f, -4.39f, -6.0f, -7.5f, -11.0f, -7.5f) + close() + // 도형 2: 가운데 원(홍채) + moveTo(12.0f, 17.0f) + curveToRelative(-2.76f, 0.0f, -5.0f, -2.24f, -5.0f, -5.0f) + reflectiveCurveToRelative(2.24f, -5.0f, 5.0f, -5.0f) + reflectiveCurveToRelative(5.0f, 2.24f, 5.0f, 5.0f) + reflectiveCurveToRelative(-2.24f, 5.0f, -5.0f, 5.0f) + close() + // 도형 3: 안쪽 원(동공) + moveTo(12.0f, 9.0f) + curveToRelative(-1.66f, 0.0f, -3.0f, 1.34f, -3.0f, 3.0f) + reflectiveCurveToRelative(1.34f, 3.0f, 3.0f, 3.0f) + reflectiveCurveToRelative(3.0f, -1.34f, 3.0f, -3.0f) + reflectiveCurveToRelative(-1.34f, -3.0f, -3.0f, -3.0f) + close() + } + }.build() + +// VisibilityOff = 위 Visibility 와 같은 방식. 눈 + 대각선 슬래시까지 닫힌 도형 4개로 구성. +private val visibilityOffIcon: ImageVector = + ImageVector.Builder( + name = "HackathonIcons.VisibilityOff", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(12.0f, 7.0f) + curveToRelative(2.76f, 0.0f, 5.0f, 2.24f, 5.0f, 5.0f) + curveToRelative(0.0f, 0.65f, -0.13f, 1.26f, -0.36f, 1.83f) + lineToRelative(2.92f, 2.92f) + curveToRelative(1.51f, -1.26f, 2.7f, -2.89f, 3.43f, -4.75f) + curveToRelative(-1.73f, -4.39f, -6.0f, -7.5f, -11.0f, -7.5f) + curveToRelative(-1.4f, 0.0f, -2.74f, 0.25f, -3.98f, 0.7f) + lineToRelative(2.16f, 2.16f) + curveTo(10.74f, 7.13f, 11.35f, 7.0f, 12.0f, 7.0f) + close() + moveTo(2.0f, 4.27f) + lineToRelative(2.28f, 2.28f) + lineToRelative(0.46f, 0.46f) + curveTo(3.08f, 8.3f, 1.78f, 10.02f, 1.0f, 12.0f) + curveToRelative(1.73f, 4.39f, 6.0f, 7.5f, 11.0f, 7.5f) + curveToRelative(1.55f, 0.0f, 3.03f, -0.3f, 4.38f, -0.84f) + lineToRelative(0.42f, 0.42f) + lineTo(19.73f, 22.0f) + lineTo(21.0f, 20.73f) + lineTo(3.27f, 3.0f) + lineTo(2.0f, 4.27f) + close() + moveTo(7.53f, 9.8f) + lineToRelative(1.55f, 1.55f) + curveToRelative(-0.05f, 0.21f, -0.08f, 0.43f, -0.08f, 0.65f) + curveToRelative(0.0f, 1.66f, 1.34f, 3.0f, 3.0f, 3.0f) + curveToRelative(0.22f, 0.0f, 0.44f, -0.03f, 0.65f, -0.08f) + lineToRelative(1.55f, 1.55f) + curveToRelative(-0.67f, 0.33f, -1.41f, 0.53f, -2.2f, 0.53f) + curveToRelative(-2.76f, 0.0f, -5.0f, -2.24f, -5.0f, -5.0f) + curveToRelative(0.0f, -0.79f, 0.2f, -1.53f, 0.53f, -2.2f) + close() + moveTo(11.84f, 9.02f) + lineToRelative(3.15f, 3.15f) + lineToRelative(0.02f, -0.16f) + curveToRelative(0.0f, -1.66f, -1.34f, -3.0f, -3.0f, -3.0f) + lineToRelative(-0.17f, 0.01f) + close() + } + }.build() diff --git a/feature/auth/src/main/java/com/example/hackathon/feature/auth/LoginScreen.kt b/feature/auth/src/main/java/com/example/hackathon/feature/auth/LoginScreen.kt index 88b65c7..0c4fb8b 100644 --- a/feature/auth/src/main/java/com/example/hackathon/feature/auth/LoginScreen.kt +++ b/feature/auth/src/main/java/com/example/hackathon/feature/auth/LoginScreen.kt @@ -19,9 +19,7 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Visibility -import androidx.compose.material.icons.filled.VisibilityOff +import com.example.hackathon.core.designsystem.icon.HackathonIcons import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults @@ -231,9 +229,9 @@ fun LoginScreen( Icon( imageVector = if (uiState.isPasswordVisible) { - Icons.Default.VisibilityOff + HackathonIcons.VisibilityOff } else { - Icons.Default.Visibility + HackathonIcons.Visibility }, contentDescription = if (uiState.isPasswordVisible) { diff --git a/feature/auth/src/main/java/com/example/hackathon/feature/auth/RegistrationScreen.kt b/feature/auth/src/main/java/com/example/hackathon/feature/auth/RegistrationScreen.kt index a16c95a..481dae1 100644 --- a/feature/auth/src/main/java/com/example/hackathon/feature/auth/RegistrationScreen.kt +++ b/feature/auth/src/main/java/com/example/hackathon/feature/auth/RegistrationScreen.kt @@ -17,9 +17,7 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Visibility -import androidx.compose.material.icons.filled.VisibilityOff +import com.example.hackathon.core.designsystem.icon.HackathonIcons import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults @@ -230,9 +228,9 @@ fun RegistrationScreen( Icon( imageVector = if (uiState.isPasswordVisible) { - Icons.Default.VisibilityOff + HackathonIcons.VisibilityOff } else { - Icons.Default.Visibility + HackathonIcons.Visibility }, contentDescription = if (uiState.isPasswordVisible) { @@ -288,9 +286,9 @@ fun RegistrationScreen( Icon( imageVector = if (uiState.isPasswordConfirmVisible) { - Icons.Default.VisibilityOff + HackathonIcons.VisibilityOff } else { - Icons.Default.Visibility + HackathonIcons.Visibility }, contentDescription = if (uiState.isPasswordConfirmVisible) { diff --git a/feature/combination/src/main/java/com/example/hackathon/feature/combination/CreateCombinationScreen.kt b/feature/combination/src/main/java/com/example/hackathon/feature/combination/CreateCombinationScreen.kt index 92d7eea..b9e21c4 100644 --- a/feature/combination/src/main/java/com/example/hackathon/feature/combination/CreateCombinationScreen.kt +++ b/feature/combination/src/main/java/com/example/hackathon/feature/combination/CreateCombinationScreen.kt @@ -22,8 +22,7 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBack +import com.example.hackathon.core.designsystem.icon.HackathonIcons import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.CircularProgressIndicator @@ -103,7 +102,7 @@ fun CreateCombinationScreen( modifier = Modifier.size(48.dp) ) { Icon( - imageVector = Icons.AutoMirrored.Filled.ArrowBack, + imageVector = HackathonIcons.ArrowBack, contentDescription = "뒤로가기" ) } diff --git a/feature/combination/src/main/java/com/example/hackathon/feature/combination/DetailScreen.kt b/feature/combination/src/main/java/com/example/hackathon/feature/combination/DetailScreen.kt index 3f97e4d..c9e4cf9 100644 --- a/feature/combination/src/main/java/com/example/hackathon/feature/combination/DetailScreen.kt +++ b/feature/combination/src/main/java/com/example/hackathon/feature/combination/DetailScreen.kt @@ -22,10 +22,7 @@ import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBack -import androidx.compose.material.icons.filled.Favorite -import androidx.compose.material.icons.outlined.FavoriteBorder +import com.example.hackathon.core.designsystem.icon.HackathonIcons import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon @@ -88,7 +85,7 @@ fun DetailScreen( modifier = Modifier.size(48.dp) ) { Icon( - imageVector = Icons.AutoMirrored.Filled.ArrowBack, + imageVector = HackathonIcons.ArrowBack, contentDescription = "뒤로가기" ) } @@ -229,9 +226,9 @@ fun DetailScreen( Icon( imageVector = if (recipe.userInteraction.isLiked) { - Icons.Filled.Favorite + HackathonIcons.Favorite } else { - Icons.Outlined.FavoriteBorder + HackathonIcons.FavoriteBorder }, contentDescription = "좋아요", tint = diff --git a/feature/combination/src/main/java/com/example/hackathon/feature/combination/component/HashTagInputSection.kt b/feature/combination/src/main/java/com/example/hackathon/feature/combination/component/HashTagInputSection.kt index 73fde89..c4f0714 100644 --- a/feature/combination/src/main/java/com/example/hackathon/feature/combination/component/HashTagInputSection.kt +++ b/feature/combination/src/main/java/com/example/hackathon/feature/combination/component/HashTagInputSection.kt @@ -6,8 +6,7 @@ import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Add +import com.example.hackathon.core.designsystem.icon.HackathonIcons import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -95,7 +94,7 @@ fun HashTagInputSection( } ) { Icon( - imageVector = Icons.Default.Add, + imageVector = HackathonIcons.Add, contentDescription = "태그 추가" ) } diff --git a/feature/combination/src/main/java/com/example/hackathon/feature/combination/component/ImageUploadSection.kt b/feature/combination/src/main/java/com/example/hackathon/feature/combination/component/ImageUploadSection.kt index ada50b3..14b7271 100644 --- a/feature/combination/src/main/java/com/example/hackathon/feature/combination/component/ImageUploadSection.kt +++ b/feature/combination/src/main/java/com/example/hackathon/feature/combination/component/ImageUploadSection.kt @@ -15,9 +15,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Add -import androidx.compose.material.icons.filled.Close +import com.example.hackathon.core.designsystem.icon.HackathonIcons import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme @@ -137,7 +135,7 @@ fun ImageUploadSection( contentAlignment = Alignment.Center ) { Icon( - imageVector = Icons.Default.Close, + imageVector = HackathonIcons.Close, contentDescription = "이미지 제거", tint = Color.White, modifier = Modifier.size(20.dp) @@ -174,7 +172,7 @@ fun ImageUploadSection( .size(20.dp) ) { Icon( - imageVector = Icons.Default.Close, + imageVector = HackathonIcons.Close, contentDescription = "이미지 제거", tint = Color.White, modifier = @@ -205,7 +203,7 @@ fun ImageUploadSection( contentAlignment = Alignment.Center ) { Icon( - imageVector = Icons.Default.Add, + imageVector = HackathonIcons.Add, contentDescription = "이미지 추가", tint = Gray700, modifier = Modifier.size(24.dp) @@ -235,7 +233,7 @@ fun ImageUploadSection( contentAlignment = Alignment.Center ) { Icon( - imageVector = Icons.Default.Add, + imageVector = HackathonIcons.Add, contentDescription = "이미지 추가", tint = Gray700, modifier = Modifier.size(24.dp) diff --git a/feature/profile/src/main/java/com/example/hackathon/feature/profile/EditProfileScreen.kt b/feature/profile/src/main/java/com/example/hackathon/feature/profile/EditProfileScreen.kt index 7ed7eb2..48415c2 100644 --- a/feature/profile/src/main/java/com/example/hackathon/feature/profile/EditProfileScreen.kt +++ b/feature/profile/src/main/java/com/example/hackathon/feature/profile/EditProfileScreen.kt @@ -14,8 +14,6 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.CircularProgressIndicator @@ -41,10 +39,10 @@ import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import coil3.compose.AsyncImage +import com.example.hackathon.core.designsystem.icon.HackathonIcons import com.example.hackathon.core.designsystem.theme.Gray700 import com.example.hackathon.core.designsystem.theme.HackathonTheme import com.example.hackathon.core.designsystem.theme.Primary -import com.example.hackathon.feature.profile.EditProfileViewModel @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -78,7 +76,7 @@ fun EditProfileScreen( navigationIcon = { IconButton(onClick = onNavigateBack) { Icon( - imageVector = Icons.Default.ArrowBack, + imageVector = HackathonIcons.ArrowBack, contentDescription = "뒤로가기", tint = Color.White ) diff --git a/feature/profile/src/main/java/com/example/hackathon/feature/profile/MyScreen.kt b/feature/profile/src/main/java/com/example/hackathon/feature/profile/MyScreen.kt index 3980b8f..69b9985 100644 --- a/feature/profile/src/main/java/com/example/hackathon/feature/profile/MyScreen.kt +++ b/feature/profile/src/main/java/com/example/hackathon/feature/profile/MyScreen.kt @@ -6,7 +6,6 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets @@ -15,23 +14,15 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.Divider import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.Scaffold import androidx.compose.material3.Tab import androidx.compose.material3.TabRow @@ -45,7 +36,6 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource -import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel @@ -53,13 +43,10 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import coil3.compose.AsyncImage import com.example.hackathon.core.designsystem.R import com.example.hackathon.core.designsystem.component.CombinationCard -import com.example.hackathon.core.designsystem.component.TopAppLogoBar import com.example.hackathon.core.designsystem.theme.Gray50 import com.example.hackathon.core.designsystem.theme.Gray700 import com.example.hackathon.core.designsystem.theme.HackathonTheme import com.example.hackathon.core.designsystem.theme.Primary -import com.example.hackathon.feature.profile.MyPageTab -import com.example.hackathon.feature.profile.MyPageViewModel @OptIn(ExperimentalMaterial3Api::class) @Composable diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ea6e407..28cdad7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -41,7 +41,7 @@ androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "u androidx-compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } androidx-compose-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" } -androidx-compose-material-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended" } +androidx-compose-material-icons-core = { group = "androidx.compose.material", name = "material-icons-core" } androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigationCompose" } okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp" } okhttp-logging-interceptor = { group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref = "okhttp" }