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
26 changes: 25 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@ on:
pull_request:
push:
branches: [master]
schedule:
- cron: '0 6 * * 1' # Mondays 06:00 UTC: catch breakage from new Flutter releases
workflow_dispatch:

jobs:
test:
name: Test & Analyze
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5

- uses: subosito/flutter-action@v2
with:
Expand All @@ -27,3 +30,24 @@ jobs:

- name: Test
run: flutter test --coverage --test-randomize-ordering-seed random

test-beta:
# Early warning for upcoming deprecations and breaking changes. Allowed to fail.
name: Test & Analyze (beta)
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@v5

- uses: subosito/flutter-action@v2
with:
channel: beta

- name: Install dependencies
run: flutter pub get

- name: Analyze
run: flutter analyze lib/ test/ example/

- name: Test
run: flutter test --test-randomize-ordering-seed random
33 changes: 0 additions & 33 deletions .github/workflows/code-quality-tests.yml

This file was deleted.

8 changes: 3 additions & 5 deletions .github/workflows/conventional-pr-title.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,9 @@ jobs:
main:
name: Validate PR title
runs-on: ubuntu-latest
permissions:
pull-requests: read
steps:
- uses: deepakputhraya/action-pr-title@master
with:
max_length: 100
github_token: ${{ secrets.GITHUB_TOKEN }}
- uses: amannn/action-semantic-pull-request@v4
- uses: amannn/action-semantic-pull-request@v6
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
40 changes: 33 additions & 7 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
# Automated publishing: https://dart.dev/tools/pub/automated-publishing
# Needs, on pub.dev (Admin tab): automated publishing enabled for this repository,
# tag pattern `v{{version}}` and required environment `pub.dev`. On GitHub: an
# environment named `pub.dev`. Release by pushing a tag that matches the pubspec
# version, e.g. `git tag v3.3.0 && git push origin v3.3.0`.
name: Publish to pub.dev

on:
push:
tags:
- 'v*'
- 'v[0-9]+.[0-9]+.[0-9]+' # tag pattern on pub.dev: 'v{{version}}'

jobs:
publish:
name: Publish
test:
name: Test & Analyze
runs-on: ubuntu-latest
permissions:
id-token: write # required for OIDC pub.dev publishing

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5

- uses: subosito/flutter-action@v2
with:
Expand All @@ -28,5 +30,29 @@ jobs:
- name: Test
run: flutter test --test-randomize-ordering-seed random

publish:
# Separate job so the short-lived OIDC token is created right before publishing.
name: Publish
needs: test
runs-on: ubuntu-latest
environment: pub.dev
permissions:
contents: read
id-token: write # Required for authentication using OIDC
steps:
- uses: actions/checkout@v5

# Creates the GitHub-signed OIDC token and registers it with pub for pub.dev.
- uses: dart-lang/setup-dart@v1

# Flutter's `dart` shadows the one from setup-dart; publishing a Flutter
# package needs the Flutter SDK.
- uses: subosito/flutter-action@v2
with:
channel: stable

- name: Install dependencies
run: flutter pub get

- name: Publish
run: flutter pub publish --force
13 changes: 13 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ version
**/doc/api/
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
Expand All @@ -47,6 +48,7 @@ unlinked_spec.ds
# Android related
#**/android/**/gradle-wrapper.jar
**/android/.gradle
**/android/.kotlin/
**/android/captures/
#**/android/gradlew
#**/android/gradlew.bat
Expand Down Expand Up @@ -85,3 +87,14 @@ unlinked_spec.ds
!**/ios/**/default.pbxuser
!**/ios/**/default.perspectivev3
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages

# Generated / local-only files
coverage/
migrate_working_dir/
.build/
.swiftpm/
**/ios/Flutter/ephemeral/
**/ios/Flutter/Flutter.podspec
**/ios/Flutter/flutter_export_environment.sh
**/ios/Flutter/.last_build_id
**/windows/flutter/ephemeral/
117 changes: 117 additions & 0 deletions .pubignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# pub ignores .gitignore in a directory that has a .pubignore, so the rules below
# start as a copy of .gitignore. Keep them in sync.

# Miscellaneous
*.class
*.lock
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/

# IntelliJ related
*.iml
*.ipr
*.iws
.idea/

# Visual Studio Code related
.vscode/

