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
39 changes: 39 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: CI Tests

on:
push:
branches:
- main
pull_request:
branches:
- main

jobs:
test:
name: Analyze & Test
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Flutter
uses: subosito/flutter-action@v2
with:
channel: stable
cache: true

- name: Install dependencies (browser_router)
run: flutter pub get

- name: Analyze package
run: flutter analyze --fatal-infos --fatal-warnings

- name: Run unit & widget tests with coverage
run: flutter test --coverage

- name: Analyze example
working-directory: example
run: |
flutter pub get
flutter analyze --fatal-infos --fatal-warnings
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
## 0.3.0

* **Feat (Transitions & Motion)**: Implemented modern **Material 3 Transitions** (`fade_through`, `fade_scale`, `shared_axis_x`, `shared_axis_y`, `shared_axis_z`, `scale`) and authentic **Cupertino iOS 16–18** transition (`slide_cupertino` with flat edges, 1/3 horizontal parallax, and left-edge drop shadow).
* **Feat (Custom Transitions)**: Added `CustomBuildTransition` and `customTransition` parameter to `BrowserRoute`, `TraceRoute`, `PageTraceRoute`, `PopupTraceRoute`, `SwipeTraceRoute`, and `OverlayTraceRoute`.
* **Feat (Platform Adaptivity)**: Added `Browser.defaultAdaptiveTransition` providing platform-native motion defaults (Cupertino on iOS/macOS, Shared Axis X on Android/Fuchsia, Fade Scale on Web/Windows/Linux).
* **Feat (Accessibility & WCAG)**: Implemented zero-configuration reduced motion support detecting `MediaQuery.disableAnimationsOf(context)` and `MediaQuery.accessibleNavigationOf(context)` to bypass animations immediately.
* **Perf**: Added `RepaintBoundary` rendering isolation in route transitions to avoid rebuilding ancestor tree rendering layers.
* **Fix (Overlay & Banners)**: Fixed secondary animation in `OverlayModal` to return `kAlwaysDismissedAnimation` (preventing visual offset / hit-test misalignment) and eliminated full-screen root opaque gesture detector in `Banner`, enabling seamless pass-through scrolling, clicks, and interactive banner buttons.
* **Test**: Expanded test coverage to 95.4% (99 passing unit & widget tests) covering modern transitions, accessibility, custom transitions, `Browser`, `BrowserRoute`, `NavigatorX` extensions, `TraceRoute`, modal barriers, swipe physics/gestures, and overlay lifecycle.
* **Docs & Example**: Enhanced `README.md` with complete transitions catalog, custom transitions guide, accessibility guide, and platform adaptive configuration. Converted `example/` into a 100% pure `package:flutter/widgets.dart` showcase with zero Material or Cupertino dependencies.

## 0.2.0

* **Feat**: Modernized codebase to Dart 3.13 syntax, leveraging concise constructor declarations (`new(...)`), dot shorthands, and cleaner expression bodies.
Expand Down
81 changes: 67 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Add `browser_router` to your `pubspec.yaml`:

```yaml
dependencies:
browser_router: ^0.2.0
browser_router: ^0.3.0
```

Then run:
Expand Down Expand Up @@ -313,28 +313,81 @@ Change how a route is presented without altering its widget implementation:
- **`SwipeTraceRoute`**: Displays the route as an interactive bottom sheet with swipe-to-dismiss gestures.
- **`OverlayTraceRoute`**: Displays the route in the overlay layer.

### Available Transitions
### Available Transitions (`RouteTransition`)

- `RouteTransition.slide_right`
- `RouteTransition.slide_left`
- `RouteTransition.slide_up`
- `RouteTransition.slide_down`
- `RouteTransition.fade`
- `RouteTransition.scale`
- `RouteTransition.none`
`browser_router` includes modern Material 3, iOS Cupertino, Web, and legacy presets:

| Preset | Platform / Style | Motion Behavior |
| :--- | :--- | :--- |
| `RouteTransition.fade_through` | **Material 3 & Web** | Outgoing fades out & scales (1.0 -> 0.96), incoming fades in & scales (0.92 -> 1.0). Ideal for bottom nav bars and top-level destinations. |
| `RouteTransition.fade_scale` | **Modern Web & M3** | Snappy zoom-fade (0.95 -> 1.0) with fast easing. Perfect for web SPAs, search overlays, and dialogs. |
| `RouteTransition.shared_axis_x` | **Material 3** | Horizontal directional slide with subtle fade. Ideal for wizards and linear multi-step flows. |
| `RouteTransition.shared_axis_y` | **Material 3** | Vertical directional slide with subtle fade. Ideal for form expansions and vertical progressions. |
| `RouteTransition.shared_axis_z` | **Material 3** | Depth zoom (0.8 -> 1.0 / 1.0 -> 1.1) with fade. Ideal for drill-down hierarchies. |
| `RouteTransition.slide_cupertino`| **Authentic iOS** | iOS native push with left-edge gradient drop shadow and 1/3 parallax on the exiting route. |
| `RouteTransition.scale` | **Popup / Dialog** | Clean scale and fade animation for alerts and confirmation modals. |
| `RouteTransition.slide_right` | **Legacy Slide** | Standard horizontal right slide. |
| `RouteTransition.slide_left` | **Legacy Slide** | Standard horizontal left slide. |
| `RouteTransition.slide_up` | **Legacy Slide** | Standard vertical upward slide. |
| `RouteTransition.slide_down` | **Legacy Slide** | Standard vertical downward slide. |
| `RouteTransition.fade` | **Legacy Fade** | Simple fade in / fade out. |
| `RouteTransition.none` | **Instant** | Zero-duration transition without animation. |

