Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ Uses [vanniktech/gradle-maven-publish-plugin](https://github.com/vanniktech/grad

Maven coordinates: `io.github.ois0886:compose-git-grass:<version>`

Current release: `1.1.1`
Current release: `1.2.0`

### 로컬 배포 (수동)

Expand Down Expand Up @@ -129,7 +129,8 @@ git push origin v<version>
- `library/src/main/java/com/inseong/gitgrass/`
- `GitGrass.kt` — 메인 컴포저블 (public API)
- `GitGrassColors.kt` — 색상 스킴 데이터 클래스
- `GitGrassComponents.kt` — 내부 UI 컴포넌트 (YearLabel, MonthRow, WeekLabelColumn, GrassGridContent, Canvas 기반 GrassWeekColumn, GrassCell hit target, StreakSummary, ColorLegend)
- `GitGrassSelection.kt` — 제어형 선택 상태 public API와 선택값 정규화
- `GitGrassComponents.kt` — 내부 UI 컴포넌트 (YearLabel, MonthLabelSlot, WeekLabelColumn, 단일 LazyRow 기반 GrassGridContent, Canvas 기반 GrassWeekColumn, GrassCell hit target, StreakSummary, ColorLegend)
- `GitGrassDefaults.kt` — 기본값 및 팩토리 (색상, 라벨, 크기, 로케일, 레이아웃 상수)
- `GridUtils.kt` — 순수 함수 유틸리티 (normalizeDateRange, normalizeContributions, generateDayList, buildGrid, dayIndexInWeek, weekDaysOrdered, createMonthLabels, formatYearLabel, calculateStreak)
- `RenderData.kt` — 셀별 count/color/접근성 라벨을 미리 계산하는 렌더링 데이터 유틸리티
Expand All @@ -144,8 +145,8 @@ git push origin v<version>
- `RenderDataTest.kt` — 렌더링 데이터 사전 계산 검증
- `GridBenchmarkTest.kt` — 성능 벤치마크 (6개)
- `library/src/androidTest/` — Compose UI 인스트루먼트 테스트
- `GrassCellTest.kt` — 셀 클릭/롱클릭 콜백 검증
- `LabelRenderingTest.kt` — 월 라벨, 주 라벨 렌더링 검증
- `GrassCellTest.kt` — 셀 클릭/롱클릭, 선택 semantics 검증
- `LabelRenderingTest.kt` — 단일 LazyRow 월 라벨, 주 시작일별 라벨 렌더링 검증

## Code Quality

Expand Down Expand Up @@ -226,3 +227,4 @@ docs: AGENTS.md 워크플로우 규칙 추가
- 2026-04-10: CI `ui-test` 실패 원인(`GrassCellTest`의 `performLongClick` 참조 오류) 수정 후, GitHub Actions 런 #24225465922 전체 잡 통과를 확인함.
- 2026-04-10: CI `ui-test` 안정성 강화를 위해 잡 타임아웃(30분)과 에뮬레이터 `-no-metrics` 옵션을 추가함.
- 2026-06-11: `v1.1.1` 릴리즈 기준으로 README/CHANGELOG/AGENTS/CLAUDE 문서의 버전 및 릴리즈 절차 표기를 재점검함.
- 2026-07-15: `v1.2.0` 릴리즈 기준으로 제어형 선택 API, 단일 LazyRow 렌더링, 샘플 쇼케이스와 README 라이트/다크 이미지를 반영함.
24 changes: 22 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,27 @@

## [Unreleased]

## [1.2.0] - 2026-07-15

### Added
- `GitGrassSelection`: 날짜, 인셋 아웃라인 색상/두께를 담는 제어형 선택 상태 API 추가
- `GitGrass.selection`: 앱 상태로 선택 셀을 강조하고 `selected` semantics를 노출하는 옵션 추가
- `GitGrassDefaults.weekLabelsFor()`: 주 시작일에 맞춰 캐시된 영어 요일 라벨을 반환하는 헬퍼 추가
- 선택 해석, 월 경계, 완전한 주 단위 초기 스크롤 및 샘플 통계 테스트 추가

### Changed
- `1.1.1` 릴리즈 기준으로 README/AGENTS/CLAUDE 문서의 현재 버전 표기와 릴리즈 절차 안내를 정리
- 월 라벨과 주별 Canvas를 하나의 `LazyRow` 항목으로 통합해 스크롤 동기화 구조 단순화
- 월 라벨을 매월 1일이 속한 주에 배치하고 첫 주 충돌 시 새 월을 우선하도록 개선
- 최신 날짜 자동 스크롤을 월 라벨이 잘리지 않는 완전한 주 열 기준으로 변경
- `weekStartDay` 변경 시 기본 영어 요일 라벨도 같은 순서로 자동 정렬
- 선택 변경 시 render grid를 재계산하지 않고 보이는 Canvas와 semantics만 갱신
- 샘플 앱을 GitHub 라이트/다크 대표 데모와 `Layout`/`Localization`/`Themes`/`Levels` 갤러리로 재구성
- README 설치/API/선택 예제와 실제 에뮬레이터 라이트·다크 이미지를 1.2.0 기준으로 갱신
- README/AGENTS/CLAUDE/CODE_QUALITY의 버전과 내부 구조 설명 동기화

### Fixed
- 롱클릭 전용 셀에 빈 클릭 액션이 함께 노출되던 접근성 semantics 수정
- 일요일 시작 그래프가 기본 월요일 순서 라벨을 표시하던 불일치 수정

## [1.1.1] - 2026-06-11

Expand Down Expand Up @@ -115,7 +134,8 @@
- 월 라벨 위치 결정 (`createMonthLabels`)
- 연도 라벨 포맷팅 (`formatYearLabel`)

[Unreleased]: https://github.com/ois0886/compose-git-grass/compare/v1.1.1...HEAD
[Unreleased]: https://github.com/ois0886/compose-git-grass/compare/v1.2.0...HEAD
[1.2.0]: https://github.com/ois0886/compose-git-grass/compare/v1.1.1...v1.2.0
[1.1.1]: https://github.com/ois0886/compose-git-grass/compare/v1.1.0...v1.1.1
[1.1.0]: https://github.com/ois0886/compose-git-grass/compare/v1.0.0...v1.1.0
[1.0.0]: https://github.com/ois0886/compose-git-grass/compare/v0.1.1...v1.0.0
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
## Quick Facts

- 배포 좌표: `io.github.ois0886:compose-git-grass:<version>`
- 현재 릴리즈: `1.1.1`
- 현재 릴리즈: `1.2.0`
- 라이브러리 모듈: `:library` (`com.inseong.gitgrass`)
- 샘플 앱 모듈: `:app`

Expand Down
19 changes: 10 additions & 9 deletions CODE_QUALITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ compose-git-grass 프로젝트의 코드 퀄리티 가이드라인.

- **Public API는 최소화**한다. 외부에 노출할 필요가 없는 모든 것은 `internal`로 선언한다.
- Public: `GitGrass`, `GitGrassColors`, `GitGrassDefaults`, `GitGrassStreakInfo`
- Internal: UI 컴포넌트 (`YearLabel`, `MonthRow`, `GrassCell` 등), 유틸리티 함수 (`buildGrid`, `calculateStreak` 등)
- Internal: UI 컴포넌트 (`YearLabel`, `MonthLabelSlot`, `GrassCell` 등), 유틸리티 함수 (`buildGrid`, `calculateStreak` 등)

### 데이터 클래스

Expand Down Expand Up @@ -77,7 +77,8 @@ fun GitGrass(...) {
3. 스타일 (colors, cellSize, fontSize)
4. UI 토글 (showYearLabel, showLegend)
5. 텍스트 라벨 (streakMaxLabel, lessLabel)
6. 람다/콜백 (levelOf, onCellClick, onCellLongClick)
6. 매핑/제어 상태 (levelOf, selection)
7. 람다/콜백 (onCellClick, onCellLongClick)
```

### remember 사용 규칙
Expand Down Expand Up @@ -156,10 +157,9 @@ UI 렌더링 (Composable + remember)
```
GitGrass (루트 조율자)
├── YearLabel
├── MonthRow (LazyListState 공유)
├── WeekLabelColumn
├── GrassGridContent (LazyListState 공유)
│ └── GrassWeekColumn (Canvas) → GrassCell (hit target)
├── GrassGridContent (단일 LazyRow)
│ └── MonthLabelSlot + GrassWeekColumn (Canvas) → GrassCell (hit target)
├── StreakSummary
└── ColorLegend
```
Expand Down Expand Up @@ -287,12 +287,14 @@ val renderGrid = remember(grid, safeContributions, colors, levelOf) {

### Lazy 렌더링 우선

주 단위처럼 반복 개수가 날짜 범위에 비례하는 UI는 eager `Row`보다 `LazyRow`를 우선한다. 월 라벨과 그리드는 같은 `LazyListState`를 공유해 정렬과 스크롤 동기화를 유지한다.
주 단위처럼 반복 개수가 날짜 범위에 비례하는 UI는 eager `Row`보다 `LazyRow`를 우선한다. 월 라벨과 주별 Canvas는 같은 lazy item 안에 배치해 별도 상태 동기화 없이 정렬을 보장한다.

### Canvas와 Semantics 분리

반복 셀의 시각 렌더링은 Canvas로 합치되, 접근성/클릭/롱클릭은 별도의 hit target 컴포저블로 유지한다. 사용자가 렌더러를 직접 선택하는 public 옵션은 실제 요구가 생기기 전까지 추가하지 않는다.

선택 아웃라인처럼 자주 바뀌는 제어 상태는 `buildRenderGrid` 입력에 넣지 않는다. 보이는 Canvas에서 날짜를 비교해 그리며 hit target에는 `selected` semantics를 별도로 적용한다.

### Consumer Rules 범위 제한

AAR consumer ProGuard 규칙은 앱 전체에 영향을 주므로 `com.inseong.gitgrass.**`처럼 라이브러리 패키지 범위로 한정한다. 전역 `class *` keep 규칙은 피한다.
Expand Down Expand Up @@ -566,7 +568,6 @@ fun GitGrass(...) {
val grid = remember(...) { buildGrid(days, weekStartDay) }
Column {
YearLabel(...)
MonthRow(...)
GrassGridContent(...)
}
}
Expand All @@ -592,8 +593,8 @@ fun GitGrass(...) {
// O: 컴포지션 — 작은 컴포저블 조합
GitGrass (루트 조율자)
├── YearLabel // 독립 컴포넌트
├── MonthRow // LazyListState를 파라미터로 주입
├── GrassGridContent // 렌더링 데이터와 LazyListState를 파라미터로 주입
├── WeekLabelColumn // 고정 요일 라벨
├── GrassGridContent // 월 라벨과 주별 Canvas를 하나의 LazyRow로 구성
└── ColorLegend // 독립 컴포넌트

// X: 상속 기반 접근
Expand Down
71 changes: 62 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ Jetpack Compose에서 사용할 수 있는 가볍고 독립적인 GitHub 잔디(
[![API](https://img.shields.io/badge/API-26%2B-brightgreen.svg)](https://developer.android.com/about/versions/oreo)

<p align="center">
<img src="screenshots/sample.png" width="500" alt="compose-git-grass sample" />
<img src="screenshots/sample.png" width="46%" alt="compose-git-grass light showcase with a selected contribution day" />
<img src="screenshots/sample-dark.png" width="46%" alt="compose-git-grass dark showcase with a selected contribution day" />
</p>

## Why?
Expand All @@ -25,16 +26,16 @@ GitHub의 잔디 그래프는 활동량을 한눈에 보여주는 훌륭한 시

## Setup

현재 최신 릴리즈는 `1.1.1`입니다.
현재 최신 릴리즈는 `1.2.0`입니다.

```kotlin
// build.gradle.kts
dependencies {
implementation("io.github.ois0886:compose-git-grass:1.1.1")
implementation("io.github.ois0886:compose-git-grass:1.2.0")
}
```

> **1.0.0에서 마이그레이션**: 새 파라미터(`cellContentDescription`, `cellClickLabel`)가 추가되었으며 모두 기본값이 있어 기존 코드가 그대로 동작합니다. `GitGrassColors.border`는 deprecated 되었습니다 (2.0.0에서 제거 예정).
> **1.1.x에서 업그레이드**: `selection` 파라미터가 기본값과 함께 추가되어 기존 호출은 그대로 동작합니다. 일요일 시작 그래프의 기본 요일 라벨은 이제 자동으로 일요일부터 정렬됩니다. `GitGrassColors.border`는 계속 deprecated 상태이며 2.0.0에서 제거될 예정입니다.

## Quick Start

Expand All @@ -48,6 +49,21 @@ GitGrass(
)
```

```kotlin
// 제어 가능한 날짜 선택
var selectedDate by remember { mutableStateOf<LocalDate?>(null) }

GitGrass(
contributions = data,
selection = selectedDate?.let { date ->
GitGrassSelection(date = date)
},
onCellClick = { date, _ ->
selectedDate = date
},
)
```

```kotlin
// 다크모드 + 스트릭 + 셀 클릭 + 범례
GitGrass(
Expand All @@ -66,6 +82,7 @@ GitGrass(
대부분의 1년 단위 그래프는 기본 설정만으로 충분히 가볍게 동작합니다. 여러 개의 그래프를 한 화면에 렌더링하거나 5년 이상의 긴 범위를 표시한다면 아래 원칙을 권장합니다:

- 별도 `renderMode`를 고를 필요는 없습니다. 내부에서 주 단위 lazy 렌더링과 Canvas 기반 셀 그리기를 자동으로 사용합니다.
- 월 라벨과 잔디 셀은 하나의 `LazyRow`에서 함께 구성되므로 별도의 스크롤 동기화 비용이 없습니다.
- `contributions`, `startDate`, `endDate`, 로케일 라벨, 커스텀 색상 팔레트는 `remember`나 ViewModel 상태로 안정적으로 전달하세요.
- 필요 없는 보조 UI는 `showMonthLabels`, `showStreak`, `showLegend`를 `false`로 꺼두면 해당 계산과 렌더링 비용을 줄일 수 있습니다.
- 매 리컴포지션마다 `LocalDate.now()`, `localizedMonthLabels()`, `GitGrassColors(...)`를 새로 만들기보다 한 번 계산한 값을 재사용하세요.
Expand All @@ -87,6 +104,29 @@ GitGrass(

## Customization

### Controlled Selection (1.2.0 신규)

`GitGrass`는 선택 상태를 내부에서 변경하지 않습니다. 앱 상태를 [GitGrassSelection](#gitgrassselection)으로 전달하고 클릭 콜백에서 갱신하세요.

```kotlin
var selectedDate by remember { mutableStateOf(LocalDate.now()) }

GitGrass(
contributions = data,
selection = GitGrassSelection(
date = selectedDate,
outlineColor = Color.Blue,
outlineWidth = 2.dp,
),
onCellClick = { date, count ->
selectedDate = date
println("$date: $count")
},
)
```

선택 날짜가 표시 범위 밖이면 강조하지 않습니다. 선택 변경만으로 스크롤 위치가 바뀌지는 않으며, `outlineColor = Color.Unspecified`일 때는 현재 색상 스킴의 `text` 색상을 사용합니다.

### Colors

원하는 색상과 레벨 수를 자유롭게 지정할 수 있습니다:
Expand Down Expand Up @@ -153,10 +193,11 @@ GitGrass(
contributions = data,
weekStartDay = DayOfWeek.SUNDAY,
startDate = GitGrassDefaults.startDate(DayOfWeek.SUNDAY),
weekLabels = GitGrassDefaults.localizedWeekLabels(DayOfWeek.SUNDAY),
)
```

영어 기본 라벨은 `weekStartDay`를 자동으로 따릅니다. 로컬라이즈된 라벨이 필요할 때만 `GitGrassDefaults.localizedWeekLabels(DayOfWeek.SUNDAY)`를 전달하면 됩니다.

### Long Press (1.0.0 신규)

```kotlin
Expand Down Expand Up @@ -204,7 +245,7 @@ GitGrass(
| `weekStartDay` | `DayOfWeek` | MONDAY | 주 시작 요일 |
| `colors` | `GitGrassColors` | GitHub 라이트 | 색상 스킴 |
| `monthLabels` | `List<String>` | 영어 | 월 이름 (인덱스 0 미사용) |
| `weekLabels` | `List<String>` | 영어 | 요일 이름 |
| `weekLabels` | `List<String>` | 주 시작일 기준 영어 | 요일 이름 |
| `cellSize` | `Dp` | 12.dp | 셀 크기 |
| `cellSpacing` | `Dp` | 3.dp | 셀 간격 |
| `cellCornerRadius` | `Dp` | 2.dp | 셀 모서리 반경 |
Expand All @@ -221,6 +262,7 @@ GitGrass(
| `cellContentDescription` | `(LocalDate, Int) -> String` | `"$date: $count"` | 셀 접근성 텍스트 (1.1.0) |
| `cellClickLabel` | `(LocalDate) -> String` | `"$date details"` | 셀 클릭 접근성 라벨 (1.1.0) |
| `levelOf` | `(Int) -> Int` | 0/1-3/4-6/7-9/10+ | 기여 횟수 → 레벨 매핑 |
| `selection` | `GitGrassSelection?` | null | 제어 가능한 선택 셀과 인셋 아웃라인 (1.2.0) |
| `onCellClick` | `((LocalDate, Int) -> Unit)?` | null | 셀 탭 콜백 |
| `onCellLongClick` | `((LocalDate, Int) -> Unit)?` | null | 셀 롱프레스 콜백 |

Expand All @@ -233,6 +275,14 @@ GitGrass(
| `text` | 라벨 텍스트 색상 |
| `border` | ~~셀 테두리 색상~~ (deprecated, 2.0.0에서 제거 예정) |

### `GitGrassSelection`

| Field | Default | Description |
|---|---|---|
| `date` | *필수* | 선택해 강조할 날짜 |
| `outlineColor` | `Color.Unspecified` | 인셋 아웃라인 색상 (`colors.text`로 대체) |
| `outlineWidth` | 2.dp | 인셋 아웃라인 두께 (셀 크기에 맞춰 안전하게 제한) |

### Forgiving Input

잘못된 입력도 크래시 없이 안전하게 처리합니다:
Expand All @@ -246,6 +296,8 @@ GitGrass(

- 모든 셀에 `contentDescription` 제공 (스크린 리더 지원)
- 클릭 가능한 셀에 `Role.Button` 및 `onClickLabel` 설정
- 선택된 셀에 `selected` semantics 제공
- 롱클릭 전용 셀에는 빈 클릭 액션 없이 롱클릭 semantics만 제공
- 그래프 루트에 "Contribution graph" semantics 적용
- 범례 셀에 "Level 0", "Level 1" 등 접근성 라벨 제공
- `cellContentDescription`, `cellClickLabel` 파라미터로 접근성 텍스트 커스터마이징 가능 (1.1.0)
Expand All @@ -265,10 +317,11 @@ GitGrass(
- **유연한 색상 레벨** - 고정된 `level1`~`level4` 대신 `levels: List<Color>`를 사용합니다. 3단계든, 5단계든, 10단계든 자유롭게 지정할 수 있습니다.
- **픽셀 단위 월 라벨 정렬** - 월 라벨이 그리드와 동일한 `Arrangement.spacedBy` + 고정 너비 슬롯 구조를 사용하여, 어떤 셀 크기에서도 정확하게 정렬됩니다.
- **자동 최적화 렌더링** - 긴 날짜 범위에서는 주 단위 `LazyRow`가 화면에 필요한 열만 구성하고, 셀 색상은 Canvas로 그려 반복 노드 비용을 줄입니다.
- **공유 LazyListState** - 월 라벨 행과 그리드가 하나의 `LazyListState`를 공유하여 가로 스크롤 시 항상 동기화됩니다.
- **순수 함수 & 테스트** - 그리드/스트릭 계산 로직이 부수효과 없는 순수 함수로 구현되어 있으며, 60+ 유닛 테스트와 Compose UI 테스트가 그리드 생성, 스트릭 계산, 색상 매핑, 엣지 케이스, UI 인터랙션을 검증합니다.
- **단일 LazyRow** - 월 라벨과 주별 Canvas를 같은 lazy item에서 구성해 스크롤 정합성을 구조적으로 보장합니다.
- **선택 렌더링 분리** - 선택 아웃라인은 render grid를 다시 만들지 않고 보이는 Canvas와 semantics만 갱신합니다.
- **순수 함수 & 테스트** - 그리드/스트릭/월 경계/초기 스크롤 계산이 순수 함수로 구현되어 있으며, 유닛 테스트와 Compose UI 테스트가 엣지 케이스와 인터랙션을 검증합니다.
- **접근성** - 모든 셀에 contentDescription, semantics 적용으로 스크린 리더를 지원합니다.
- **오늘 날짜로 자동 스크롤** - `LaunchedEffect`로 첫 컴포지션 시 가장 최근 날짜(오른쪽 끝)로 자동 스크롤됩니다.
- **완전한 주 단위 자동 스크롤** - 첫 컴포지션에서 최신 날짜를 유지하면서 시작 월 라벨이 잘리지 않는 완전한 주 열로 스냅합니다.

## Requirements

Expand Down
Loading