# 작업: 기존 화면 4개를 새 디자인으로 다시 꾸미기 ## 목표 - 저장소 목록, 알림, 저장소별 이슈/PR, 설정 화면의 **모양만** 새 디자인 컴포넌트로 바꾼다. 동작과 콜백은 그대로 둔다. ## 배경 - 작업 위치: `D:\Android\UBGitea\.claude\worktrees\awesome-pascal-3f2ada` (git 워크트리). **이 폴더 안에서만 작업한다.** - Android 앱(Kotlin, Jetpack Compose, Material 3). 앱은 하단 탭 4개(홈/저장소/작업/알림) 구조로 바뀌었다. 탭과 시스템 바 여백은 바깥의 `AppShell` 이 처리한다. - 이미 새 디자인으로 만든 화면이 있다. **이 두 파일을 본보기로 삼아 같은 모양을 만든다**: - `app/src/main/java/com/ubisam/ubgitea/feature/work/WorkScreen.kt` (탭 화면: 상단 브랜드 바 + 제목 + 필터 칩 + 카드형 목록) - `app/src/main/java/com/ubisam/ubgitea/feature/history/HistoryScreen.kt` (하위 화면: 카드형 행, 상태 배지) - 공통 컴포넌트: `app/src/main/java/com/ubisam/ubgitea/ui/components/UBGiteaTopBar.kt` (`BrandTopBar`, `ScreenTopBar`, `TabTitle`, `SectionTitle`), `.../ui/components/Elements.kt` (`UBIcon`, `Avatar`, `KindTile`, `StatusBadge`, `MonoChip`, `ListCard`, `CenteredLoading`, `CenteredMessage`, `CautionBanner`, `ActivityTone`). - 아이콘: `app/src/main/res/drawable/ic_ub_*.xml` (home, repo, pull_request, merge, branch, commit, issue, bell, star, star_filled, search, back, chevron_right, chevron_down, chevron_up, folder, file, lock, comment, eye, list, copy, more, close, tag, settings, logout, refresh, check 등). `UBIcon(R.drawable.ic_ub_xxx, contentDescription)` 로 쓴다. - 상대 시간: `com.ubisam.ubgitea.ui.formatRelative(epochMillis)` (예: "12분 전"). - **Gradle 은 실행하지 않는다.** 다른 작업자가 같은 폴더에서 테스트를 돌리고 있다. 컴파일 확인은 메인 세션이 한다. 대신 import 누락과 괄호 짝을 꼼꼼히 확인한다. ## 결정사항 (변경 금지) - 각 화면 최상위 Composable 의 **함수 이름과 매개변수 목록은 그대로** 둔다 (호출하는 `MainActivity.kt` 를 고치지 않기 위해). 예외: 아래에 명시한 매개변수 추가만 허용. - ViewModel, UiState, 도메인 모델은 고치지 않는다. - 색은 하드코딩하지 않는다. `MaterialTheme.colorScheme.*`, `UBGiteaTheme.colors.*` (`com.ubisam.ubgitea.ui.theme.UBGiteaTheme`), `ActivityTone` 만 쓴다. - 상태 색 규칙: 열림 = `ActivityTone.SUCCESS`, 병합됨 = `ActivityTone.MERGED`, 닫힘 = `ActivityTone.DANGER`, 알 수 없음/비공개 = `ActivityTone.NEUTRAL`, 공개 = `ActivityTone.BRAND`. - 사용자에게 보이는 글자는 전부 문자열 리소스. 새 문자열은 해당 화면의 기존 `app/src/main/res/values/strings_<화면>.xml` 파일 끝에 추가한다. 한국어. - 유니코드 기호(★ ☆ ☰)를 아이콘 대신 쓰던 곳은 `UBIcon` 으로 바꾼다. - 누를 수 있는 것은 높이 48dp 이상. 아이콘만 있는 버튼은 `contentDescription` 필수. - 화면 안에서 `Scaffold` 를 쓰지 않는다 (바깥 `AppShell` 이 이미 처리). 최상위는 `Column(modifier.fillMaxSize())`. - 목록 카드 모양: 페이지 배경(`colorScheme.background`) 위에 흰 카드(`colorScheme.surface`), 모서리 20dp, 좌우 여백 20dp, 행 사이 `HorizontalDivider(color = colorScheme.outlineVariant)`. LazyColumn 에서는 `WorkScreen.kt` 의 `WorkRow` 처럼 행마다 `isFirst`/`isLast` 로 모서리를 둥글린다. - 코드 스타일: 읽기 쉬운 명시적인 코드. 주석은 "왜"만, 기존 주석 중 여전히 맞는 것은 남긴다. ## 수정/생성할 파일 - `app/src/main/java/com/ubisam/ubgitea/feature/repositories/RepositoryListScreen.kt` : 수정 - `app/src/main/java/com/ubisam/ubgitea/feature/notifications/NotificationsScreen.kt` : 수정 (매개변수 3개 추가) - `app/src/main/java/com/ubisam/ubgitea/feature/workitems/WorkItemsScreen.kt` : 수정 - `app/src/main/java/com/ubisam/ubgitea/feature/settings/SettingsScreen.kt` : 수정 (매개변수 2개 추가) - `app/src/main/res/values/strings_repositories.xml`, `strings_notifications.xml`, `strings_workitems.xml`, `strings_settings.xml` : 문자열 추가 - `app/src/main/java/com/ubisam/ubgitea/MainActivity.kt` : 아래 3, 5번에 적은 호출부 인자 추가만 ## 구체 작업 1. **RepositoryListScreen.kt** - 최상위의 `Scaffold(topBar = BrandTopBar(...))` 를 `Column` 으로 바꾸고 `BrandTopBar(state.accountNameOrNull(), instanceHost, onViewSettings, onLogout)` 를 맨 위에 둔다. - 로그인 전 상태(`SignedOut`, `WaitingForBrowser`)에서는 `BrandTopBar` 를 보이지 않고, 화면 가운데에 로고(`Image(painterResource(R.drawable.logo_ubisam), ...)`, 높이 36dp), 안내 문구, 버튼을 세로로 둔다. - `Loading` → `CenteredLoading`, 오류/빈 상태 → `CenteredMessage` (기존 문구와 버튼 동작 유지. `debugDetail` 표시도 유지). - `RepositoryContent`: 헤더를 `TabTitle(stringResource(R.string.repositories_list_title))` 로, `ModeTabs`(전체 저장소/즐겨찾기)와 `OrganizationFilterRow` 는 유지하되 흰 배경 헤더 영역(`Surface(color = surface, shape = RoundedCornerShape(bottomStart = 28.dp, bottomEnd = 28.dp))`) 안에 넣는다. 좌우 스와이프로 탭을 바꾸는 `modeSwipeGesture` 는 그대로 둔다. - 저장소 행: `owner / name` (owner 는 흐린 색, name 은 굵게), 공개/비공개 `StatusBadge` (비공개면 앞에 `ic_ub_lock` 은 생략 가능), 설명(있으면, 최대 2줄), 언어 + `MonoChip(마지막 push 브랜치, icon = ic_ub_branch)` + `formatRelative(updatedAtEpochMillis)`, 오른쪽에 즐겨찾기 버튼. - `LastPushedBranchRow` 의 Loading/Empty/Failed 상태와 재시도 동작은 유지한다. - `FavoriteStar`: `★/☆` 글자를 `UBIcon(ic_ub_star_filled / ic_ub_star)` 로. 채워진 별 색은 `UBGiteaTheme.colors.brand`, 실패는 `colorScheme.error`. 별 개수 숫자 유지. 48dp 터치 영역. contentDescription 은 기존 문자열 `repositories_favorite` / `repositories_unfavorite`. - `OfflineCacheBanner` → `CautionBanner`. - `ReferencesContent`(저장소를 고른 뒤 브랜치/태그 목록): 맨 위에 `ScreenTopBar(title = 저장소 이름, subtitle = owner, onBack = onBackToRepositories)` 를 두고 actions 에 PR/이슈 버튼(`ic_ub_pull_request`, `onViewWorkItems`). 로그아웃 버튼은 제거 (프로필 메뉴에 있음). 이 상태에서는 `BrandTopBar` 를 보이지 않는다. `ReferencesContent` 에 `onBackToRepositories` 를 넘기도록 내부 함수 시그니처를 바꿔도 된다. - `ReferenceRow`: 브랜치는 `ic_ub_branch`, 태그는 `ic_ub_tag` 아이콘, 이름은 모노스페이스, 커밋 id 는 `MonoChip`, 시각은 `formatRelative`, 보호된 브랜치는 `StatusBadge(..., ActivityTone.WARNING)`. 2. **NotificationsScreen.kt** - 매개변수 추가: `accountName: String?`, `instanceHost: String`, `onViewSettings: () -> Unit`, `onLogout: () -> Unit` (4개. `modifier` 앞에). - `Scaffold` 제거. `BrandTopBar(accountName, instanceHost, onViewSettings, onLogout)` + 흰 헤더 영역에 `TabTitle(stringResource(R.string.notifications_title))` 와 필터(읽지 않음/전체, `FilterChip` 2개로). - 행: 읽지 않음이면 왼쪽에 지름 9dp 점(`UBGiteaTheme.colors.brand`, contentDescription 은 기존 `notifications_unread_label` 의 대괄호를 뺀 새 문자열), `KindTile`(이슈 `ic_ub_issue`, PR `ic_ub_pull_request`, 병합된 PR `ic_ub_merge`, 커밋 `ic_ub_commit`, 저장소 `ic_ub_repo`), 저장소 이름 + `#번호` 모노스페이스, 제목(읽지 않음이면 굵게), `StatusBadge`, 오른쪽 위에 `formatRelative(updatedAtEpochMillis)`. - 누를 수 있는 조건(`navigable`)은 기존과 같다. 3. **MainActivity.kt 의 NotificationsScreen 호출부**에 `accountName = accountName, instanceHost = instanceHost, onViewSettings = { push(Route.Settings) }, onLogout = ::signOut,` 를 추가한다. 다른 곳은 건드리지 않는다. 4. **WorkItemsScreen.kt** (저장소 하나의 이슈/PR 목록과 상세) - `Scaffold` 제거. 맨 위에 `ScreenTopBar`. 제목은 저장소 이름(`state` 의 `target.repository.name`), subtitle 은 owner. 상세 화면에서는 제목을 `#번호`, subtitle 을 저장소 이름으로. `onBack` 은 목록 상태에서는 `onBack`, 상세 상태에서는 `onBackToList`. `WorkItemsUiState.Idle` 에서는 제목 없이 빈 화면. - 이슈/PR 탭(`ModeTabs`)은 유지. - 목록 행은 `WorkScreen.kt` 의 `WorkRow` 와 같은 모양 (KindTile + StatusBadge + 제목 + 작성자 · 상대 시간 + 댓글 수). - 상세: 제목과 상태 배지, 작성자(`Avatar`), 본문은 `ListCard` 안에. PR 은 `MonoChip(headRef, ic_ub_branch)` → `MonoChip(baseRef, ic_ub_branch)`, 병합 가능 여부 배지. 리뷰와 댓글은 `SectionTitle` 아래 `ListCard` 목록, 각 항목에 `Avatar` + 작성자 + 상대 시간 + 본문. - `NumberedPageFooter` 와 페이지 이동 입력 로직은 그대로 둔다 (모양만 유지). 5. **SettingsScreen.kt** - 매개변수 추가: `accountName: String?`, `instanceHost: String`, `onSetCodeWordWrapEnabled: (Boolean) -> Unit`, `onSetCodeFontSizeSp: (Int) -> Unit`, `onLogout: () -> Unit` (`onBack` 앞에). - `Scaffold` 제거. `ScreenTopBar(title = stringResource(R.string.settings_title), onBack = onBack)`. - 섹션 3개를 `SectionTitle` + `ListCard` 로: "계정"(Avatar, 계정 이름, 접속 서버 주소 모노스페이스, 로그아웃 버튼), "코드 보기"(줄바꿈 스위치 = `state.codeWordWrapEnabled`, 글자 크기 = `AppSettingsStore.CODE_FONT_SIZES_SP` 를 `FilterChip` 으로 나열, 선택값 `state.codeFontSizeSp`), "동작"(기존 스위치 2개). - `MainActivity.kt` 의 `SettingsScreen` 호출부에 `accountName = accountName, instanceHost = instanceHost, onSetCodeWordWrapEnabled = settingsViewModel::setCodeWordWrapEnabled, onSetCodeFontSizeSp = settingsViewModel::setCodeFontSizeSp, onLogout = ::signOut,` 추가. 6. 쓰지 않게 된 문자열 리소스와 import 는 지운다 (lint `UnusedResources` 경고 방지). 단 다른 파일에서 쓰는지 Grep 으로 확인 후 지운다. ## 건드리지 말 것 - `app/src/test/` 전체, `feature/home/`, `feature/work/`, `feature/codebrowser/`, `feature/history/`, `feature/branchdetail/`, `ui/`, `data/`, `domain/`, `core/`. - `MainActivity.kt` 는 3, 5번의 호출부 인자 추가 외에는 고치지 않는다. - Gradle 실행, git commit/push/stash 금지. ## 완료 기준 - 4개 화면 모두 `Scaffold` 없이 새 컴포넌트를 사용. - 하드코딩된 색(`Color(0x...)`)과 한글/영문 리터럴 문자열이 화면 코드에 없음. - 각 파일의 import 가 실제 사용과 일치. ## 보고 형식 - 파일별로 무엇을 바꿨는지 3줄 이내 - 추가/삭제한 문자열 리소스 이름 - 확신이 없는 부분 (컴파일이 안 될 수 있다고 생각되는 곳) ## 막히면 - 추측해서 진행하지 말고 멈춘 뒤, 무엇이 불명확한지 보고할 것