Skip to content
Closed
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
9 changes: 9 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ name: CI
on:
pull_request:
branches: [ main ]
# `ready_for_review` is NOT in the default type set (opened/synchronize/reopened).
# Without it a PR opened as a draft and later marked ready fires no event at all, so
# the draft guard below would skip every run and CI would never report on that PR.
types: [ opened, synchronize, reopened, ready_for_review ]
push:
branches: [ main ]

Expand All @@ -14,6 +18,11 @@ concurrency:
jobs:
test:
name: Tests & coverage gate
# Draft PRs can't be merged, so don't spend runner minutes on them. Marking a PR ready
# fires `ready_for_review` (see types above), which is when CI runs for a drafted PR.
# The event_name check keeps pushes to main running: on push there is no
# `event.pull_request`, and a null never equals false in GitHub expressions.
if: github.event_name != 'pull_request' || github.event.pull_request.draft == false
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand Down
17 changes: 17 additions & 0 deletions .run/Clear_Local_Maven_Publish.run.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Clear Local Maven Publish" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="$PROJECT_DIR$/scripts/clear-local-publish.sh" />
<option name="SCRIPT_OPTIONS" value="" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="/bin/bash" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="true" />
<option name="EXECUTE_SCRIPT_FILE" value="true" />
<envs />
<method v="2" />
</configuration>
</component>
17 changes: 17 additions & 0 deletions .run/Publish_to_Maven_Local.run.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Publish to Maven Local" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="$PROJECT_DIR$/scripts/publish-local.sh" />
<option name="SCRIPT_OPTIONS" value="" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="/bin/bash" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="true" />
<option name="EXECUTE_SCRIPT_FILE" value="true" />
<envs />
<method v="2" />
</configuration>
</component>
17 changes: 17 additions & 0 deletions .run/Publish_to_Maven_Local__signed_.run.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Publish to Maven Local (signed)" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="$PROJECT_DIR$/scripts/publish-local.sh" />
<option name="SCRIPT_OPTIONS" value="--signed" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="/bin/bash" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="true" />
<option name="EXECUTE_SCRIPT_FILE" value="true" />
<envs />
<method v="2" />
</configuration>
</component>
6 changes: 3 additions & 3 deletions AppSnackBar-UiState/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ Integration module that bridges the AppSnackBar and UiState modules, providing a

```gradle.kts
// Requires both base modules
implementation("uk.co.appoly.droid:uistate:1.9.0")
implementation("uk.co.appoly.droid:appsnackbar:1.9.0")
implementation("uk.co.appoly.droid:appsnackbar-uistate:1.9.0")
implementation("uk.co.appoly.droid:uistate:1.9.1")
implementation("uk.co.appoly.droid:appsnackbar:1.9.1")
implementation("uk.co.appoly.droid:appsnackbar-uistate:1.9.1")
```

## Usage
Expand Down
2 changes: 1 addition & 1 deletion AppSnackBar/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ A customizable Jetpack Compose Snackbar implementation with support for differen
## Installation

```gradle.kts
implementation("uk.co.appoly.droid:appsnackbar:1.9.0")
implementation("uk.co.appoly.droid:appsnackbar:1.9.1")
```

## Usage
Expand Down
4 changes: 2 additions & 2 deletions BaseRepo-AppolyJson/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ Appoly's JSON format.

```gradle.kts
// Requires the base BaseRepo module
implementation("uk.co.appoly.droid:baserepo:1.9.0")
implementation("uk.co.appoly.droid:baserepo-appolyjson:1.9.0")
implementation("uk.co.appoly.droid:baserepo:1.9.1")
implementation("uk.co.appoly.droid:baserepo-appolyjson:1.9.1")
```

## API Response Structure
Expand Down
10 changes: 5 additions & 5 deletions BaseRepo-Paging-AppolyJson/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ follow Appoly's paging format.

```gradle.kts
// Requires the base modules
implementation("uk.co.appoly.droid:baserepo:1.9.0")
implementation("uk.co.appoly.droid:baserepo-paging:1.9.0")
implementation("uk.co.appoly.droid:baserepo-paging-appolyjson:1.9.0")
implementation("uk.co.appoly.droid:baserepo:1.9.1")
implementation("uk.co.appoly.droid:baserepo-paging:1.9.1")
implementation("uk.co.appoly.droid:baserepo-paging-appolyjson:1.9.1")

// For Compose UI integration
implementation("uk.co.appoly.droid:lazylistpagingextensions:1.9.0") // For LazyColumn
implementation("uk.co.appoly.droid:lazygridpagingextensions:1.9.0") // For LazyGrid
implementation("uk.co.appoly.droid:lazylistpagingextensions:1.9.1") // For LazyColumn
implementation("uk.co.appoly.droid:lazygridpagingextensions:1.9.1") // For LazyGrid
```

