diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 8c36de4..46498a6 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -44,14 +44,14 @@ jobs:
run: |
set -euo pipefail
raw="$(sed -nE 's/^version:[[:space:]]*//p' pubspec.yaml | head -n1 | tr -d '\r')"
- if [[ ! "$raw" =~ ^([0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z.-]+)?)\+([0-9]+)$ ]]; then
+ if [[ ! "$raw" =~ ^([0-9]+\.[0-9]+\.[0-9]+-xarleyn\.([1-9][0-9]*))\+([1-9][0-9]*)$ ]]; then
echo "Unexpected pubspec version format: $raw" >&2
exit 1
fi
version_name="${BASH_REMATCH[1]}"
version_code="${BASH_REMATCH[3]}"
tag="v${version_name}"
- asset_name="meshcore-wardrive-${version_name}-${version_code}.apk"
+ asset_name="meshcore-wardrive-xarleyn-${version_name}-${version_code}.apk"
{
echo "raw=${raw}"
echo "version_name=${version_name}"
@@ -61,6 +61,27 @@ jobs:
} >> "$GITHUB_OUTPUT"
echo "Publishing ${raw} as ${tag} (${asset_name})"
+ - name: Ensure versionCode increases
+ env:
+ GH_TOKEN: ${{ github.token }}
+ VERSION_CODE: ${{ steps.version.outputs.version_code }}
+ run: |
+ set -euo pipefail
+ max_published_code=0
+ while IFS= read -r asset_name; do
+ if [[ "$asset_name" =~ -([0-9]+)\.apk$ ]] &&
+ (( BASH_REMATCH[1] > max_published_code )); then
+ max_published_code="${BASH_REMATCH[1]}"
+ fi
+ done < <(
+ gh api --paginate "repos/${GITHUB_REPOSITORY}/releases?per_page=100" \
+ --jq '.[].assets[].name'
+ )
+ if (( VERSION_CODE <= max_published_code )); then
+ echo "versionCode ${VERSION_CODE} must exceed published ${max_published_code}." >&2
+ exit 1
+ fi
+
- name: Ensure release tag is free
env:
GH_TOKEN: ${{ github.token }}
@@ -119,6 +140,37 @@ jobs:
- name: Build release APK
run: flutter build apk --release
+ - name: Verify release APK identity and signature
+ env:
+ EXPECTED_CERT_SHA256: ${{ secrets.ANDROID_CERT_SHA256 }}
+ VERSION_NAME: ${{ steps.version.outputs.version_name }}
+ VERSION_CODE: ${{ steps.version.outputs.version_code }}
+ run: |
+ set -euo pipefail
+ if [[ -z "$EXPECTED_CERT_SHA256" ]]; then
+ echo "Missing secret ANDROID_CERT_SHA256" >&2
+ exit 1
+ fi
+ apk="build/app/outputs/flutter-apk/app-release.apk"
+ build_tools="$(find "$ANDROID_HOME/build-tools" -mindepth 1 -maxdepth 1 -type d | sort -V | tail -n1)"
+ aapt="$build_tools/aapt"
+ apksigner="$build_tools/apksigner"
+ test -x "$aapt"
+ test -x "$apksigner"
+
+ badging="$($aapt dump badging "$apk" | head -n1)"
+ [[ "$badging" == *"name='io.github.xarleyn.meshcore.wardrive'"* ]]
+ [[ "$badging" == *"versionCode='${VERSION_CODE}'"* ]]
+ [[ "$badging" == *"versionName='${VERSION_NAME}'"* ]]
+
+ "$apksigner" verify --verbose "$apk"
+ actual_cert="$($apksigner verify --print-certs "$apk" | sed -nE 's/^Signer #1 certificate SHA-256 digest: (.*)$/\1/p' | head -n1 | tr -d ':[:space:]' | tr '[:upper:]' '[:lower:]')"
+ expected_cert="$(printf '%s' "$EXPECTED_CERT_SHA256" | tr -d ':[:space:]' | tr '[:upper:]' '[:lower:]')"
+ if [[ -z "$actual_cert" || "$actual_cert" != "$expected_cert" ]]; then
+ echo "APK signer SHA-256 does not match ANDROID_CERT_SHA256." >&2
+ exit 1
+ fi
+
- name: Prepare release asset
env:
ASSET_NAME: ${{ steps.version.outputs.asset_name }}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3c152d7..c14f446 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,15 @@
## Unreleased
+### Breaking change
+
+- Changed the Android package ID from `mintylinux.meshcore.wardrive` to
+ `io.github.xarleyn.meshcore.wardrive`. Android installs this fork separately
+ from the original application and from older fork builds. No data migration
+ is provided: the new package starts with empty local data and default
+ settings, and requires permissions, background/MIUI settings, device access,
+ widgets, offline maps, and credentials to be configured again.
+
### Added
- Independent Map Display toggles for privacy zones and GPS exclusion zones.
@@ -36,6 +45,13 @@
both their descriptions and their unlock logic, without converting between
units: the thresholds stay 10/100/500, so 100 miles and 100 km unlock the
same badge depending on the setting.
+- Debug builds now use `io.github.xarleyn.meshcore.wardrive.debug`, allowing a
+ debug and signed release build of the fork to coexist.
+- Fork releases use `base-xarleyn.N` version names. The update checker compares
+ both the base version and fork revision, ignores downgrades, and selects the
+ newest compatible tag from multiple GitHub releases.
+- The release workflow verifies package ID, version name, increasing version
+ code, APK signature, release tag, and asset name before publication.
### Fixed
diff --git a/README.md b/README.md
index fadbdee..86ac60b 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,11 @@ companion radio over USB or Bluetooth, and displays coverage on a Flutter map.
## 📥 Download Pre-built APK
-**Latest Release:** [Download from Releases Repository](https://github.com/mintylinux/Meshcore-Wardrive-Android)
+**Latest Release:** [Download the xarleyn fork](https://github.com/xarleyn/Meshcore-Wardrive-Android-Source/releases)
+
+The fork uses Android package ID `io.github.xarleyn.meshcore.wardrive`, so it
+can coexist with the original app. The first fork-identity release is a clean
+installation and does not migrate data from `mintylinux.meshcore.wardrive`.
## 🚀 Features
@@ -47,8 +51,8 @@ companion radio over USB or Bluetooth, and displays coverage on a Flutter map.
1. Clone this repository:
```bash
-git clone https://github.com/mintylinux/Meshcore-Wardrive-Android-Source.git
-cd meshcore_wardrive
+git clone https://github.com/xarleyn/Meshcore-Wardrive-Android-Source.git
+cd Meshcore-Wardrive-Android-Source
```
2. Install dependencies:
@@ -69,7 +73,8 @@ flutter run
### Building Release APK
Configure the stable signing key once, then use the release script. It
-increments Android's build number and keeps the displayed version synchronized:
+increments Android's build number and the `-xarleyn.N` fork revision while
+keeping the displayed version synchronized:
```powershell
.\tool\build_release.ps1
diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts
index 5c2f88d..3b957c2 100644
--- a/android/app/build.gradle.kts
+++ b/android/app/build.gradle.kts
@@ -47,7 +47,7 @@ if (releaseTaskRequested) {
}
android {
- namespace = "mintylinux.meshcore.wardrive"
+ namespace = "io.github.xarleyn.meshcore.wardrive"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
@@ -57,7 +57,7 @@ android {
}
defaultConfig {
- applicationId = "mintylinux.meshcore.wardrive"
+ applicationId = "io.github.xarleyn.meshcore.wardrive"
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
@@ -74,6 +74,9 @@ android {
}
buildTypes {
+ debug {
+ applicationIdSuffix = ".debug"
+ }
release {
signingConfig = signingConfigs.getByName("release")
}
diff --git a/android/app/src/debug/res/values/strings.xml b/android/app/src/debug/res/values/strings.xml
new file mode 100644
index 0000000..bf6a547
--- /dev/null
+++ b/android/app/src/debug/res/values/strings.xml
@@ -0,0 +1,4 @@
+
+
+ MeshCore Wardrive (xarleyn Debug)
+
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 4125e0f..cd62931 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -35,7 +35,7 @@
diff --git a/android/app/src/main/kotlin/mintylinux/meshcore/wardrive/MainActivity.kt b/android/app/src/main/kotlin/io/github/xarleyn/meshcore/wardrive/MainActivity.kt
similarity index 96%
rename from android/app/src/main/kotlin/mintylinux/meshcore/wardrive/MainActivity.kt
rename to android/app/src/main/kotlin/io/github/xarleyn/meshcore/wardrive/MainActivity.kt
index b4c2544..3c469c4 100644
--- a/android/app/src/main/kotlin/mintylinux/meshcore/wardrive/MainActivity.kt
+++ b/android/app/src/main/kotlin/io/github/xarleyn/meshcore/wardrive/MainActivity.kt
@@ -1,4 +1,4 @@
-package mintylinux.meshcore.wardrive
+package io.github.xarleyn.meshcore.wardrive
import android.Manifest
import android.content.Context
@@ -24,10 +24,10 @@ import io.flutter.plugin.common.MethodChannel
class MainActivity : FlutterActivity() {
private val TAG = "MeshcoreFeedback"
- private val CHANNEL = "mintylinux.meshcore.wardrive/feedback"
- private val WIFI_CHANNEL = "mintylinux.meshcore.wardrive/wifi_location"
+ private val CHANNEL = "io.github.xarleyn.meshcore.wardrive/feedback"
+ private val WIFI_CHANNEL = "io.github.xarleyn.meshcore.wardrive/wifi_location"
private val TRACKING_SETTINGS_CHANNEL =
- "mintylinux.meshcore.wardrive/tracking_settings"
+ "io.github.xarleyn.meshcore.wardrive/tracking_settings"
private var toneGenerator: ToneGenerator? = null
private fun getVibrator(): Vibrator {
diff --git a/android/app/src/main/kotlin/mintylinux/meshcore/wardrive/WardriveWidgetProvider.kt b/android/app/src/main/kotlin/io/github/xarleyn/meshcore/wardrive/WardriveWidgetProvider.kt
similarity index 98%
rename from android/app/src/main/kotlin/mintylinux/meshcore/wardrive/WardriveWidgetProvider.kt
rename to android/app/src/main/kotlin/io/github/xarleyn/meshcore/wardrive/WardriveWidgetProvider.kt
index 722f084..0aef374 100644
--- a/android/app/src/main/kotlin/mintylinux/meshcore/wardrive/WardriveWidgetProvider.kt
+++ b/android/app/src/main/kotlin/io/github/xarleyn/meshcore/wardrive/WardriveWidgetProvider.kt
@@ -1,4 +1,4 @@
-package mintylinux.meshcore.wardrive
+package io.github.xarleyn.meshcore.wardrive
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
diff --git a/android/app/src/main/res/values-ru/strings.xml b/android/app/src/main/res/values-ru/strings.xml
index 0da5412..d18ada0 100644
--- a/android/app/src/main/res/values-ru/strings.xml
+++ b/android/app/src/main/res/values-ru/strings.xml
@@ -1,6 +1,6 @@
- MeshCore Wardrive
+ MeshCore Wardrive (xarleyn)
Ожидание
Замеры
LoRa
diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml
index 428e484..68c866b 100644
--- a/android/app/src/main/res/values/strings.xml
+++ b/android/app/src/main/res/values/strings.xml
@@ -1,6 +1,6 @@
- MeshCore Wardrive
+ MeshCore Wardrive (xarleyn)
Idle
Samples
LoRa
diff --git a/docs/INSTALLATION.md b/docs/INSTALLATION.md
index b0b37b9..fd451f1 100644
--- a/docs/INSTALLATION.md
+++ b/docs/INSTALLATION.md
@@ -17,6 +17,18 @@
3. Wait for installation to complete
4. Tap **Open** or find the app in your app drawer
+### Important: fresh fork identity
+
+Starting with `v1.0.44-xarleyn.1`, this fork uses the Android package ID
+`io.github.xarleyn.meshcore.wardrive`. The original application and older fork
+builds used `mintylinux.meshcore.wardrive`, so Android treats this release as a
+different application. Both applications can be installed at the same time.
+
+There is no automatic data migration. The new fork starts with an empty
+database and default settings; data in the old package is not available to it.
+Treat old measurements and settings as lost for this transition. Uninstalling
+the old package permanently deletes its private data.
+
### Step 4: Grant Permissions
On first launch, grant these permissions:
- **Location** (Always) - Required for GPS tracking
@@ -61,6 +73,8 @@ On first launch, grant these permissions:
- Make sure you have enough storage space (need ~100MB free)
- For an update, make sure the APK has the same application ID and signing
certificate as the installed app, and a higher build number
+- `v1.0.44-xarleyn.1` is a fresh install and does not replace a build using
+ `mintylinux.meshcore.wardrive`
- Restart your device and try again
### Permissions denied
@@ -74,7 +88,9 @@ On first launch, grant these permissions:
## Updating
-To update to a new version:
+For updates between releases that already use
+`io.github.xarleyn.meshcore.wardrive`:
+
1. Download the new APK
2. Install over the existing app (data will be preserved)
@@ -83,6 +99,12 @@ Uninstalling deletes the app's settings and collected wardrive data. If Android
rejects the update because its signing certificate differs, export or back up
important data before uninstalling the old app.
+The one-time change from `mintylinux.meshcore.wardrive` to
+`io.github.xarleyn.meshcore.wardrive` is not an update. After installing the
+new fork, grant permissions again and reconfigure background location, battery
+optimization/MIUI autostart, Bluetooth or USB access, widgets, offline maps,
+and any stored credentials. No migration from the old package is provided.
+
## Uninstalling
1. Go to Settings → Apps → MeshCore Wardrive
diff --git a/docs/development/releasing.md b/docs/development/releasing.md
index 71c3be7..22cac15 100644
--- a/docs/development/releasing.md
+++ b/docs/development/releasing.md
@@ -2,7 +2,7 @@
Android accepts an APK as an update only when all of the following remain true:
-- the `applicationId` is unchanged (`mintylinux.meshcore.wardrive`);
+- the `applicationId` is unchanged (`io.github.xarleyn.meshcore.wardrive`);
- the new `versionCode` is greater than the installed one;
- the new APK is signed with the same certificate as the installed APK.
@@ -10,6 +10,10 @@ Application settings and the SQLite database surviving installation are a
reliable sign that Android performed an in-place update. Uninstalling the app
deletes that private application data.
+The package ID changed once at `v1.0.44-xarleyn.1`. That release is a fresh
+installation, not an in-place update of `mintylinux.meshcore.wardrive`; no data
+migration is implemented. All later releases must retain the new package ID.
+
## Configure stable signing once
Release builds intentionally fail when signing is not configured. This avoids
@@ -32,10 +36,10 @@ Changing from an old debug key to a dedicated release key is itself a signing
change. Existing installations signed by the old key must be uninstalled once,
unless the original signing key is retained for releases.
-`flutter run` installs a debug-signed APK with the same application ID. It can
-update another debug installation, but it cannot replace an APK signed with the
-dedicated release key. Use the signed release APK when validating the real
-upgrade path; use `flutter run` for development-only device validation.
+`flutter run` installs `io.github.xarleyn.meshcore.wardrive.debug`, while signed
+releases use `io.github.xarleyn.meshcore.wardrive`. Debug and release builds can
+therefore coexist and keep independent data. Use the signed release APK when
+validating the real upgrade path.
## Build and increment the version
@@ -45,15 +49,16 @@ From PowerShell at the repository root, run:
.\tool\build_release.ps1
```
-The script increments only the Android build number (`versionCode`), for
-example `1.0.42+45` to `1.0.42+46`, synchronizes the in-app version constant,
-and builds the release APK. If the build fails, it restores both version files.
+The script increments the fork revision and Android build number, for example
+`1.0.44-xarleyn.1+47` to `1.0.44-xarleyn.2+48`, synchronizes the in-app version
+constant, and builds the release APK. If the build fails, it restores both
+version files.
-For a new public version, pass its semantic version name. The build number is
-still incremented automatically:
+When adopting a new upstream base version, reset the fork revision to `.1`.
+The build number is still incremented automatically:
```powershell
-.\tool\build_release.ps1 -VersionName 1.0.43
+.\tool\build_release.ps1 -VersionName 1.0.45-xarleyn.1
```
To produce per-architecture APKs, add `-SplitPerAbi`.
@@ -64,9 +69,13 @@ The lower-level cross-platform version commands are:
. .\.toolchain\env.ps1
dart run tool/version.dart check
dart run tool/version.dart sync
-dart run tool/version.dart bump 1.0.43
+dart run tool/version.dart bump 1.0.45-xarleyn.1
```
+The version tool rejects names without the required `-xarleyn.N` suffix and
+rejects zero or invalid Android build numbers. The `bump` command without a
+version name increments both the fork revision and `versionCode`.
+
Commit `pubspec.yaml` and `lib/constants/app_version.dart` together after a
successful release build. Never commit `android/key.properties`, a keystore,
or generated APK files.
@@ -87,6 +96,7 @@ Configure these secrets on the fork before the first Actions release:
| `ANDROID_KEYSTORE_PASSWORD` | `storePassword` from `key.properties` |
| `ANDROID_KEY_PASSWORD` | `keyPassword` from `key.properties` |
| `ANDROID_KEY_ALIAS` | `keyAlias` from `key.properties` |
+| `ANDROID_CERT_SHA256` | SHA-256 fingerprint of the release certificate (colons optional) |
On Linux or macOS:
@@ -101,16 +111,42 @@ On Windows PowerShell:
[Convert]::ToBase64String([IO.File]::ReadAllBytes('C:\secure\meshcore-wardrive-upload.jks')) | Set-Clipboard
```
+Read the expected certificate fingerprint with:
+
+```powershell
+. .\.toolchain\env.ps1
+keytool -list -v -keystore C:\secure\meshcore-wardrive-upload.jks -alias upload
+```
+
+Copy the value shown as `SHA256` into `ANDROID_CERT_SHA256`.
+
### Publish
1. Commit the desired `version:` in `pubspec.yaml` (and matching
`lib/constants/app_version.dart`) on the branch you want to ship.
2. Open **Actions → Release APK → Run workflow**.
3. Optionally mark the GitHub Release as a pre-release.
-4. The workflow creates tag `v{versionName}` (for example `v1.0.42` from
- `1.0.42+45`) and attaches
- `meshcore-wardrive-{versionName}-{versionCode}.apk`.
+4. The workflow creates tag `v{versionName}` (for example
+ `v1.0.44-xarleyn.1`) and attaches
+ `meshcore-wardrive-xarleyn-{versionName}-{versionCode}.apk`.
If that tag or release already exists, the workflow fails so you can bump the
-version first. Continuous integration on `main` runs format checks, analyzer,
-tests, and a debug APK build; it does not publish releases.
+version first. It also fails when the version suffix, package ID, version code,
+version name, or signing certificate is unexpected, or when `versionCode` is
+not greater than codes found in existing release asset names. Continuous
+integration on `main` runs format checks, analyzer, tests, and a debug APK
+build; it does not publish releases.
+
+## Manual release validation
+
+Before publication, validate on a device that:
+
+- the original app, the fork release, and the fork debug build can coexist;
+- upgrading from one `-xarleyn.N` release to the next preserves fork data;
+- USB, Bluetooth LE, foreground location, notifications, and the widget work;
+- MIUI Security Space and battery/autostart settings are configured for the new
+ package independently.
+
+The release workflow performs `aapt dump badging` and `apksigner verify`
+equivalents automatically, but physical radio and Android background behavior
+still require device testing.
diff --git a/lib/constants/app_version.dart b/lib/constants/app_version.dart
index f3ded4b..3c74e22 100644
--- a/lib/constants/app_version.dart
+++ b/lib/constants/app_version.dart
@@ -1,2 +1,2 @@
/// Application version synchronized from pubspec.yaml by tool/version.dart.
-const String appVersion = '1.0.43';
+const String appVersion = '1.0.44-xarleyn.1';
diff --git a/lib/screens/map_screen.dart b/lib/screens/map_screen.dart
index 628c762..227396b 100644
--- a/lib/screens/map_screen.dart
+++ b/lib/screens/map_screen.dart
@@ -1637,14 +1637,17 @@ class _MapScreenState extends State {
if (response.statusCode == 200) {
final releases = jsonDecode(response.body) as List;
- final latestVersion = releases.isEmpty
- ? null
- : versionFromReleaseTag(releases.first['tag_name'].toString());
+ final latestVersion = latestVersionFromReleaseTags(
+ releases
+ .whereType