Skip to content

Commit 56a7843

Browse files
authored
Merge pull request #4362 from CruGlobal/databindingMigration
Migrate ArticlesFragment from DataBinding to Compose
2 parents 934bcd5 + 5bc2026 commit 56a7843

11 files changed

Lines changed: 284 additions & 252 deletions

File tree

.claude/skills/pr-review/SKILL.md

Lines changed: 153 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,95 +1,187 @@
11
---
22
name: pr-review
3-
description: This skill should be used when the user asks to review a pull request, check code quality, run a PR review, or asks "is this PR ready to merge?" for the godtools-android project. It performs a structured review checking project-specific patterns learned from historical PR feedback.
4-
version: 1.0.0
3+
description: Review a pull request against GodTools Android project conventions. Use when asked to review a PR, check code quality, or audit changes.
4+
argument-hint: [pr-number]
5+
allowed-tools: Bash, Read, Grep, Glob, Write, Edit
56
---
67

7-
# GodTools Android PR Review
8+
Review pull request $ARGUMENTS against the GodTools Android project conventions.
89

9-
Perform a structured pull request review for the godtools-android project. This skill applies patterns learned from historical code reviews and the project's established conventions.
10+
## Steps
1011

11-
## Review Workflow
12+
1. Check for dismissed issues by reading `.claude/skills/pr-review/dismissed-issues.md` if it exists.
13+
Load all dismissed entries — each has a **Pattern** and **Reason**. You will use these to suppress matching findings later.
1214

13-
### Step 1: Get the Diff
15+
2. Fetch the PR diff and metadata:
16+
```
17+
gh pr diff $ARGUMENTS
18+
gh pr view $ARGUMENTS
19+
```
1420

15-
```bash
16-
# If a PR exists
17-
gh pr diff
21+
3. Identify all changed files and categorize them (Kotlin, build scripts, resources, manifests, tests).
1822

19-
# Otherwise compare to base branch
20-
git diff develop...HEAD --name-only
21-
git diff develop...HEAD
23+
4. Pre-flight check — run ktlint. This is a hard blocker:
24+
```
25+
./gradlew :build-logic:ktlintCheck ktlintCheck
2226
```
27+
If it fails, report as **Must Fix** before reviewing anything else.
2328

24-
Identify all changed files and categorize them by type (Kotlin, build scripts, resources, manifests, tests).
29+
5. Review each category using the checklist below.
2530

26-
### Step 2: Pre-Flight Check — ktlint
31+
6. Before outputting, cross-reference every finding against dismissed patterns. A finding matches a dismissed pattern when it describes the same class of issue (not necessarily the exact file/line — match by concept). Move matched findings to a separate suppressed list.
2732

28-
Always verify ktlint passes. This is a hard blocker:
33+
7. Output a structured review (format below).
34+
35+
8. After the review output, print:
2936

30-
```bash
31-
./gradlew :build-logic:ktlintCheck ktlintCheck
3237
```
38+
---
39+
To dismiss a finding so it won't appear in future reviews, say:
40+
dismiss: <short title> — <reason>
41+
```
42+
43+
---
44+
45+
## Review Checklist
46+
47+
### Build Scripts (`build.gradle.kts`)
48+
49+
- [ ] `godtools.library-conventions` / `godtools.application-conventions` applied — do not re-declare `minSdk`, `compileSdk`, `targetSdk`, Kotlin toolchain, or proguard rules
50+
- [ ] Compose enabled via `configureCompose(project)` in `android {}` — do not manually add Compose BOM or Circuit deps
51+
- [ ] No redundant `buildFeatures` flags already covered by the convention plugin
52+
- [ ] New modules use `godtools.library-conventions`, `godtools.application-conventions`, or `godtools.dynamic-feature-conventions` as appropriate
53+
- [ ] KSP used for new modules; `kapt` only if DataBinding is still present (see TODO comment pattern)
54+
55+
### `gradle/libs.versions.toml`
56+
57+
- [ ] No duplicate version keys
58+
- [ ] No unused `[plugins]` entries (common AS wizard artifact)
59+
- [ ] No unused `[libraries]` entries
60+
- [ ] All new dependencies declared here, not hardcoded in `build.gradle.kts`
61+
62+
### Android Resources & Manifests
63+
64+
- [ ] No AS wizard scaffolding in library modules: `colors.xml`, `strings.xml` with `app_name`, default launcher icons, per-module theme definitions
65+
- [ ] Library `AndroidManifest.xml` does not declare `application` attributes (theme, label, icon) — those belong in `:app`
66+
- [ ] Hardcoded strings extracted to `strings.xml` (inline string literals in composables are flagged)
67+
- [ ] No `app_name` or generic color/theme resources in library modules
68+
69+
### `settings.gradle.kts`
70+
71+
- [ ] No local `includeBuild` substitutions for `kotlin-mpp-godtools-tool-parser` committed — publish artifact and update version in `libs.versions.toml` instead
72+
73+
### Jetpack Compose
74+
75+
- [ ] Every public or internal composable has `modifier: Modifier = Modifier` parameter
76+
- [ ] Always uses `GodToolsTheme` from `:ui:base` — no per-module `MaterialTheme(colorScheme = …)` wrappers
77+
- [ ] Colors accessed via `MaterialTheme.colorScheme.*` or `GodToolsTheme.extendedColorScheme`
78+
- [ ] CPU-heavy work (bitmap generation, etc.) done off the main thread via `produceState { withContext(Dispatchers.IO) { … } }`
3379