## API Response Format
Expand Down
8 changes: 4 additions & 4 deletions BaseRepo-Paging/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ extended for specific JSON formats.

```gradle.kts
// Requires the base BaseRepo module
implementation("uk.co.appoly.droid:baserepo:1.9.0")
implementation("uk.co.appoly.droid:baserepo-paging:1.9.0")
implementation("uk.co.appoly.droid:baserepo:1.9.1")
implementation("uk.co.appoly.droid:baserepo-paging:1.9.1")

// For Compose UI integration
implementation("uk.co.appoly.droid:lazylistpagingextensions:1.9.0") // For LazyColumn
implementation("uk.co.appoly.droid:lazygridpagingextensions:1.9.0") // For LazyGrid
implementation("uk.co.appoly.droid:lazylistpagingextensions:1.9.1") // For LazyColumn
implementation("uk.co.appoly.droid:lazygridpagingextensions:1.9.1") // For LazyGrid
```

## Extensions
Expand Down
6 changes: 3 additions & 3 deletions BaseRepo-S3Uploader-Multipart/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ Extension module that bridges BaseRepo and S3Uploader-Multipart, enabling pausab

```gradle.kts
// Requires the base modules
implementation("uk.co.appoly.droid:baserepo:1.9.0")
implementation("uk.co.appoly.droid:s3uploader-multipart:1.9.0")
implementation("uk.co.appoly.droid:baserepo-s3uploader-multipart:1.9.0")
implementation("uk.co.appoly.droid:baserepo:1.9.1")
implementation("uk.co.appoly.droid:s3uploader-multipart:1.9.1")
implementation("uk.co.appoly.droid:baserepo-s3uploader-multipart:1.9.1")
```

## Usage
Expand Down
6 changes: 3 additions & 3 deletions BaseRepo-S3Uploader/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ An extension module that bridges BaseRepo and S3Uploader, enabling seamless file

```gradle.kts
// Requires both the base modules
implementation("uk.co.appoly.droid:baserepo:1.9.0")
implementation("uk.co.appoly.droid:s3uploader:1.9.0")
implementation("uk.co.appoly.droid:baserepo-s3uploader:1.9.0")
implementation("uk.co.appoly.droid:baserepo:1.9.1")
implementation("uk.co.appoly.droid:s3uploader:1.9.1")
implementation("uk.co.appoly.droid:baserepo-s3uploader:1.9.1")
```

## How it Works
Expand Down
2 changes: 1 addition & 1 deletion BaseRepo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Foundation module for implementing the repository pattern with standardized API
## Installation

```gradle.kts
implementation("uk.co.appoly.droid:baserepo:1.9.0")
implementation("uk.co.appoly.droid:baserepo:1.9.1")
```

## Extensions
Expand Down
3 changes: 3 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,9 @@ Published to **Maven Central** under `uk.co.appoly.droid`, with lowercase artifa
Gradle reads them only under the `ORG_GRADLE_PROJECT_` prefix with exact camelCase. The vault
item is set in the git-ignored `scripts/publish.conf` — this repo is public, so it is not
committed. See `scripts/publish.conf.example`.
- `./scripts/publish-local.sh` installs to `~/.m2` unsigned with no credentials (the everyday
local-testing loop), and `./scripts/clear-local-publish.sh` removes that install again. Both are
also Android Studio run configurations in `.run/`.
- `./scripts/publish.sh --local` publishes signed artifacts to `~/.m2`; without `--local` it
releases to Central. **Releases are run manually and locally** — there is no release CI job and
no Maven Central secrets in the repo, so a version tag publishes nothing on its own.
Expand Down
137 changes: 125 additions & 12 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,105 @@ covers building, testing and releasing it.
## Testing an unreleased change

Maven Central publishes only what is released, so there is no equivalent of JitPack's
build-any-branch behaviour. Two options replace it.
build-any-branch behaviour — and releases are immutable, so a mistake cannot be corrected in
place. Test locally first. Two options replace JitPack.

