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
81 changes: 81 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
name: CI

on:
push:
branches: [master]
pull_request:
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5

- uses: actions/setup-java@v5
with:
distribution: temurin
java-version: '21'

- uses: gradle/actions/setup-gradle@v5

# Catches the class of breakage that left master unbuildable between 1.6 and 2.0.0:
# assemble every module, then run lint and the unit tests.
- name: Assemble
run: ./gradlew assembleDebug assembleRelease --stacktrace

- name: Lint
run: ./gradlew lint --stacktrace

- name: Unit tests
run: ./gradlew testDebugUnitTest --stacktrace

- name: Screenshot tests
run: ./gradlew verifyRoborazziDebug --stacktrace

# Roborazzi goldens are recorded on the maintainer's machine; if the Linux renderer
# disagrees, the diffs are the fastest way to see whether it is a real regression.
- name: Upload screenshot diffs
if: failure()
uses: actions/upload-artifact@v4
with:
name: roborazzi-diffs
path: |
**/build/outputs/roborazzi/**
if-no-files-found: ignore
retention-days: 7

- name: Upload lint reports
if: failure()
uses: actions/upload-artifact@v4
with:
name: lint-reports
path: '**/build/reports/lint-results-*.html'
if-no-files-found: ignore
retention-days: 7

publish-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5

- uses: actions/setup-java@v5
with:
distribution: temurin
java-version: '21'

- uses: gradle/actions/setup-gradle@v5

# JitPack builds tags with -Pgroup/-Pversion injected. Exercising the same path here means a
# broken release is caught before the tag is cut, not after.
- name: Publish to Maven local as JitPack would
run: |
./gradlew publishToMavenLocal \
-Pgroup=com.github.zjywill -Pversion=0.0.0-ci --stacktrace

