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
5 changes: 4 additions & 1 deletion .github/workflows/checks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,11 @@ jobs:
- name: Run tests
run: flutter test --coverage

# Exclude generated *.g.dart (Pigeon): they are not hand-formatted, and
# `dart format` ignores the analysis_options `formatter: exclude` list, so
# filter them out here via git pathspec.
- name: Check formatting
run: dart format --output=none --set-exit-if-changed .
run: dart format --output=none --set-exit-if-changed $(git ls-files '*.dart' ':!:*.g.dart')

# This is a plugin package: the buildable app lives in example/,
# so build there (the repo root has no lib/main.dart).
Expand Down
104 changes: 104 additions & 0 deletions .github/workflows/integration-android.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
name: Android integration tests

on:
pull_request:
types:
- opened
- reopened
- synchronize
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
patrol-android:
if: github.event.pull_request.draft == false
runs-on: ubuntu-latest
timeout-minutes: 90

steps:
- uses: actions/checkout@v4

# Hardware acceleration for the Android emulator on ubuntu runners.
- 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

- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '17'

- uses: subosito/flutter-action@v2
with:
flutter-version: '3.44.0'
channel: stable

- name: Get dependencies
run: flutter pub get

# Pin to match the `patrol` package major in example/pubspec.yaml (^4.x).
- name: Install patrol_cli
run: |
dart pub global activate patrol_cli 4.3.1
echo "$HOME/.pub-cache/bin" >> "$GITHUB_PATH"

# GitHub-hosted emulators cold-boot flakily (adb offline / frozen device),
# which is the phase our runs hung in. Boot from a cached AVD snapshot
# instead: a warm-up run creates it once, and test runs restore it with
# `-no-snapshot-save`. The image is `google_apis` (ships Google Play
# services, needed for the FCM token fetch on init) — the AOSP `default`
# image lacks it and is markedly flakier. All AVD params must match between
# the warm-up and the test run, and are encoded in the cache key.
- name: AVD cache
uses: actions/cache@v4
id: avd-cache
with:
path: |
~/.android/avd/*
~/.android/adb*
key: avd-api34-google_apis-x86_64-pixel6-v1

- name: Create AVD and generate snapshot for caching
if: steps.avd-cache.outputs.cache-hit != 'true'
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 34
target: google_apis
arch: x86_64
profile: pixel_6
ram-size: 4096M
cores: 3
force-avd-creation: false
emulator-options: >-
-no-window -noaudio -no-boot-anim
-gpu swiftshader_indirect -camera-back none
disable-animations: false
script: echo "Generated AVD snapshot for caching."

# Boots the emulator from the cached snapshot and runs the whole
# integration_test/ suite. The tests drive the real example app against the
# REES46 demo shop (c1140c…), exercising the Kotlin bridge + real backend.
- name: Run Patrol integration tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 34
target: google_apis
arch: x86_64
profile: pixel_6
ram-size: 4096M
cores: 3
force-avd-creation: false
emulator-options: >-
-no-snapshot-save -no-window -noaudio -no-boot-anim
-gpu swiftshader_indirect -camera-back none
disable-animations: true
working-directory: example
script: |
flutter pub get
patrol test --verbose
76 changes: 76 additions & 0 deletions .github/workflows/version.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
name: Bump version

# Autobump for the Flutter SDK, mirroring the Android/iOS pipeline but self-contained
# (no shared rees46/workflow changes). semantic-release computes the next version from
# Conventional Commits; this workflow writes it into pubspec.yaml + CHANGELOG.md and
# commits `chore(release): <version>` to master. The existing deploy.yaml then tags
# v<version>, which triggers publish.yaml -> pub.dev (OIDC).
on:
push:
branches:
- master
paths-ignore:
- .github
workflow_dispatch:

jobs:
# Skip bumping when HEAD is itself a release commit — otherwise the push made by
# this workflow would trigger another bump (infinite loop).
find-release-commit:
uses: rees46/workflow/.github/workflows/reusable-release-commit-finder.yaml@master
with:
commitMessage: 'chore(release):'
secrets:
githubToken: ${{ secrets.GITHUB_TOKEN }}

bump-version:
needs: find-release-commit
if: |
github.event_name == 'workflow_dispatch' || (
github.event_name == 'push' &&
needs.find-release-commit.outputs.hasCommit == 'false')
runs-on: ubuntu-latest
permissions: write-all
steps:
- name: Generate app token
id: app-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ vars.VERSIONER_ID }}
private-key: ${{ secrets.VERSIONER_SECRET }}

- name: Checkout
uses: actions/checkout@v5
with:
token: ${{ steps.app-token.outputs.token }}
fetch-depth: 0

# Dry-run: compute the next version + notes from Conventional Commits without
# tagging, committing or publishing. Reads .releaserc.yml from the repo root.
- name: Compute next version
id: semantic
uses: docker://ghcr.io/codfish/semantic-release-action:v3
with:
dry-run: true
env:
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}

- name: Apply version bump
if: steps.semantic.outputs.new-release-published == 'true'
env:
RELEASE_VERSION: ${{ steps.semantic.outputs.release-version }}
RELEASE_NOTES: ${{ steps.semantic.outputs.release-notes }}
run: |
chmod +x ./scripts/apply_release.sh
./scripts/apply_release.sh

# Commit the bump to master. The existing deploy.yaml reacts to this push,
# tags v<version> (with its own app token), and publish.yaml publishes.
- name: Commit and push release
if: steps.semantic.outputs.new-release-published == 'true'
run: |
git config user.name "rees46-versioner[bot]"
git config user.email "rees46-versioner[bot]@users.noreply.github.com"
git add pubspec.yaml CHANGELOG.md
git commit -m "chore(release): ${{ steps.semantic.outputs.release-version }}"
git push origin HEAD:master
14 changes: 14 additions & 0 deletions .releaserc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Autobump config for the Flutter SDK.
#
# semantic-release runs in DRY-RUN mode from .github/workflows/version.yaml — it only
# computes the next version + release notes from Conventional Commits (feat: -> minor,
# fix: -> patch, BREAKING CHANGE -> major). It does NOT tag, commit or publish here.
#
# The workflow then writes the computed version into pubspec.yaml + CHANGELOG.md and
# commits `chore(release): <version>` to master. The existing deploy.yaml tags
# v<version>, which triggers publish.yaml -> pub.dev (OIDC). See version.yaml.
branches:
- master
plugins:
- '@semantic-release/commit-analyzer'
- '@semantic-release/release-notes-generator'
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,27 @@ fvm flutter run
- **Android**: when `autoSendPushToken=true`, the native SDK fetches the FCM token via `FirebaseMessaging.getInstance().token` during initialization and sends it.
- **iOS**: when `autoSendPushToken=true`, the native SDK requests notification permission and registers for remote notifications. The Flutter plugin also forwards `didRegisterForRemoteNotificationsWithDeviceToken` and `didReceiveRemoteNotification` AppDelegate callbacks to the native SDK.

### Android push notification icon

On Android the plugin displays incoming pushes itself, so the notification's **small icon** is resolved from the **host app**, never from REES46. You should point it at your own icon:

```xml
<!-- android/app/src/main/AndroidManifest.xml, inside <application> -->
<meta-data
android:name="com.rees46.push.notification_icon"
android:resource="@drawable/ic_stat_notify" />
```

The icon must be a **white, alpha-only silhouette** — Android tints the small icon, so a full-colour image renders as a solid white/grey square. Generate one via Android Studio → *New → Image Asset → Notification Icons*.

**Already using Firebase?** If your app already declares `com.google.firebase.messaging.default_notification_icon`, you don't need to set anything — the plugin reuses that icon. Set `com.rees46.push.notification_icon` only if you want a different icon for REES46 pushes specifically.

Resolution order:

1. The `com.rees46.push.notification_icon` meta-data icon above (recommended if you want a dedicated icon).
2. The existing Firebase `com.google.firebase.messaging.default_notification_icon`, if declared.
3. The app's launcher icon, if neither is set (may look like a square, since a launcher icon is not a silhouette).
4. A neutral non-branded default bundled in the plugin, only if the host has no icon at all.

For Flutter plugin development basics, see Flutter docs: [develop plugins](https://flutter.dev/to/develop-plugins).

7 changes: 7 additions & 0 deletions analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
include: package:flutter_lints/flutter.yaml

# Generated files (e.g. Pigeon *.g.dart) are not hand-edited, so exclude them from
# analysis. NOTE: `dart format` does NOT honour this list, so the CI formatting
# check filters *.g.dart out on the command line instead (see .github/workflows).
analyzer:
exclude:
- "**/*.g.dart"

# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options
10 changes: 7 additions & 3 deletions android/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -84,14 +84,18 @@ dependencies {
// REES46 Android SDK (JitPack).
//
// Published from github.com/rees46/android-sdk as `com.github.rees46:android-sdk:<tag>`.
// v2.33.0 is the first release that exposes the loyalty manager
// (`SDK.instance.loyaltyManager`).
val rees46AndroidSdkVersion = "v2.33.0"
// v2.34.0 adds the catalog read managers (profile, product counters,
// category, collection) on top of the loyalty manager (v2.33.0).
val rees46AndroidSdkVersion = "v2.34.0"
add(
"rees46Implementation",
"com.github.rees46:android-sdk:$rees46AndroidSdkVersion",
)

// Used directly by the push presenter (NotificationCompat / ContextCompat). The native SDK
// depends on the same version but as `implementation`, so it is not exposed transitively.
implementation("androidx.core:core-ktx:1.13.1")

testImplementation("org.jetbrains.kotlin:kotlin-test")
testImplementation("org.mockito:mockito-core:5.0.0")
}
10 changes: 10 additions & 0 deletions android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.rees46.rees46_flutter_sdk">
<application>
<!--
Installs the push display listener at process start so pushes appear even when the app has
been swiped away (cold process, no Flutter engine running). See Rees46PushInitProvider.
-->
<provider
android:name="com.rees46.rees46_flutter_sdk.push.Rees46PushInitProvider"
android:authorities="${applicationId}.rees46pushinitprovider"
android:exported="false" />
</application>
</manifest>
Loading
Loading