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
3 changes: 3 additions & 0 deletions .github/workflows/ci-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ jobs:
- name: Decode keystore
run: echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > /tmp/release.jks

- name: Run release safety gate
run: ./gradlew test lint assembleRelease --no-daemon --stacktrace

- name: Build signed release APK & AAB
env:
KEYSTORE_PATH: /tmp/release.jks
Expand Down
100 changes: 97 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ on:
pull_request:
types: [opened, synchronize, reopened]

# The coverage step comments on the pull request; everything else only reads.
permissions:
contents: read
pull-requests: write

jobs:
editor:
name: Build notes editor and audit dependencies
Expand Down Expand Up @@ -88,19 +93,108 @@ jobs:
- name: Build debug
run: ./gradlew :app:assembleDebug --no-daemon --stacktrace

- name: Compile release
run: ./gradlew :app:assembleRelease --no-daemon --stacktrace

- name: Run unit tests and generate coverage
run: ./gradlew :app:testDebugUnitTest :app:createDebugUnitTestCoverageReport --no-daemon --stacktrace
run: ./gradlew :app:test :app:createDebugUnitTestCoverageReport --no-daemon --stacktrace

- name: Upload debug unit-test coverage
if: always()
uses: actions/upload-artifact@v4
with:
name: debug-unit-test-coverage
path: app/build/reports/coverage/
name: coverage-unit
path: app/build/reports/coverage/test/debug/report.xml
if-no-files-found: error

- name: Run lint
run: ./gradlew :app:lintDebug --no-daemon --stacktrace

- name: Check Java formatting
run: ./gradlew :app:spotlessCheck --no-daemon --stacktrace

instrumentation:
name: Android instrumentation and Room migrations
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v6

- name: Set up JDK 17
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: '17'

- name: Set up Android SDK
uses: android-actions/setup-android@v4
with:
log-accepted-android-sdk-licenses: 'false'

# Without this the x86_64 emulator runs unaccelerated on GitHub-hosted Linux runners
# and boot times out before any test runs. Required by reactivecircus/android-emulator-runner.
- name: Enable KVM
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' \
| sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm

- name: Run sync integration and migration tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 35
target: google_apis
arch: x86_64
# The coverage variant also runs the tests, and the Room store, the DAOs and the
# preference adapters are only reachable here — without this report they look untested.
script: ./gradlew :app:createDebugAndroidTestCoverageReport --no-daemon --stacktrace

- name: Upload instrumentation coverage
if: always()
uses: actions/upload-artifact@v4
with:
name: coverage-instrumentation
path: app/build/reports/coverage/androidTest/debug/connected/report.xml
if-no-files-found: error

coverage:
name: Report test coverage on the pull request
runs-on: ubuntu-latest
needs: [build, instrumentation]
# Report whatever exists even if a test job failed, so a coverage drop is still visible.
if: always()

steps:
- name: Checkout
uses: actions/checkout@v6

- name: Download unit-test coverage
uses: actions/download-artifact@v4
continue-on-error: true
with:
name: coverage-unit
path: coverage/unit

- name: Download instrumentation coverage
uses: actions/download-artifact@v4
continue-on-error: true
with:
name: coverage-instrumentation
path: coverage/instrumentation