34-
If it fails, report as **Critical** — PR cannot merge until resolved.
80+
### Circuit Presenter/UI Patterns
3581

36-
### Step 3: Run the Checklist
82+
**Presenter**
83+
- [ ] Uses `@AssistedInject` constructor; `Navigator`/`Screen` injected via `@Assisted`
84+
- [ ] Contains nested `Factory` interface annotated with `@AssistedFactory` and `@CircuitInject(<Screen>::class, SingletonComponent::class)`
85+
- [ ] `UiState` is a `data class` implementing `CircuitUiState`
86+
- [ ] `UiEvent` is a `sealed interface` implementing `CircuitUiEvent`, marked `internal`
87+
- [ ] `UiState` and `UiEvent` defined as nested types inside the Presenter
88+
- [ ] `UiState` exposes `val eventSink: (UiEvent) -> Unit`
89+
- [ ] Presenter contains no UI logic — pure state/event handling
3790

38-
For each changed file, apply the relevant checks from `references/patterns.md`. Use the priority levels below to triage findings:
91+
**UI Composable**
92+
- [ ] Annotated with `@CircuitInject(<Screen>::class, SingletonComponent::class)`
93+
- [ ] Signature: `(state: <Presenter>.UiState, modifier: Modifier = Modifier)`
94+
- [ ] All user interactions delegated via `state.eventSink(UiEvent.*)` — no direct function calls from UI
3995

40-
- **Critical** — Hard blocker, must fix before merge
41-
- **Important** — Should fix in this PR
42-
- **Suggestion** — Nice to have, can be deferred
96+
**Screen**
97+
- [ ] Annotated with `@Parcelize`
98+
- [ ] Is an `object` or `data class` implementing `Screen`
4399

44-
### Step 4: PR Hygiene Check
100+
### Cross-Module Activity / Intent Creation
101+
102+
- [ ] `Intent`/`PendingIntent` creation for Activities uses extension functions in `ui/base/src/main/kotlin/org/cru/godtools/base/ui/Activities.kt` (string class names, no hard compile-time deps between sibling UI modules)
103+
104+
### Repository & DAO Patterns
105+
106+
- [ ] DAOs use `suspend fun` for single-shot queries, `Flow<T>` for reactive queries
107+
- [ ] `@Upsert` for sync operations
108+
- [ ] `@RewriteQueriesToDropUnusedColumns` when selecting partial projections
109+
110+
### WorkManager
111+
112+
- [ ] Workers annotated with `@HiltWorker` + `@AssistedInject`
113+
- [ ] Extend `CoroutineWorker` for suspend support
114+
- [ ] Catch `IOException` (specific), not `Exception` (broad)
115+
116+
### Kotlin Code Quality
117+
118+
- [ ] Logging uses `Timber` — no `println` or `Log.*`
119+
- [ ] Exception handling catches specific types, not bare `Exception`
120+
- [ ] Visibility is intentional: `internal` for module-scoped symbols, `private` where possible
121+
- [ ] Multi-branch conditionals use `when`, not chained `if/else`
122+
- [ ] `Bundle`/`Intent` extra keys are `const val` shared between producer and consumer
123+
124+
### Testing
125+
126+
- [ ] Presenter tests use `presenterTestOf { }` (Circuit test API)
127+
- [ ] Paparazzi tests extend `BasePaparazziTest` from `:ui:base` testFixtures with `@TestParameter` night/accessibility matrix
128+
- [ ] Snapshots not recorded locally — triggered via GitHub Actions workflow on the feature branch
129+
130+
### PR Hygiene
131+
132+
- [ ] No unrelated auto-formatter whitespace changes (check with `git diff develop...HEAD --stat`)
133+
- [ ] KMP tool UI changes (app bar, lesson/tract rendering) belong in `kotlin-mpp-godtools-tool-parser`, not Android renderer modules
134+
135+
For detailed examples of each pattern, see `references/patterns.md`.
136+
137+
---
138+
139+
## Output Format
45140

