Four tabs in an app shell that becomes a rail from 600dp, a brighter sky-blue palette with the real UbiSam logo, a Home tab built on Gitea's activity feeds, a Work tab that searches issues and pull requests across repositories, a full-screen code viewer with search, outline and Visual Studio colors, C/C++ header/source pairing, and two-pane layouts for wide windows. The five tasks were built and checked together, so they are one commit: MainActivity, the routes and the shared components belong to all of them. 409 unit tests pass, lint reports 0 errors. Verified on an emulator up to the sign-in screen only; no physical device was available. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
11 KiB
11 KiB
작업: 기존 화면 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번에 적은 호출부 인자 추가만
구체 작업
-
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).
- 최상위의
-
NotificationsScreen.kt
- 매개변수 추가:
accountName: String?,instanceHost: String,onViewSettings: () -> Unit,onLogout: () -> Unit(4개.modifier앞에). Scaffold제거.BrandTopBar(accountName, instanceHost, onViewSettings, onLogout)+ 흰 헤더 영역에TabTitle(stringResource(R.string.notifications_title))와 필터(읽지 않음/전체,FilterChip2개로).- 행: 읽지 않음이면 왼쪽에 지름 9dp 점(
UBGiteaTheme.colors.brand, contentDescription 은 기존notifications_unread_label의 대괄호를 뺀 새 문자열),KindTile(이슈ic_ub_issue, PRic_ub_pull_request, 병합된 PRic_ub_merge, 커밋ic_ub_commit, 저장소ic_ub_repo), 저장소 이름 +#번호모노스페이스, 제목(읽지 않음이면 굵게),StatusBadge, 오른쪽 위에formatRelative(updatedAtEpochMillis). - 누를 수 있는 조건(
navigable)은 기존과 같다.
- 매개변수 추가:
-
MainActivity.kt 의 NotificationsScreen 호출부에
accountName = accountName, instanceHost = instanceHost, onViewSettings = { push(Route.Settings) }, onLogout = ::signOut,를 추가한다. 다른 곳은 건드리지 않는다. -
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와 페이지 이동 입력 로직은 그대로 둔다 (모양만 유지).
-
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,추가.
- 매개변수 추가:
-
쓰지 않게 된 문자열 리소스와 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줄 이내
- 추가/삭제한 문자열 리소스 이름
- 확신이 없는 부분 (컴파일이 안 될 수 있다고 생각되는 곳)
막히면
- 추측해서 진행하지 말고 멈춘 뒤, 무엇이 불명확한지 보고할 것