# Posts, and on later pushes updates, a single comment showing overall coverage and the
# coverage of the files this PR actually changed. Both reports are passed together so the
# numbers reflect the unit and on-device suites combined.
- name: Comment coverage
uses: madrapps/jacoco-report@v1.7.1
with:
paths: |
${{ github.workspace }}/coverage/unit/report.xml
${{ github.workspace }}/coverage/instrumentation/report.xml
token: ${{ secrets.GITHUB_TOKEN }}
title: Test coverage (unit + instrumentation)
update-comment: true
min-coverage-overall: 0
min-coverage-changed-files: 0
7 changes: 5 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,8 @@ app/src/main/res/raw/*
app/google-services.json
gha-creds-*.json

# Local design/plan notes, deliberately kept out of the repository
docs/
# Local design/plan notes, deliberately kept out of the repository.
# Ignore the contents rather than the directory itself, so a single tracked file can be
# re-included without exposing everything else under docs/.
docs/*
!docs/google-drive-sync-invariants.md
64 changes: 24 additions & 40 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,54 +1,38 @@
# CHANGELOG

## [2.6.49] - 01.09.2026

**Improvements**

- **Google Drive sync for everyone:** The staged rollout is complete. All signed-in users who
explicitly confirm their first sync can now sync their notes, tasks, tags, preferences, and
attachments across devices.

## [2.6.48] - 01.09.2026

**Improvements**

- **Safer Google Drive sync:** Sync snapshots are now published as immutable files and merged
deterministically, so a concurrent device update cannot overwrite another device's data.
- **Efficient background sync:** Periodic sync now runs only on unmetered networks when the battery
is not low, and unchanged data no longer creates an extra Drive snapshot.
- **Quality visibility:** Added JaCoCo unit-test coverage reports to CI for every pull request.

**Fixes**

- Sync now requires explicit first-sync confirmation in the coordinator itself, preventing any
caller from bypassing the data-upload review.
- Fixed updates to an existing Drive sync bundle on Android/JDK configurations that reject HTTP
PATCH requests.

## [2.6.47] - 01.09.2026
## [2.6.50] - 04.09.2026

**New**

- **Google Drive sync:** Optionally keep notes, tasks, tags, preferences, and attachments in sync
across devices while continuing to work offline. Your data is merged safely before a sync is
published, and the first sync clearly explains what may be uploaded.
- **Your data:** Added an Account tab with Google sign-in, sync status, a manual sync action, and
an optional background-sync switch. Backup, export, and import remain available in their own
tabs.
- **Google Drive sync:** Keep notes, tasks, tags, settings, and attachments in sync across your
devices, while the app keeps working fully offline. Sync is optional and off until you sign in.
The first sync explains exactly what will be uploaded and waits for your confirmation.
- **Your data:** The backup screen gained an Account tab — sign in, see sync status and when the
last sync ran, sync on demand, and turn on background sync. Backup, export, and import stay in
their own tabs and work without an account.
- **Choosing between two versions:** When the same note was edited on two devices, the app now
shows both versions side by side with their times, marks the newer one, and highlights exactly
where they differ, so you pick a version instead of guessing which side is yours.

**Improvements**

- Attachments are deduplicated and verified during sync, reducing unnecessary uploads while
protecting file integrity.
- The app now remains fully usable when Google services are unavailable or when you choose not to
sign in.
- Updated translations across all supported languages for the new sync and account experience.
- Attachments are uploaded once and verified by content, so the same image shared between notes
never travels twice and a damaged upload is detected rather than trusted.
- Background sync runs only on unmetered networks and not on a low battery, and it skips
publishing entirely when nothing has changed.
- The Account tab now says plainly when sync cannot be offered on a device that has no Google
Play services, instead of showing controls that lead nowhere.
- Translations updated across all supported languages for sync and the account screen.

**Fixes**

- Fixed several sync stability issues, including leaving the screen during an active sync and
preserving the time of the last successful sync.
- Fixed Google sign-in compatibility on Android 8.0 and 8.1.
- Restoring a backup no longer loses a note when the backup mixes restored and renumbered
entries, and a note's attachments now follow it into the restored note.
- Leaving the screen during a sync, or a sync interrupted partway, no longer loses the time of
the last successful sync or the edits that were being uploaded.
- Fixed a blank strip drawn above the toolbar on the Tasks and Help screens, which looked like a
second, empty app bar.
- Fixed Google sign-in on Android 8.0 and 8.1.

## [2.6.46] - 18.05.2026

Expand Down
8 changes: 6 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ apply from: "$projectDir/gradle/libs-task.gradle"
apply from: "$projectDir/gradle/changelog-task.gradle"


def appVersionCode = 49
def appVersionCode = 50
def appVersionName = "2.6.${appVersionCode}"

def gitCommitHashProvider = providers.exec {
Expand Down Expand Up @@ -115,6 +115,10 @@ android {
minifyEnabled false
shrinkResources false
enableUnitTestCoverage true
// The Room store, the DAOs and the preference adapters are only reachable on a
// device, so without this they report 0% and the PR comment understates what is
// actually tested.
enableAndroidTestCoverage true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}

Expand All @@ -128,7 +132,7 @@ android {
}
namespace = 'com.pasich.mynotes'
lint {
abortOnError false
abortOnError true
}
testOptions {
unitTests {
Expand Down
10 changes: 9 additions & 1 deletion app/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,12 @@
public static *** i(...);
public static *** w(...);
public static *** e(...);
}
}
# Gson maps these by field name, and only the classes under data.model are kept above.
# Everything below is parsed from data the app itself wrote earlier — note attachments, local
# backups, Google Keep imports — so a renamed field silently deserializes to null rather than
# failing loudly: attachments stop resolving and a restore produces empty notes.
-keep class com.pasich.mynotes.extendedEditor.models.** { *; }
-keep class com.pasich.mynotes.utils.backup.models.** { *; }
-keepclassmembers class com.pasich.mynotes.extendedEditor.models.** { *; }
-keepclassmembers class com.pasich.mynotes.utils.backup.models.** { *; }
Loading
Loading