Skip to content

Commit 9531d41

Browse files
yadaniyilclaude
andauthored
v3.2.0 — bug fixes, new API, CI/CD (#132)
* test: rewrite and expand test suite - Rewrite content_change tests: replace illegal state.didUpdateWidget() calls with proper TestWidgetScreen+setState so the framework drives the lifecycle naturally - Fix duplicate test name in show_hide tests (shadowed the second case) - Remove unnecessary async from all synchronous unit tests - Add 15 new test cases: standalone badge rendering/onTap/ignorePointer, showBadge:false initial state, BadgePosition factory defaults, Icon content change animation, non-Text/Icon limitation documentation, and issue #114 regression (skip:true — confirms early-return bug in didUpdateWidget loop branch) - Note in PLAN.md: always use GestureDetector (not ElevatedButton) when triggering setState in animation tests to avoid ripple pollution Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore: upgrade flutter_lints to ^6.0.0, fix resulting lint warnings - Bump flutter_lints ^2.0.1 → ^6.0.0 in main package - Update flutter SDK lower bound from ">=0.2.5" → ">=3.10.0" to match the Dart >=3.0.0 constraint (Dart 3.0 shipped with Flutter 3.10) - Example app: replace discontinued pedantic ^1.11.1 with flutter_lints ^6.0.0, remove unused integration_test dep, bump cupertino_icons to ^1.0.8, add flutter SDK lower bound - Fix 3 use_super_parameters lint warnings in Badge, BadgePositioned, and TestWidgetScreen constructors Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * docs: mark Phase 1 complete in PLAN.md Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: v3.2.0 — bug fixes, new API, and code quality improvements Bug fixes: - #114: showBadge ignored when loop animation active (didUpdateWidget restructured) - #130: showBadge slow to hide with animation (stop() before reverse()) - #98: border anti-aliasing artifact (border moved into BoxDecoration strokeAlignInside) - #115: animation not re-triggered for custom content (key-based detection) New API: - BadgeStyle.copyWith() covering all 8 fields - BadgePosition.centerStart() and .centerEnd() named constructors - BadgeState.animationController and .appearanceController public getters Code quality: - _BadgeVisual extracted as StatelessWidget (PR #120, reduces rebuilds) - ConstrainedBox + IntrinsicWidth for proportional small badges (PR #111) - BadgeGradient.gradient() asserts document constructor invariants - Example app: 16 lint warnings resolved, pedantic replaced with flutter_lints - README: corrected GIF height, added hide Badge import option Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ci: add GitHub Actions for CI and pub.dev publishing - ci.yml: runs on every PR and push to master (analyze, format check, tests) - publish.yml: publishes to pub.dev on v* tag push using OIDC (no stored secrets) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent f767271 commit 9531d41

27 files changed

Lines changed: 1136 additions & 316 deletions

.github/workflows/ci.yml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
name: CI
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches: [master]
7+
8+
jobs:
9+
test:
10+
name: Test & Analyze
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v4
14+
15+
- uses: subosito/flutter-action@v2
16+
with:
17+
channel: stable
18+
19+
- name: Install dependencies
20+
run: flutter pub get
21+
22+
- name: Analyze
23+
run: flutter analyze lib/ test/ example/
24+
25+
- name: Format check
26+
run: dart format --set-exit-if-changed .
27+
28+
- name: Test
29+
run: flutter test --coverage --test-randomize-ordering-seed random

.github/workflows/publish.yml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
name: Publish to pub.dev
2+
3+
on:
4+
push:
5+
tags:
6+
- 'v*'
7+
8+
jobs:
9+
publish:
10+
name: Publish
11+
runs-on: ubuntu-latest
12+
permissions:
13+
id-token: write # required for OIDC pub.dev publishing
14+
15+
steps:
16+
- uses: actions/checkout@v4
17+
18+
- uses: subosito/flutter-action@v2
19+
with:
20+
channel: stable
21+
22+
- name: Install dependencies
23+
run: flutter pub get
24+
25+
- name: Analyze
26+
run: flutter analyze lib/ test/
27+
28+
- name: Test
29+
run: flutter test --test-randomize-ordering-seed random
30+
31+
- name: Publish
32+
run: flutter pub publish --force

CHANGELOG.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,27 @@
1+
## [3.2.0] - [April 9, 2026]
2+
3+
### Bug Fixes
4+
* **Issue #114** — Fixed `showBadge: false` being ignored when a loop animation is active. `didUpdateWidget` now handles `showBadge` changes before any loop-animation guards.
5+
* **Issue #130**`showBadge: false` now hides the badge immediately. `_animationController.stop()` and `_appearanceController.stop()` are called before `reverse()` so the badge does not wait for the animation to play through.
6+
* **Issue #98** — Fixed border anti-aliasing artifact. Border is now drawn entirely inside the circle via `strokeAlign: BorderSide.strokeAlignInside` in `BoxDecoration`, preventing background-colour bleed at antialiased edges.
7+
* **Issue #115 (partial)** — Key-based animation re-trigger: setting a `Key` on `badgeContent` and changing it now restarts the animation for any widget type, not just `Text` and `Icon`.
8+
9+
### New Features
10+
* **`BadgeStyle.copyWith`** — Returns a copy of a `BadgeStyle` with overridden fields.
11+
* **`BadgePosition.centerStart`** — New named constructor: badge vertically centered on the start (left) side.
12+
* **`BadgePosition.centerEnd`** — New named constructor: badge vertically centered on the end (right) side.
13+
* **`BadgeState.animationController` / `appearanceController` getters** — Public access to the internal animation controllers for advanced use cases (PR #128).
14+
* **`_BadgeVisual` private widget** — Extracted badge visual from an inner closure into a proper `StatelessWidget` so Flutter's element tree can cache it and avoid rebuilding the full subtree on every opacity tick (PR #120).
15+
* **Minimum-square badge sizing** — Single-character text and small-icon badges now maintain correct circular/square proportions (PR #111).
16+
* **`BadgeGradient.gradient()` asserts** — Added `assert` statements documenting constructor invariants instead of silently force-unwrapping nullable fields.
17+
* **Controller duration updates**`didUpdateWidget` now updates `AnimationController` durations when `badgeAnimation` duration properties change.
18+
19+
### Example App & Docs
20+
* Fixed all lint warnings in `example/` (`use_super_parameters`, `prefer_final_fields`, `curly_braces_in_flow_control_structures`, `avoid_print`, `deprecated_member_use`).
21+
* README: fixed showcase GIF height (`600px``400px`).
22+
* README: added `hide Badge` import pattern (issue #123).
23+
* Bumped `flutter_lints` to `^6.0.0`.
24+
125
## [3.1.2] - [August 28, 2023]
226
* Update Dart SDK version. Update readme.
327

CLAUDE.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Commands
6+
7+
```bash
8+
# Get dependencies
9+
flutter pub get
10+
11+
# Run all tests (with randomized ordering)
12+
flutter test --coverage --test-randomize-ordering-seed random
13+
14+
# Run a single test file
15+
flutter test test/badges_test.dart
16+
17+
# Lint
18+
flutter analyze .
19+
20+
# Format (CI enforces this — run before committing)
21+
flutter format .
22+
```
23+
24+
## Architecture
25+
26+
This is a Flutter package (`badges`) that provides a single `Badge` widget for overlaying notification-style badges on any widget.
27+
28+
### Public API (`lib/badges.dart`)
29+
30+
All public exports go through `lib/badges.dart`. Consumers import as:
31+
```dart
32+
import 'package:badges/badges.dart' as badges;
33+
```
34+
The `as badges` alias is required because Flutter 3.7+ added a `Badge` widget to Material that conflicts.
35+
36+
### Core widget (`lib/src/badge.dart`)
37+
38+
`Badge` is a `StatefulWidget` with `TickerProviderStateMixin`. It manages two `AnimationController`s:
39+
- `_animationController` — drives the content-change animation (slide, scale, fade, size, rotation)
40+
- `_appearanceController` — drives the fade in/out when `showBadge` toggles
41+
42+
The `didUpdateWidget` override is where animation re-triggering logic lives: it watches for changes to `badgeContent` (Text/Icon data), `badgeColor`, `showBadge`, and `loopAnimation`.
43+
44+
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.
45+
46+
### Configuration objects
47+
48+
- **`BadgeStyle`** — visual properties: shape, color, gradient, border, padding, elevation
49+
- **`BadgeAnimation`** — named constructors per animation type (`.slide()`, `.fade()`, `.scale()`, `.size()`, `.rotation()`); each sets the `animationType` field and relevant defaults
50+
- **`BadgePosition`** — named constructors (`topEnd`, `topStart`, `bottomEnd`, `bottomStart`, `center`) plus custom offsets
51+
- **`BadgeGradient`** — named constructors (`.linear()`, `.radial()`, `.sweep()`) wrapping Flutter gradient types
52+
- **`BadgeShape`** — enum: `circle`, `square`, `twitter`, `instagram`
53+
54+
### Custom shapes
55+
56+
`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.
57+
58+
### Internal utilities (not exported)
59+
60+
- `lib/src/utils/calculation_utils.dart` — computes padding and position adjustments for tappable badges
61+
- `lib/src/utils/gradient_utils.dart` — gradient rendering helpers
62+
- `lib/src/badge_border_gradient.dart` — custom `BoxBorder` subclass that paints a gradient border
63+
- `lib/src/badge_gradient_type.dart` — internal enum used by `BadgeGradient`
64+
65+
### Tests (`test/`)
66+
67+
`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.

PLAN.md

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
# flutter_badges — Renovation Plan
2+
3+
> Track progress across sessions. Check off items as they are completed.
4+
> Current Flutter: **3.41.6** | Dart: **3.11.4** | Package version: **3.2.0**
5+
6+
---
7+
8+
## Phase 1 — Dependency & Tooling Updates ✅ Completed
9+
10+
- [x] **1.1** Bumped `flutter_lints` `^2.0.1``^6.0.0` in main package
11+
- [x] **1.2** Example app: replaced `pedantic ^1.11.1` (discontinued) with `flutter_lints ^6.0.0`, removed unused `integration_test` dep, bumped `cupertino_icons ^1.0.4``^1.0.8`
12+
- [x] **1.3** Updated flutter SDK lower bound `">=0.2.5"``">=3.10.0"` in both pubspec files (Flutter 3.10 is where Dart 3.0 landed)
13+
- [x] **1.4** Ran `flutter pub upgrade`; lockfiles are gitignored so not committed
14+
- [x] Fixed 3 `use_super_parameters` lint warnings introduced by the new lint rules (`Badge`, `BadgePositioned`, `TestWidgetScreen`)
15+
16+
---
17+
18+
## Phase 2 — Bug Fixes (Open Issues) ✅ Completed
19+
20+
- [x] **2.1** **Issue #114`showBadge` ignored when fade loop animation is active**
21+
- Restructured `didUpdateWidget` so `showBadge` changes are handled **before** any loop-animation guards. The early-return that blocked `_appearanceController.reverse()` is gone.
22+
23+
- [x] **2.2** **Issue #130`showBadge` is slow to respond when animation is on**
24+
- `_animationController.stop()` + `_appearanceController.stop()` are now called before `reverse()` when `showBadge` flips to false, so hiding is immediate.
25+
26+
- [x] **2.3** **Issue #98 — Border anti-aliasing artifact (thin inner border visible)**
27+
- Border moved from `Material.shape` into `BoxDecoration` with `strokeAlign: BorderSide.strokeAlignInside`. Material shape uses `CircleBorder()` / `RoundedRectangleBorder()` with no side (elevation shadow only). `gradientBorder``boxBorder` typed as `BoxBorder?`.
28+
29+
- [x] **2.4** **Issue #115 — Animation not working (general)**
30+
- Added key-based re-trigger: if `badgeContent` has a `Key` and it changes, the animation restarts. Handles all widget types beyond `Text` and `Icon`.
31+
32+
---
33+
34+
## Phase 3 — Open PRs Review ✅ Completed
35+
36+
- [x] **3.1** **PR #122 — Fix for issue #114 (showBadge + loop fade)**
37+
- Our Phase 2.1 fix supersedes this PR. Cleaner restructuring of `didUpdateWidget`.
38+
39+
- [x] **3.2** **PR #120 — Replace private helper methods with private widgets**
40+
- Extracted `badgeView()` inner closure into `_BadgeVisual` `StatelessWidget` in `badge.dart`. Flutter's element tree now caches it across appearance controller ticks.
41+
42+
- [x] **3.3** **PR #128 — Expose `animationController` and `appearanceController`**
43+
- Added public getters `animationController` and `appearanceController` to `BadgeState`.
44+
45+
- [x] **3.4** **PR #111 — Fix display of square badge with small content size**
46+
- Wrapped badge content in `ConstrainedBox` + `IntrinsicWidth` inside `_BadgeVisual` so single-character / small-icon badges stay proportional.
47+
48+
---
49+
50+
## Phase 4 — Code Quality & Improvements ✅ Completed
51+
52+
- [x] **4.1** **Fixed all 16 lint warnings in `example/`**
53+
- `use_super_parameters` in alarm_app, flag_app, human_avatar, instagram_message, instagram_verified_account, twitter_verified_account, yako_app, test_screen
54+
- `prefer_final_fields` in alarm_app (`_isLooped`)
55+
- `deprecated_member_use` (`withOpacity``withValues`) in instagram_verified_account
56+
- `use_key_in_widget_constructors` + `library_private_types_in_public_api` in main.dart
57+
- `curly_braces_in_flow_control_structures` in test_screen.dart
58+
- `avoid_print` in yako_app.dart (removed print)
59+
60+
- [x] **4.2** **Replaced `badgeView()` helper method with `_BadgeVisual` widget class** — covered by 3.2
61+
62+
- [x] **4.3** **Audited `didUpdateWidget`** — fully restructured with clear priority ordering; covered by 2.1
63+
64+
- [x] **4.4** **Controller getters** — covered by 3.3 (public `animationController` + `appearanceController` getters on `BadgeState`)
65+
66+
- [x] **4.5** **`BadgePosition` — added `centerStart` and `centerEnd` named constructors**
67+
68+
- [x] **4.6** **`BadgeStyle.copyWith`** — added `copyWith` method covering all 8 fields
69+
70+
- [x] **4.7** **`BadgeGradient.gradient()` asserts** — added `assert` statements in each `switch` case documenting constructor invariants instead of silently force-unwrapping
71+
72+
---
73+
74+
## Phase 5 — Example App ✅ Completed
75+
76+
- [x] **5.1** Fixed all 16 lint issues in `example/` — see 4.1 above
77+
- [x] **5.2** Example app builds cleanly with no analyzer warnings
78+
79+
---
80+
81+
## Phase 6 — Tests ✅ Completed
82+
83+
**175 tests passing, 0 skipped.**
84+
85+
Changes made:
86+
- **Rewrote `content_change_badge_animation_tests.dart`**: removed illegal direct calls to `state.didUpdateWidget()` — tests now use `TestWidgetScreen` with `setState` so the framework calls `didUpdateWidget` naturally.
87+
- **Fixed `show_hide_badge_animation_tests.dart`**: renamed duplicate test name.
88+
- **Cleaned `utils_tests.dart`**: removed unnecessary `async` keyword from unit tests.
89+
- **Added new test groups to `badges_test.dart`**:
90+
- `Badge without child`
91+
- `showBadge false at initial render`
92+
- `BadgePosition factory defaults`
93+
- `Icon content change triggers animation`
94+
- `Non-Text/non-Icon content change does not re-trigger animation`
95+
- `Issue #114 regression` — now passing (was previously skipped)
96+
- **Removed `skip: true`** from the issue #114 regression test after Phase 2 fix.
97+
98+
---
99+
100+
## Phase 7 — Documentation & README ✅ Completed
101+
102+
- [x] **7.1** Fixed README main GIF height: `600px``400px`
103+
- [x] **7.2** Added `hide Badge` import pattern (Option 2) to README
104+
- [x] **7.3** Bumped version: `3.1.2``3.2.0` in `pubspec.yaml` and README
105+
- [x] **7.4** Updated `CHANGELOG.md` with all changes for 3.2.0
106+
107+
---
108+
109+
## Phase 8 — Release
110+
111+
- [x] **8.1** Version bump: `3.2.0` (new features: `copyWith`, new positions, controller getters, key-based animation trigger, `_BadgeVisual` widget, min-square sizing)
112+
- [x] **8.2** All 175 tests passing
113+
- [x] **8.3** `flutter analyze lib/ test/ example/` — 0 issues
114+
- [x] **8.4** `dart format .` — all files formatted
115+
- [ ] **8.5** Publish: `flutter pub publish` — intentionally skipped per task instructions
116+
117+
---
118+
119+
## Notes & Decisions Log
120+
121+
| Date | Decision |
122+
|------|----------|
123+
| 2026-04-09 | PR #122 superseded by cleaner Phase 2.1 restructure of `didUpdateWidget` |
124+
| 2026-04-09 | PR #111 triangle shape deferred; only size-fix implemented |
125+
| 2026-04-09 | `BadgeController` abstraction (issue #127 / PR #128) deferred; raw controller getters added as simpler API |
126+
| 2026-04-09 | `copyWith` added to `BadgeStyle` only (not `BadgeAnimation` / `BadgePosition` — lower demand) |

README.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,29 @@
1212
<img src="https://github.com/yako-dev/flutter_badges/blob/master/images/readme_header.png?raw=true">
1313
</p>
1414
<p align="center">
15-
<img src="https://github.com/yako-dev/flutter_badges/blob/master/images/showcase.gif?raw=true" height="600px">
15+
<img src="https://github.com/yako-dev/flutter_badges/blob/master/images/showcase.gif?raw=true" height="400px">
1616
</p>
1717

1818

1919
## Installing:
2020
In your pubspec.yaml
2121
```yaml
2222
dependencies:
23-
badges: ^3.1.2
23+
badges: ^3.2.0
2424
```
2525
Attention! In Flutter 3.7 the Badge widget was introduced in the Material library, so to escape the ambiguous imports you need to import the package like this:
26+
27+
**Option 1: namespace prefix**
2628
```dart
2729
import 'package:badges/badges.dart' as badges;
2830
```
2931
and then use the "badges.Badge" widget instead of the "Badge" widget. The same for all the classes from this package.
32+
33+
**Option 2: hide Flutter's Material Badge widget**
34+
```dart
35+
import 'package:badges/badges.dart';
36+
import 'package:flutter/material.dart' hide Badge;
37+
```
3038
<br>
3139
<br>
3240

example/lib/alarm_app.dart

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,14 @@ import 'package:badges/badges.dart' as badges;
22
import 'package:flutter/material.dart';
33

44
class AlarmApp extends StatefulWidget {
5-
const AlarmApp({
6-
Key? key,
7-
}) : super(key: key);
5+
const AlarmApp({super.key});
86

97
@override
108
State<AlarmApp> createState() => _AlarmAppState();
119
}
1210

1311
class _AlarmAppState extends State<AlarmApp> {
14-
bool _isLooped = true;
12+
final bool _isLooped = true;
1513
int counter = 1;
1614

1715
@override

example/lib/flag_app.dart

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@ import 'package:badges/badges.dart' as badges;
22
import 'package:flutter/material.dart';
33

44
class FlagApp extends StatelessWidget {
5-
const FlagApp({
6-
Key? key,
7-
}) : super(key: key);
5+
const FlagApp({super.key});
86

97
@override
108
Widget build(BuildContext context) {

example/lib/human_avatar.dart

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@ import 'package:flutter/material.dart';
22
import 'package:badges/badges.dart' as badges;
33

44
class HumanAvatar extends StatefulWidget {
5-
const HumanAvatar({
6-
Key? key,
7-
}) : super(key: key);
5+
const HumanAvatar({super.key});
86

97
@override
108
State<HumanAvatar> createState() => _HumanAvatarState();

example/lib/instagram_message.dart

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ import 'package:flutter/material.dart';
33

44
class InstagramMessage extends StatefulWidget {
55
const InstagramMessage(
6-
{Key? key, required this.text, required this.emojiReaction})
7-
: super(key: key);
6+
{super.key, required this.text, required this.emojiReaction});
87

98
final String text;
109
final String emojiReaction;

0 commit comments

Comments
 (0)