diff --git a/README.md b/README.md index f081b56..37d0200 100644 --- a/README.md +++ b/README.md @@ -8,92 +8,113 @@ Runtime accessibility scanner that overlays issues directly on your Compose UI ![Annotated GIF showing the Compose A11y Scanner issue summary, view highlights, and issue detail sheet in the sample app](docs/overlay-demo.gif) -## Architecture +## Quick start -```mermaid -flowchart LR - SemanticsTree["SemanticsTree"] --> Extractor[":scanner-ui
A11yNodeExtractor"] - Extractor --> ColorExtractor["ColorExtractor"] - ColorExtractor --> Nodes["A11yNode list"] - Extractor --> Nodes - Nodes --> Core[":scanner-core
A11yScanEngine"] - Rules[":scanner-rules
Built-in A11yRule implementations"] --> Core - Core --> Result["ScanResult / ScannerState"] - Result --> Overlay[":scanner-ui
Overlay, summary, report sheet"] -``` - -`:scanner-core` owns the scan engine, models, result state, and `A11yRule` contract. `:scanner-rules` contains the built-in rules. `:scanner-ui` reads the Compose semantics tree, samples colors through `ColorExtractor`, runs scans, and draws issue overlays on top of the host UI. - -## Installation +Add JitPack and the debug-only scanner dependency: ```kotlin -repositories { maven("https://jitpack.io") } -dependencies { debugImplementation("com.github.mohdaquib.ComposeA11yScanner:scanner-ui:v1.0.0") } +// settings.gradle.kts +dependencyResolutionManagement { + repositories { + google() + mavenCentral() + maven("https://jitpack.io") + } +} + +// app/build.gradle.kts +dependencies { + debugImplementation("com.github.mohdaquib.ComposeA11yScanner:scanner-ui:v1.0.0") +} ``` -Use a debug-only dependency because the scanner is intended for development builds. `:scanner-ui` brings `:scanner-core` and `:scanner-rules` transitively. +That is all the integration required. AndroidX Startup automatically attaches the overlay and runs an initial scan for each `ComponentActivity` in a debuggable app. Release builds do not package the scanner when it is added with `debugImplementation`. + +To request another scan, call the API from a debug source set (for example, `src/debug/java/.../ScannerActions.kt`): -## Integration +```kotlin +import com.composea11yscanner.ComposeA11yScanner -### A11yScannerScaffold +ComposeA11yScanner.triggerScan() +``` -Wrap the screen you want to inspect with `A11yScannerScaffold`. Provide an `A11yScannerController` that can extract nodes from your current Compose semantics tree. +For shake-to-scan, add one call to a debug-only composable that is active while the screen is visible: ```kotlin -@Composable -fun DebugScannerScreen( - scannerController: A11yScannerController, - content: @Composable () -> Unit, -) { - val config = remember { - ScannerConfig( - enabledRules = ScannerRules.allRuleIds().toSet(), - autoScan = false, - ) - } +import com.composea11yscanner.triggers.scanOnShake - A11yScannerScaffold( - scannerController = scannerController, - config = config, - modifier = Modifier.fillMaxSize(), - ) { - content() - } +@Composable +fun App() { + scanOnShake() + AppContent() } ``` -The scaffold renders your content first, then draws issue highlight boxes, the scan summary bar, and the issue detail sheet above it. +The scanner enables all built-in rules by default. The overlay is removed automatically when its activity is destroyed. Keep direct scanner imports in `src/debug`; a `debugImplementation` dependency is intentionally unavailable while compiling release sources. -### Shake To Scan +## Configuration -Call `scanOnShake()` from a composable that is active while the scanned screen is visible. +Optional manifest metadata configures the auto-installed scanner: -```kotlin -scanOnShake( - enabled = true, - onScanRequested = { - scannerController.startScan() - }, -) +```xml + + + + ``` -### Manual Trigger +### Manual installation + +Use manual installation only when you need a programmatic `ScannerConfig`. First disable the automatic initializer in the app manifest: + +```xml + + + + + + + +``` -Wire a debug menu item, toolbar button, or test-only action directly to the controller. +Then install after `setContent`: ```kotlin -IconButton(onClick = { scannerController.startScan() }) { - Icon( - imageVector = Icons.Default.Search, - contentDescription = "Scan accessibility", - ) -} +setContent { App() } + +ComposeA11yScanner.install( + activity = this, + config = ScannerConfig( + enabledRules = ScannerRules.allRuleIds().toSet(), + minContrastRatio = 4.5f, + autoScan = false, + ), +) ``` -If you use the top-level activity overlay API, call `ComposeA11yScanner.triggerScan()` instead. +Do not combine automatic and manual installation. Repeated installation on the same activity is ignored, but keeping one ownership path makes configuration predictable. + +### Embedded scaffold + +`A11yScannerScaffold` is the advanced API for apps that want the scanner UI inside their own Compose hierarchy or need a custom node provider. It requires an `A11yScannerController`; most integrations should use the automatic activity overlay above. ```kotlin -ComposeA11yScanner.triggerScan() +A11yScannerScaffold( + scannerController = scannerController, + config = config, + modifier = Modifier.fillMaxSize(), +) { + AppContent() +} ``` ## Built-In Rules @@ -149,3 +170,17 @@ val scannerController = A11yScannerController( ``` Custom rule IDs are automatically enabled by `A11yScannerController.withRules(...)` before each scan. + +## Architecture + +```mermaid +flowchart LR + SemanticsTree["SemanticsTree"] --> Extractor[":scanner-ui
A11yNodeExtractor"] + Extractor --> Nodes["A11yNode list"] + Nodes --> Core[":scanner-core
A11yScanEngine"] + Rules[":scanner-rules
Built-in and custom rules"] --> Core + Core --> Result["ScanResult / ScannerState"] + Result --> Overlay[":scanner-ui
Overlay and issue details"] +``` + +`:scanner-core` owns the scan engine and public models. `:scanner-rules` contains built-in rules. `:scanner-ui` handles Android/Compose integration, node extraction, triggers, and the overlay.