---

### Custom Transitions (`CustomBuildTransition`)

You can define custom transition builders at the route level or per navigation request:

```dart
// At BrowserRoute level
BrowserRoute(
path: '/custom',
page: const CustomScreen(),
customTransition: CustomBuildTransition(
({required animation, required secondaryAnimation, required child}) {
return RotationTransition(
turns: animation,
child: child,
);
},
),
);

// Or per navigation request via TraceRoute
context.pushNamed(
'/custom',
traceRoute: PageTraceRoute(
customTransition: CustomBuildTransition(
({required animation, required secondaryAnimation, required child}) {
return FadeTransition(opacity: animation, child: child);
},
),
),
);
```

---

### Accessibility & Reduced Motion (WCAG Compliance)

All `BrowserPageRoute` and `BrowserPopupRoute` transitions automatically detect system accessibility settings:
- `MediaQuery.disableAnimationsOf(context)`
- `MediaQuery.accessibleNavigationOf(context)`

When motion reduction is requested by the user, transitions bypass animations instantly with zero motion discomfort, requiring zero extra configuration.

---

### Adaptive Transitions and Traces

Configure global transition or presentation rules in `Browser`:
Configure global platform-adaptive transition rules using built-in `Browser.defaultAdaptiveTransition`:

```dart
Browser(
routes: routes,
defaultRoute: routes.first,
adaptiveTransition: (route) {
// Apply platform-specific transitions
return RouteTransition.slide_right;
},
adaptiveTransition: Browser.defaultAdaptiveTransition, // iOS: slide_cupertino, Android: shared_axis_x, Web/Desktop: fade_scale
adaptiveTrace: (name) {
// All routes under /modal/ open as popups automatically
if (name?.startsWith('/modal/') ?? false) {
Expand Down
56 changes: 45 additions & 11 deletions example/lib/app_traces.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import 'package:browser_example/push_args.dart';
import 'package:browser_router/browser.dart';

import 'push_args.dart';
import 'package:flutter/widgets.dart';

/// A centralized, type-safe way to manage all application paths.
/// This avoids using raw strings for routes throughout the app.
Expand Down Expand Up @@ -46,34 +46,68 @@ class AppTrace extends Trace {

/// Navigates to the screen that demonstrates returning arguments via pop.
factory toPopArgs() => AppTrace._(path: AppPath.popArgs.path);
factory toSetPopArgs() =>
AppTrace._(path: AppPath.toSetPopArgs.path);
factory toSetPopArgs() => AppTrace._(path: AppPath.toSetPopArgs.path);

/// Navigates to the intermediate screen for the multi-level pop example.
factory toIntermediate() =>
AppTrace._(path: AppPath.intermediate.path);
factory toIntermediate() => AppTrace._(path: AppPath.intermediate.path);

/// Navigates to the deepest screen for the multi-level pop example.
factory toDeep() => AppTrace._(path: AppPath.deep.path);

/// Presents a specific screen as a modal popup.
factory asPopup() {
/// Presents a specific screen as a modal popup with modern scale transition.
factory asPopup({RouteTransition transition = RouteTransition.scale}) {
return AppTrace._(
path: AppPath.popupContent.path,
traceRoute: const PopupTraceRoute(routeTransition: RouteTransition.fade),
traceRoute: PopupTraceRoute(routeTransition: transition),
);
}

/// Presents the push-args screen as a swipeable bottom sheet.
factory asSheet() {
return AppTrace._(
path: AppPath.pushArgs.path,
args:
PushArgs(message: 'Presented as a Sheet', source: 'SwipeTraceRoute'),
args: const PushArgs(
message: 'Presented as a Sheet',
source: 'SwipeTraceRoute',
),
traceRoute: const SwipeTraceRoute(
screenMaximumPercentage: 0.6,
routeTransition: RouteTransition.none,
),
);
}

/// Navigates using a custom builder transition (e.g. rotation & scale).
factory toCustomTransition({required String name}) {
return AppTrace._(
path: AppPath.pushArgs.path,
args: PushArgs(
message: 'Custom transition: $name',
source: 'CustomBuildTransition',
),
traceRoute: PageTraceRoute(
customTransition: CustomBuildTransition(
({
required animation,
required secondaryAnimation,
required child,
}) {
final curvedAnimation = CurvedAnimation(
parent: animation,
curve: Curves.easeOutBack,
);
return ScaleTransition(
scale: curvedAnimation,
child: RotationTransition(
turns: Tween<double>(begin: 0.85, end: 1).animate(
curvedAnimation,
),
child: child,
),
);
},
),
),
);
}
}
Loading
Loading