**Install locally.** From a checkout of the branch you want to test:
### Install locally (the normal loop)

From a checkout of the branch you want to test:

```bash
./scripts/publish-local.sh # every module, unsigned — no credentials needed
./scripts/publish-local.sh BaseRepo UiState # only those modules, for a tight iteration loop
./scripts/publish-local.sh --signed # every module, signed (= ./scripts/publish.sh --local)
```

Undo it with:

```bash
./scripts/publish.sh --local
./scripts/clear-local-publish.sh # every locally installed version
./scripts/clear-local-publish.sh 1.9.1-local1 # just that version
./scripts/clear-local-publish.sh --dry-run # list what would go, delete nothing
```

That publishes every module to `~/.m2`, signed. Add `mavenLocal()` to the consuming project's
repositories, ahead of `mavenCentral()`.
Both are also Android Studio run configurations, checked in under `.run/` and shared through
version control: **Publish to Maven Local**, **Publish to Maven Local (signed)** and **Clear Local
Maven Publish**. They run in the Run window's terminal, so the clear script's confirmation prompt
works there. To publish a subset from the IDE, edit the run configuration's *Script options* field —
or just use the terminal.

> Take `mavenLocal()` out again before committing, and before drawing any conclusion about a
> released version. A locally published build carries the same version string as the real one, so
> leaving it in means resolving your own artifacts while believing you are testing the release.
`clear-local-publish.sh` only ever touches `~/.m2/repository/uk/co/appoly/droid` (or `PUBLISH_GROUP`
from `scripts/publish.conf`, for a fork). Nothing else in `~/.m2` is read or written.

**Publish a snapshot.** Snapshot versions go to Central's snapshot repository rather than the main
one, and need it adding explicitly:
**Signed or not?** Unsigned is the default because signing needs the release key out of 1Password,
and Gradle does not verify signatures on resolve — an unsigned local install behaves identically to
a signed one for every purpose this loop has. Use `--signed` only when the thing under test *is* the
signing, or the exact artifact set a release would upload. That path is `publish.sh --local`, which
`--signed` simply delegates to.

### Consuming a local install from another project

Add `mavenLocal()` **first** in the consuming project's repository list, so it wins over Central:

```kotlin
// settings.gradle.kts
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
mavenLocal()
google()
mavenCentral()
}
}
```

Then depend on the toolbox exactly as usual — the BOM works unchanged, since it is installed
locally alongside everything else:

```kotlin
// <version> is whatever TOOLBOX_VERSION you just installed — this file is not version-synced,
// so read it out of buildSrc/src/main/kotlin/BuildConfig.kt rather than trusting a number here.
implementation(platform("uk.co.appoly.droid:bom:<version>"))
implementation("uk.co.appoly.droid:baserepo")
implementation("uk.co.appoly.droid:uistate")
```

Sync with `--refresh-dependencies` the first time:

```bash
./gradlew --refresh-dependencies :app:assembleStagingDebug
```

Without it Gradle may serve a cached module for that version string — resolved earlier from Central
— and never look in `~/.m2` at all. The same applies in reverse *after* clearing: a consumer that
already resolved the local copy keeps serving it until refreshed.

A narrower alternative, if you would rather `mavenLocal()` could not possibly shadow anything else,
is to scope it to the toolbox group:

```kotlin
exclusiveContent {
forRepository { mavenLocal() }
filter { includeGroup("uk.co.appoly.droid") }
}
```

> **Take `mavenLocal()` back out when you are done**, and run `clear-local-publish.sh`. A local
> install carries the same version string as the real release, so leaving either in place means
> resolving your own working tree while believing you are testing the published artifacts. A partial
> install (`publish-local.sh BaseRepo`) is worse still: the other modules in `~/.m2` are whatever was
> installed last, possibly a different build of the same version.

The cleanest way to remove the ambiguity entirely is to bump `TOOLBOX_VERSION` in
`buildSrc/src/main/kotlin/BuildConfig.kt` to something that does not and will not exist on Central —
`1.9.1-local1` — and depend on that from the consuming project. Then there is no version string in
play that could mean two different things, and the dependency-cache problem disappears with it.
Revert the bump before committing.

### Publish a snapshot

Snapshot versions go to Central's snapshot repository rather than the main one, and need it adding
explicitly:

```kotlin
maven { url = uri("https://central.sonatype.com/repository/maven-snapshots/") }
Expand Down Expand Up @@ -50,7 +132,36 @@ Bump `TOOLBOX_VERSION` in `buildSrc/src/main/kotlin/BuildConfig.kt` first. Every
one version; see [Why one version for all modules](#why-one-version-for-all-modules).

> **Releases are immutable.** A version can never be re-uploaded or corrected — the only remedy is
> publishing a new one. Iterate with `--local` *before* releasing, never after.
> publishing a new one. Iterate with [`publish-local.sh`](#install-locally-the-normal-loop) *before*
> releasing, never after.

### Central publishing limits — batch releases, do not split modules

Maven Central enforces three per-calendar-month quotas per organisation, from 1 October 2026. Our
applied limits, confirmed by Sonatype on 2026-09-09, are **1,000 files, 80 MB and 7 releases**.
Track usage in the [Usage Center](https://central.sonatype.com/publishing/usage).

One toolbox release is **508 files, 11.55 MB, and one release event** — Central scores a multi-module
deployment bundle as a single release, not one per artifact. So release count is a non-issue and size
is nowhere near. **File count is the binding constraint:** 508 files is roughly half the monthly
allowance, so a second release in the same calendar month lands at ~1,016 and a third cannot fit.

Two consequences for release practice:

- **Batch patch releases.** A flurry of same-month point releases — the 1.8.0 → 1.8.3 pattern of
August 2026 — would be ~2,540 files, over twice the allowance. Fold fixes into one version and
iterate through a local install or a snapshot in the meantime.
- **Do not split modules to reduce usage; it does the opposite.** 26 separately-published
repositories would be 26 release events per version, past the limit of 7 on day one. The single
batched deployment is the cheapest possible shape under these rules — a further reason for the
caveat in [Why one version for all modules](#why-one-version-for-all-modules).

Sonatype granted `uk.co.appoly.droid` an **OSS exemption** on 2026-09-09, so Central's
*commercial nature* classification — which is independent of publishing volume and would otherwise
require Publisher Pro — does not apply to us. The same response declined to raise the file-count
ceiling in substance: the "enhanced" limits it granted match what was already applied, sized to a
publishing history of a single release. If the one-release-per-month cap starts to hurt, that is the
thing to go back to `central-support@sonatype.com` about, with a concrete cadence to justify it.

### Credentials

Expand Down Expand Up @@ -142,7 +253,9 @@ coherent version set, so it cannot catch either.

Republishing everything costs minutes of upload and no consumer risk. If a module ever genuinely
earns its own release cadence, split it into its own repository rather than versioning it
independently here.
independently here — but weigh it against
[Central publishing limits](#central-publishing-limits--batch-releases-do-not-split-modules) first,
since each extra repository is another monthly release event.

## Documentation

Expand Down
2 changes: 1 addition & 1 deletion ComposeExtensions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Compose utilities for insets/IME padding, padding arithmetic, serialization-safe
## Installation

```gradle.kts
implementation("uk.co.appoly.droid:composeextensions:1.9.0")
implementation("uk.co.appoly.droid:composeextensions:1.9.1")
```

## Usage
Expand Down
2 changes: 1 addition & 1 deletion ConnectivityMonitor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
Add the following dependency to your project's `build.gradle` file:

```gradle.kts
implementation("uk.co.appoly.droid:connectivitymonitor:1.9.0")
implementation("uk.co.appoly.droid:connectivitymonitor:1.9.1")
```

## Usage
Expand Down
10 changes: 5 additions & 5 deletions DateHelperUtil-Room/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ Extension module for DateHelperUtil that provides Room database integration for

```gradle.kts
// Requires base DateHelperUtil module
implementation("uk.co.appoly.droid:datehelperutil:1.9.0")
implementation("uk.co.appoly.droid:datehelperutil-room:1.9.0")
implementation("uk.co.appoly.droid:datehelperutil:1.9.1")
implementation("uk.co.appoly.droid:datehelperutil-room:1.9.1")

// Required Room dependencies
implementation("androidx.room:room-runtime:2.8.4")
implementation("androidx.room:room-ktx:2.8.4")
ksp("androidx.room:room-compiler:2.8.4")
implementation("androidx.room:room-runtime:2.8.5")
implementation("androidx.room:room-ktx:2.8.5")
ksp("androidx.room:room-compiler:2.8.5")
```

## Usage
Expand Down
Loading
Loading