# Flutter repo-specific
/bin/cache/
/bin/mingit/
/dev/benchmarks/mega_gallery/
/dev/bots/.recipe_deps
/dev/bots/android_tools/
/dev/docs/doc/
/dev/docs/lib/
/dev/docs/pubspec.yaml
/packages/flutter/coverage/
version

# Flutter/Dart/Pub related
**/doc/api/
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
build/
flutter_*.png
linked_*.ds
unlinked.ds
unlinked_spec.ds

# Android related
#**/android/**/gradle-wrapper.jar
**/android/.gradle
**/android/.kotlin/
**/android/captures/
#**/android/gradlew
#**/android/gradlew.bat
**/android/local.properties
**/android/**/GeneratedPluginRegistrant.java

# iOS/XCode related
**/ios/**/*.mode1v3
**/ios/**/*.mode2v3
**/ios/**/*.moved-aside
**/ios/**/*.pbxuser
**/ios/**/*.perspectivev3
**/ios/**/*sync/
**/ios/**/.sconsign.dblite
**/ios/**/.tags*
**/ios/**/.vagrant/
**/ios/**/DerivedData/
**/ios/**/Icon?
**/ios/**/Pods/
**/ios/**/.symlinks/
**/ios/**/profile
**/ios/**/xcuserdata
**/ios/.generated/
**/ios/Flutter/App.framework
**/ios/Flutter/Flutter.framework
**/ios/Flutter/Generated.xcconfig
**/ios/Flutter/app.flx
**/ios/Flutter/app.zip
**/ios/Flutter/flutter_assets/
**/ios/ServiceDefinitions.json
**/ios/Runner/GeneratedPluginRegistrant.*

# Exceptions to above rules.
!**/ios/**/default.mode1v3
!**/ios/**/default.mode2v3
!**/ios/**/default.pbxuser
!**/ios/**/default.perspectivev3
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages

# Generated / local-only files
coverage/
migrate_working_dir/
.build/
.swiftpm/
**/ios/Flutter/ephemeral/
**/ios/Flutter/Flutter.podspec
**/ios/Flutter/flutter_export_environment.sh
**/ios/Flutter/.last_build_id
**/windows/flutter/ephemeral/

# Not part of the published package
AGENTS.md
CLAUDE.md
PLAN.md
/coverage/
/example/coverage/
/example/ios/Flutter/ephemeral/
.history/
.idea/