- name: Show published artifacts
run: find ~/.m2/repository/com/github/zjywill -type f | sort
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,37 @@ dependencies {
}
```

Check the [JitPack page](https://jitpack.io/#zjywill/OverwatchProgress) for the coordinates of the
newest release: `2.1.0` adds a second artifact for the Compose API, and JitPack names artifacts
differently once a repository publishes more than one module.

> The coordinate is unchanged from `1.6`, so upgrading is a version bump. `2.0.0` requires JDK 17
> and raises `minSdk` to 24.

# Compose

```kotlin
HiveProgress(
modifier = Modifier.size(160.dp),
colors = HiveProgressDefaults.Rainbow,
spacing = 8.dp,
cornerRadius = 6.dp,
shrink = true,
)
```

There is also a stateless overload that takes the wave position instead of animating itself, which
is what makes the comb a pure function of its arguments — useful for screenshot tests, or for
driving the wave from your own animation:

```kotlin
HiveProgress(progress = 420f, modifier = Modifier.size(160.dp))
```

Both renderers share [`HiveGeometry`](overwatch/src/main/java/com/comix/overwatch/HiveGeometry.java),
so the comb layout and the fade wave cannot drift apart between them. The Compose version reports
itself as an indeterminate progress indicator to accessibility services.

# Spacing

`hive_spacing` is the gap between two neighbouring hexagons, and it applies uniformly in all six
Expand Down
14 changes: 14 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.compose)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Apply the Android Kotlin plugin for Kotlin sources

This applies only the Compose compiler plugin, but neither this module nor :overwatch-compose applies org.jetbrains.kotlin.android. The Compose compiler plugin configures Kotlin compilations after the Kotlin plugin exists; it does not create Kotlin compile tasks, so the .kt files added in this commit (MainActivity.kt, HiveProgress.kt, and the screenshot tests) are not compiled/packaged, leaving the demo activity and the new Compose API missing from the build artifacts. Add the Android Kotlin plugin alias and apply it in both Kotlin-containing modules.

Useful? React with 👍 / 👎.

}

android {
Expand All @@ -14,6 +15,10 @@ android {
versionName = "1.0"
}

buildFeatures {
compose = true
}

buildTypes {
release {
isMinifyEnabled = false
Expand All @@ -29,4 +34,13 @@ android {

dependencies {
implementation(project(":overwatch"))
implementation(project(":overwatch-compose"))

implementation(platform(libs.compose.bom))
implementation(libs.compose.ui)
implementation(libs.compose.foundation)
implementation(libs.compose.material3)
implementation(libs.compose.ui.tooling.preview)
implementation(libs.activity.compose)
debugImplementation(libs.compose.ui.tooling)
}
45 changes: 0 additions & 45 deletions app/src/main/java/com/comix/demo/MainActivity.java

This file was deleted.

104 changes: 104 additions & 0 deletions app/src/main/java/com/comix/demo/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package com.comix.demo

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawingPadding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Slider
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import com.comix.overwatch.HiveProgressView
import com.comix.overwatch.compose.HiveProgress
import com.comix.overwatch.compose.HiveProgressDefaults
import kotlin.math.roundToInt

private const val ANIMATION_MILLIS = 5000
private val CORNER_RADIUS = 8.dp
private const val MAX_SPACING_DP = 48f

class MainActivity : ComponentActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
MaterialTheme {
Surface(Modifier.fillMaxSize()) { DemoScreen() }
}
}
}
}

/** Drives both renderers from one slider, so any drift between them is visible side by side. */
@Composable
private fun DemoScreen() {
var spacingDp by remember { mutableFloatStateOf(8f) }
// HiveProgressView still takes a raw pixel corner radius, so convert to keep both sides equal.
val cornerRadiusPx = with(LocalDensity.current) { CORNER_RADIUS.roundToPx() }

Column(
modifier = Modifier
.fillMaxSize()
.safeDrawingPadding()
.padding(horizontal = 24.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(4.dp, Alignment.CenterVertically),
) {
Text("Compose", style = MaterialTheme.typography.labelLarge)
HiveProgress(
modifier = Modifier.size(170.dp),
colors = HiveProgressDefaults.Rainbow,
spacing = spacingDp.dp,
cornerRadius = CORNER_RADIUS,
shrink = true,
maxAlpha = 1f,
animationDurationMillis = ANIMATION_MILLIS,
)

Text("View", style = MaterialTheme.typography.labelLarge)
AndroidView(
factory = { context ->
HiveProgressView(context).apply {
isRainbow = true
isShrink = true
cornerRadius = cornerRadiusPx
maxAlpha = 255
animationTime = ANIMATION_MILLIS
}
},
update = { view -> view.setSpacingDp(spacingDp) },
modifier = Modifier.size(170.dp),
)

Text("Spacing ${spacingDp.roundToInt()}dp")
Slider(
value = spacingDp,
onValueChange = { spacingDp = it },
valueRange = 0f..MAX_SPACING_DP,
)
}
}

@Preview(showBackground = true)
@Composable
private fun DemoScreenPreview() {
MaterialTheme { DemoScreen() }
}
44 changes: 0 additions & 44 deletions app/src/main/res/layout/activity_main.xml

This file was deleted.

2 changes: 2 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.android.library) apply false
alias(libs.plugins.kotlin.compose) apply false
alias(libs.plugins.roborazzi) apply false
}
28 changes: 28 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,9 +1,37 @@
[versions]
agp = "9.3.1"
# Must match the kotlin-gradle-plugin AGP bundles (9.3.1 -> 2.2.10).
kotlin = "2.2.10"
composeBom = "2026.06.01"
activityCompose = "1.13.0"
compileSdk = "36"
targetSdk = "36"
minSdk = "24"
junit = "4.13.2"
robolectric = "4.16.1"
androidxTestJunit = "1.3.0"
roborazzi = "1.71.0"

[libraries]
compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
compose-foundation = { group = "androidx.compose.foundation", name = "foundation" }
compose-ui = { group = "androidx.compose.ui", name = "ui" }
compose-material3 = { group = "androidx.compose.material3", name = "material3" }
compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
compose-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }

junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-test-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidxTestJunit" }
robolectric = { group = "org.robolectric", name = "robolectric", version.ref = "robolectric" }
roborazzi = { group = "io.github.takahirom.roborazzi", name = "roborazzi", version.ref = "roborazzi" }
roborazzi-compose = { group = "io.github.takahirom.roborazzi", name = "roborazzi-compose", version.ref = "roborazzi" }
roborazzi-junit-rule = { group = "io.github.takahirom.roborazzi", name = "roborazzi-junit-rule", version.ref = "roborazzi" }

[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
android-library = { id = "com.android.library", version.ref = "agp" }
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
roborazzi = { id = "io.github.takahirom.roborazzi", version.ref = "roborazzi" }
Loading