46-
```bash
47-
git diff develop...HEAD --stat
48141
```
142+
## PR Review: <title> (#<number>)
49143
50-
Look for signs of unrelated auto-formatter changes:
51-
- Large line-count diffs on files tangentially touched by the PR
52-
- Whitespace-only changes in unrelated sections
53-
- Reformatted import blocks or trailing commas not part of the feature
144+
### Summary
145+
<1–2 sentence summary of what the PR does>
54146
55-
If found, flag as **Important**: instruct to use git patch-mode staging or scope-selective reformatting in Android Studio.
147+
### Checklist Findings
56148
57-
### Step 5: Report Findings
149+
#### ✅ Looks Good
150+
- <item>
58151
59-
Structure the output as:
152+
#### ⚠️ Minor Issues
153+
- <file:line> — <issue> — <suggested fix>
60154
155+
#### ❌ Must Fix
156+
- <file:line> — <issue> — <suggested fix>
157+
158+
#### ⏭️ Suppressed
159+
- <short title> — dismissed: <reason>
160+
(omit this section entirely if nothing was suppressed)
161+
162+
### Overall Verdict
163+
APPROVE / REQUEST CHANGES / COMMENT
164+
<brief rationale>
61165
```
62-
## PR Review: <branch or PR title>
63166

64-
### Critical Issues (must fix before merge)
65-
- [FILE:LINE] Issue description
167+
Be specific. Reference file paths and line numbers. Cite the relevant convention when flagging an issue.
168+
169+
---
170+
171+
## Handling Dismissals
66172

67-
### Important Issues (should fix)
68-
- [FILE:LINE] Issue description
173+
When the user says `dismiss: <title> — <reason>` (in any form — "dismiss the X issue because Y", etc.):
69174

70-
### Suggestions
71-
- [FILE:LINE] Suggestion
175+
1. Read `.claude/skills/pr-review/dismissed-issues.md` if it exists (create it if not).
176+
2. Run `git config user.name` to get the current user's name.
177+
3. Append a new entry in this format:
72178

73-
### Looks Good
74-
- What's well done in this PR
179+
```markdown
180+
## <title>
181+
**Pattern**: <describe the class of issue broadly enough to match future occurrences>
182+
**Reason**: <reason the user gave>
183+
**Dismissed**: <today's date as YYYY-MM-DD>
184+
**Dismissed by**: <git user.name>
75185
```
76186

77-
## Quick Reference: Top Issues from Historical Reviews
78-
79-
These are the patterns most frequently flagged in past reviews. Check these first:
80-
81-
1. **ktlint failures** — Run check before reporting anything else.
82-
2. **AS wizard scaffolding left in** — Delete generated `colors.xml`, `strings.xml` with `app_name`, default launcher icons, and per-module theme definitions from library modules.
83-
3. **Unrelated auto-formatter changes** — Flag if diff contains formatting-only changes in untouched sections.
84-
4. **Convention plugin boilerplate in `build.gradle.kts`**`godtools.library-conventions` already provides minSdk, namespace (via `android {}` block), Kotlin toolchain, proguard, and Compose setup. Don't re-declare them.
85-
5. **`libs.versions.toml` clutter** — No duplicate version entries, no unused plugin declarations.
86-
6. **Composables missing `modifier: Modifier = Modifier`** — Required on all public/internal composables.
87-
7. **Wrong theme** — Use `GodToolsTheme` from `:ui:base`, never define a per-module theme.
88-
8. **Cross-module Activity intents** — Intent/PendingIntent creation for Activities must use extension functions in `ui/base/src/main/kotlin/org/cru/godtools/base/ui/Activities.kt`.
89-
9. **Circuit actions as direct calls** — User-triggered actions must flow through named `UiEvent` entries in `UiState.eventSink`, not direct function calls from the UI composable.
90-
10. **Local `includeBuild` in settings.gradle.kts** — Never commit local KMP project substitutions. Publish the KMP artifact and update the version in `libs.versions.toml` instead.
91-
11. **`println` for logging** — Use `Timber` instead.
92-
12. **Overly broad exception catching** — Catch specific exception types, not `Exception`.
93-
13. **Visibility too wide** — Prefer `internal` for module-scoped composables/functions, `private` where possible.
94-
95-
For full pattern details, consult `references/patterns.md`.
187+
4. Confirm to the user what was added and that it will be suppressed in future reviews.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Dismissed Review Issues
2+
3+
Issues listed here are suppressed in future PR reviews.
4+
5+
---

gradle/libs.versions.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ circuitx-gesture-navigation = { module = "com.slack.circuit:circuitx-gesture-nav
9595
coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coil" }
9696
coil-test = { module = "io.coil-kt:coil-test", version.ref = "coil" }
9797
colormath-android-colorint = { module = "com.github.ajalt.colormath:colormath-ext-android-colorint", version.ref = "colormath" }
98+
colormath-jetpack-compose = { module = "com.github.ajalt.colormath:colormath-ext-jetpack-compose", version.ref = "colormath" }
9899
compose-reorderable = "org.burnoutcrew.composereorderable:reorderable:0.9.6"
99100
dagger = { module = "com.google.dagger:dagger", version.ref = "dagger" }
100101
dagger-compiler = { module = "com.google.dagger:dagger-compiler", version.ref = "dagger" }
@@ -160,6 +161,7 @@ gtoSupport-play-auth = { module = "org.ccci.gto.android:gto-support-play-auth",
160161
gtoSupport-retrofit2 = { module = "org.ccci.gto.android:gto-support-retrofit2", version.ref = "gtoSupport" }
161162
gtoSupport-scarlet = { module = "org.ccci.gto.android:gto-support-scarlet", version.ref = "gtoSupport" }
162163
gtoSupport-scarlet-actioncable = { module = "org.ccci.gto.android:gto-support-scarlet-actioncable", version.ref = "gtoSupport" }
164+
gtoSupport-sync = { module = "org.ccci.gto.android:gto-support-sync", version.ref = "gtoSupport" }
163165
gtoSupport-testing-dagger = { module = "org.ccci.gto.android.testing:gto-support-dagger", version.ref = "gtoSupport" }
164166
gtoSupport-testing-picasso = { module = "org.ccci.gto.android.testing:gto-support-picasso", version.ref = "gtoSupport" }
165167
gtoSupport-testing-timber = { module = "org.ccci.gto.android.testing:gto-support-timber", version.ref = "gtoSupport" }

ui/article-aem-renderer/src/main/kotlin/org/cru/godtools/article/aem/db/ArticleDao.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import androidx.room.Insert
88
import androidx.room.OnConflictStrategy
99
import androidx.room.Query
1010
import java.util.Locale
11+
import kotlinx.coroutines.flow.Flow
1112
import org.cru.godtools.article.aem.model.Article
1213

1314
private const val GET_ARTICLES_FROM = """
@@ -38,7 +39,7 @@ interface ArticleDao {
3839
ORDER BY a.title
3940
"""
4041
)
41-
fun getArticles(tool: String, locale: Locale): LiveData<List<Article>>
42+
fun getArticlesFlow(tool: String, locale: Locale): Flow<List<Article>>
4243
@AnyThread
4344
@Query(
4445
"""
@@ -50,7 +51,7 @@ interface ArticleDao {
5051
ORDER BY a.title
5152
"""
5253
)
53-
fun getArticles(tool: String, locale: Locale, tags: List<@JvmSuppressWildcards String>): LiveData<List<Article>>
54+
fun getArticlesFlow(tool: String, locale: Locale, tags: List<@JvmSuppressWildcards String>): Flow<List<Article>>
5455

5556
@Insert(onConflict = OnConflictStrategy.IGNORE)
5657
suspend fun insertOrIgnore(article: Article)

ui/article-renderer/build.gradle.kts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ plugins {
77
android {
88
namespace = "org.cru.godtools.tool.article"
99

10+
configureCompose(project)
1011
configureQaBuildType(project)
1112
configureGodToolsCustomUri()
1213

@@ -21,19 +22,22 @@ dependencies {
2122
implementation(projects.library.base)
2223
implementation(projects.ui.baseTool)
2324

25+
implementation(libs.androidx.compose.material3)
2426
implementation(libs.androidx.fragment.ktx)
2527
implementation(libs.androidx.lifecycle.livedata.ktx)
2628
implementation(libs.androidx.constraintlayout)
2729
implementation(libs.androidx.recyclerview)
28-
implementation(libs.androidx.swiperefreshlayout)
2930

3031
implementation(libs.gtoSupport.androidx.fragment)
3132
implementation(libs.gtoSupport.androidx.lifecycle)
3233
implementation(libs.gtoSupport.androidx.recyclerview)
3334
implementation(libs.gtoSupport.core)
35+
implementation(libs.gtoSupport.kotlin.coroutines)
3436
implementation(libs.gtoSupport.picasso)
37+
implementation(libs.gtoSupport.sync)
3538
implementation(libs.gtoSupport.util)
3639

40+
implementation(libs.colormath.jetpack.compose)
3741
implementation(libs.dagger)
3842
implementation(libs.hilt)
3943
implementation(libs.splitties.fragmentargs)

ui/article-renderer/src/main/kotlin/org/cru/godtools/article/ui/articles/ArticlesAdapter.kt

Lines changed: 0 additions & 57 deletions
This file was deleted.

0 commit comments

Comments
 (0)