# README loads these from GitHub URLs. Only the pubspec `screenshots:` image ships.
/images/*
!/images/pub_dev_preview.png
67 changes: 67 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# AGENTS.md

Guidance for coding agents (Claude Code, Codex, ...) working in this repository.

## Commands

```bash
# Get dependencies
flutter pub get

# Run all tests (with randomized ordering)
flutter test --coverage --test-randomize-ordering-seed random

# Run a single test file
flutter test test/badges_test.dart

# Lint (CI runs exactly this; infos fail it)
flutter analyze lib/ test/ example/

# Format (CI enforces this — run before committing)
dart format .
```

## Architecture

This is a Flutter package (`badges`) that provides a single `Badge` widget for overlaying notification-style badges on any widget.

### Public API (`lib/badges.dart`)

All public exports go through `lib/badges.dart`. Consumers import as:
```dart
import 'package:badges/badges.dart' as badges;
```
The `as badges` alias is required because Flutter 3.7+ added a `Badge` widget to Material that conflicts.

### Core widget (`lib/src/badge.dart`)

`Badge` is a `StatefulWidget` with `TickerProviderStateMixin`. It manages two `AnimationController`s:
- `_animationController` — drives the content-change animation (slide, scale, fade, size, rotation)
- `_appearanceController` — drives the fade in/out when `showBadge` toggles

The `didUpdateWidget` override is where animation re-triggering logic lives: it watches for changes to `badgeContent` (Text/Icon data), `badgeColor`, `showBadge`, and `loopAnimation`.

When `child` is null, the badge renders standalone. When `child` is provided, it uses a `Stack` with `BadgePositioned` to overlay the badge. When `onTap` is set, extra padding is added to the child and the position is recalculated via `CalculationUtils` to keep the full badge tappable.

### Configuration objects

- **`BadgeStyle`** — visual properties: shape, color, gradient, border, padding, elevation
- **`BadgeAnimation`** — named constructors per animation type (`.slide()`, `.fade()`, `.scale()`, `.size()`, `.rotation()`); each sets the `animationType` field and relevant defaults
- **`BadgePosition`** — named constructors (`topEnd`, `topStart`, `bottomEnd`, `bottomStart`, `center`) plus custom offsets
- **`BadgeGradient`** — named constructors (`.linear()`, `.radial()`, `.sweep()`) wrapping Flutter gradient types
- **`BadgeShape`** — enum: `circle`, `square`, `twitter`, `instagram`

### Custom shapes

`BadgeShape.twitter` and `BadgeShape.instagram` bypass the `Material`/`AnimatedContainer` path and use `CustomPaint` with their respective painters in `lib/src/painters/`. The `DrawingUtils.drawBadgeShape()` helper selects the correct painter.

### Internal utilities (not exported)

- `lib/src/utils/calculation_utils.dart` — computes padding and position adjustments for tappable badges
- `lib/src/utils/gradient_utils.dart` — gradient rendering helpers
- `lib/src/badge_border_gradient.dart` — custom `BoxBorder` subclass that paints a gradient border
- `lib/src/badge_gradient_type.dart` — internal enum used by `BadgeGradient`

### Tests (`test/`)

`badges_test.dart` is the entry point; it imports and calls group functions from `test/badge_animations_tests/`. Tests use `flutter_test` and wrap widgets in `MaterialApp` + `Scaffold`. Animation tests pump specific durations and check `hasRunningAnimations` to assert controller state.
20 changes: 18 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,29 @@
## [4.0.0] - [September 23, 2026]
## [4.0.0] - [Unreleased]

### Breaking Changes
* **Migrated to `package:material_ui`** — Material and Cupertino were decoupled from the Flutter SDK in Flutter 3.47 and now ship as standalone packages. This package now imports `package:material_ui/material_ui.dart` instead of `package:flutter/material.dart`.
* **Migrated to `package:material_ui`** (#135, thanks @mayainle) — Material and Cupertino were decoupled from the Flutter SDK in Flutter 3.47 and now ship as standalone packages. This package now imports `package:material_ui/material_ui.dart` instead of `package:flutter/material.dart`.
* In apps that use `material_ui`, badge content now gets the app's theme text style without `MaterialUiCompatibilityBridge`. Apps still on `package:flutter/material.dart` keep working, but badge text uses the default Material text style until they migrate. This is a major version bump, as the Flutter team recommends for this migration.
* To hide the ambiguous `Badge`, use `import 'package:material_ui/material_ui.dart' hide Badge;` instead of the Flutter Material equivalent.
* **Minimum SDK raised** to Dart `3.13.0` / Flutter `3.47.0`, the floor required by `material_ui`.

### Bug Fixes
* **Issue #133** — The `ignorePointer` docs said the opposite of what it does. `true` lets taps pass through the badge; `false` (default) lets the badge get taps.
* **`BadgeStyle.elevation` works again.** It has drawn no shadow since 3.0.3. The default is now `0`, so badges that don't set it look the same as before.
* **`BadgePosition.center()` with `onTap`** no longer moves the badge to the top-left corner.
* **`BadgePosition.centerStart()` / `centerEnd()`** are now vertically centered, as documented.
* **Right-to-left layouts with `onTap`** no longer shift the badge. The tap padding is now directional.
* **Hidden badges (`showBadge: false`)** no longer catch taps or call `onTap`; taps reach the widget below.
* **Twitter / Instagram shapes** no longer crash with gradients of 3+ colors. Gradient `stops`, `tileMode`, sweep angles and directional alignments are now used.
* **Turning `toAnimate` on at runtime** no longer leaves the badge invisible.
* Fixed a listener leak: every rebuild added a listener to the appearance controller.
* Removed a no-op `ConstrainedBox`. The 3.2.0 "minimum-square sizing" note was wrong: badge sizing did not change in 3.2.0 and does not change now.

### Maintenance
* Replaced `SizeTransition.axisAlignment`, deprecated after Flutter v3.41, with `alignment`. `BadgeAnimation.sizeTransitionAxisAlignment` is unchanged and still takes a `double`.
* CI: removed the broken "Code Quality" workflow, set up pub.dev automated publishing in the publish workflow, updated actions, added a weekly run on Flutter beta.
* Example app: regenerated Android / iOS / Windows projects so it builds again, and fixed the test screen overflowing on phone widths.
* Smaller pub.dev archive (`.pubignore`).
* README: fixed build and downloads badges, documented `onTap` padding and `ignorePointer`.

## [3.2.0] - [April 9, 2026]

Expand Down
Loading
Loading