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
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
# Changelog

## 7.0.3

- **Customizable route transitions.** `route(transition:)` and the new app-wide
`ModularApp(defaultTransition:)` now accept any `PageTransition`, an open
contract that builds the route's `Page`. Three ways to supply one:
- the **`TransitionType`** presets (`material`, `fade`, `none`) — each value
now *is* a `PageTransition`, so existing `transition: TransitionType.fade`
keeps working;
- **`CustomTransition`** — the inline convenience: pass a
`transitionsBuilder` (same signature as `PageRouteBuilder`) and optionally
tune `duration`/`reverseDuration`/`opaque`/`barrierColor`/
`barrierDismissible`/`fullscreenDialog`; Modular still owns the `Page`;
- implement **`PageTransition`** yourself for full control of the `Page`
(e.g. a `CupertinoPage` with swipe-back, a `fullscreenDialog`, shared-axis
from the `animations` package).
- **App-wide default.** `ModularApp.defaultTransition` (default
`TransitionType.material`) applies to every route that doesn't declare its
own. Precedence: route-local → app default → `material`. `route(transition:)`
now defaults to `null` (inherit the app default) instead of forcing
`material`.

## 7.0.2

- **`context.select<T, R>(selector)`** — the method-based twin of the `Selector`
Expand Down
71 changes: 68 additions & 3 deletions doc/docs/flutter_modular/navigation.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ c.route(
void Function(Scoped scoped)? provide, // page-scoped state — see State management
void Function(ModularContext c)? children, // nested routes — see Nested routes
List<ModularGuard>? guards, // redirects — see below
TransitionType transition, // material | fade | none
PageTransition? transition, // preset, CustomTransition, or your own — see below
});
```

Expand Down Expand Up @@ -175,13 +175,78 @@ gate authenticated areas, or to redirect unknown paths to a 404 page.

## Transitions

Set a route's page transition with `transition:`:
A route's page transition is any **`PageTransition`** — the contract that turns a
route into the `Page` pushed on the stack. There are three ways to supply one,
from simplest to most powerful.

### 1. Presets

`TransitionType` ships three ready-made transitions, and each value **is** a
`PageTransition`:

```dart
c.route('/details', transition: TransitionType.fade, child: ...);
```

`TransitionType` is `material` (the default), `fade`, or `none` (instant).
`TransitionType` is `material`, `fade`, or `none` (instant).

### 2. `CustomTransition` — bring your own animation

When you just want a different animation, `CustomTransition` builds the `Page` for
you; you supply the `transitionsBuilder` (same signature as
`PageRouteBuilder.transitionsBuilder`) and, optionally, `duration` and a few route
flags (`reverseDuration`, `opaque`, `barrierColor`, `barrierDismissible`,
`fullscreenDialog`):

```dart
c.route('/details/:id',
transition: CustomTransition(
duration: const Duration(milliseconds: 250),
transitionsBuilder: (context, animation, secondary, child) => SlideTransition(
position: animation.drive(
Tween(begin: const Offset(1, 0), end: Offset.zero),
),
child: child,
),
),
child: (ctx, state) => DetailsPage(id: state['id']!));
```

### 3. Implement `PageTransition` — own the whole `Page`

For full control — a `CupertinoPage` with interactive swipe-back, a
`fullscreenDialog`, a custom barrier, shared-axis from the `animations` package —
implement `PageTransition` and return whatever `Page` you like:

```dart
class SharedAxisTransition extends PageTransition {
const SharedAxisTransition();

@override
Page<void> buildPage(LocalKey key, Widget child) =>
// any Page you want — e.g. CupertinoPage, a custom PageRouteBuilder, …
CupertinoPage<void>(key: key, child: child);
}

c.route('/profile', transition: const SharedAxisTransition(), child: ...);
```

### An app-wide default

Set a fallback transition once on `ModularApp.defaultTransition`; every route that
doesn't declare its own inherits it. Precedence is **route-local → app default →
`material`**:

```dart
ModularApp(
module: appModule,
defaultTransition: TransitionType.fade, // every route fades unless it overrides
child: const AppRoot(),
);
```

A route's own `transition:` always wins over the app default; leave it unset to
inherit. With no `defaultTransition` set, the default is `TransitionType.material`.

## Next

Expand Down
10 changes: 7 additions & 3 deletions doc/docs/flutter_modular/start.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,9 +168,10 @@ class CounterPage extends StatelessWidget {
```dart
ModularApp(
module: appModule,
initialRoute: '/home', // first route when the platform reports no deep link
navigatorKey: myNavigatorKey, // imperative access from outside the tree
navigatorObservers: [myObserver], // analytics, RouteObserver, …
initialRoute: '/home', // first route when the platform reports no deep link
navigatorKey: myNavigatorKey, // imperative access from outside the tree
navigatorObservers: [myObserver], // analytics, RouteObserver, …
defaultTransition: TransitionType.fade, // app-wide fallback page transition
child: const AppRoot(),
);
```
Expand All @@ -181,6 +182,9 @@ ModularApp(
- **`navigatorKey`** lets you reach the root `Navigator` imperatively (e.g. to show a
global dialog). A fresh key is created if you omit it.
- **`navigatorObservers`** are attached to the root navigator.
- **`defaultTransition`** is the page transition every route inherits unless it sets its
own `transition:`. Defaults to `TransitionType.material`. See
[Transitions](./navigation.md#transitions).

:::tip Clean URLs on the web
Call `usePathUrlStrategy()` (from `package:flutter_web_plugins/url_strategy.dart`) at
Expand Down
3 changes: 2 additions & 1 deletion lib/flutter_modular.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ export 'src/module/module.dart';
export 'src/navigation/modular_navigation.dart';
export 'src/navigation/modular_router_config.dart';
export 'src/navigation/outlet.dart' show RouterOutlet, RouterOutletState;
export 'src/navigation/transition.dart' show TransitionType;
export 'src/navigation/transition.dart'
show CustomTransition, PageTransition, TransitionType;
export 'src/route/modular_route.dart';
export 'src/route/route_state.dart';
export 'src/state/consumer.dart';
Expand Down
10 changes: 10 additions & 0 deletions lib/src/app/modular_app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'package:flutter/widgets.dart';

import '../module/module.dart';
import '../navigation/modular_router_config.dart';
import '../navigation/transition.dart';
import '../route/route_state.dart';
import '../state/scoped.dart';

Expand Down Expand Up @@ -45,6 +46,7 @@ class ModularApp extends StatefulWidget {
this.initialRoute = '/',
this.navigatorKey,
this.navigatorObservers = const [],
this.defaultTransition = TransitionType.material,
super.key,
});

Expand Down Expand Up @@ -73,6 +75,13 @@ class ModularApp extends StatefulWidget {
/// for route-aware widgets, etc.
final List<NavigatorObserver> navigatorObservers;

/// The app-wide DEFAULT page transition, applied to every route that doesn't
/// declare its own (`route(transition:)` left unset). A route's local
/// transition always wins; this is the fallback. Defaults to
/// [TransitionType.material]. Accepts any [PageTransition] — a preset, a
/// [CustomTransition], or your own implementation.
final PageTransition defaultTransition;

/// The router config built by the nearest enclosing [ModularApp].
static RouterConfig<RouteState> routerConfigOf(BuildContext context) {
final scope = context
Expand All @@ -98,6 +107,7 @@ class _ModularAppState extends State<ModularApp> {
initialRoute: widget.initialRoute,
navigatorKey: widget.navigatorKey,
observers: widget.navigatorObservers,
defaultTransition: widget.defaultTransition,
);

@override
Expand Down
4 changes: 2 additions & 2 deletions lib/src/module/module.dart
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ abstract class ModularContext {
void Function(Scoped scoped)? provide,
void Function(ModularContext c)? children,
List<ModularGuard>? guards,
TransitionType transition,
PageTransition? transition,
});

/// Include another module. The mount path is [at] ?? `module.path`:
Expand Down Expand Up @@ -237,7 +237,7 @@ class _ContextImpl implements ModularContext {
void Function(Scoped scoped)? provide,
void Function(ModularContext c)? children,
List<ModularGuard>? guards,
TransitionType transition = TransitionType.material,
PageTransition? transition,
}) {
var nested = const <ModularRoute>[];
if (children != null) {
Expand Down
3 changes: 3 additions & 0 deletions lib/src/navigation/modular_router_config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import '../route/modular_route.dart';
import '../route/route_state.dart';
import 'modular_route_information_parser.dart';
import 'modular_router_delegate.dart';
import 'transition.dart';

/// Wires the Navigator 2.0 pieces into a [RouterConfig] for
/// `MaterialApp.router(routerConfig: ...)`.
Expand All @@ -20,6 +21,7 @@ RouterConfig<RouteState> modularRouterConfig(
String initialRoute = '/',
GlobalKey<NavigatorState>? navigatorKey,
List<NavigatorObserver> observers = const [],
PageTransition defaultTransition = TransitionType.material,
}) {
final inj = injector ?? (AutoInjector()..commit());
return RouterConfig<RouteState>(
Expand All @@ -29,6 +31,7 @@ RouterConfig<RouteState> modularRouterConfig(
manager: manager,
navigatorKey: navigatorKey,
observers: observers,
defaultTransition: defaultTransition,
),
routeInformationParser: ModularRouteInformationParser(),
routeInformationProvider: PlatformRouteInformationProvider(
Expand Down
9 changes: 8 additions & 1 deletion lib/src/navigation/modular_router_delegate.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,17 @@ class ModularRouterDelegate extends RouterDelegate<RouteState>
this.manager,
GlobalKey<NavigatorState>? navigatorKey,
this.observers = const [],
this.defaultTransition = TransitionType.material,
}) : navigatorKey = navigatorKey ?? GlobalKey<NavigatorState>();

final RouteCollection routes;
final AutoInjector injector;
final ModuleManager? manager;

/// The app-wide fallback transition, applied to any route that declares none
/// (`ModularRoute.transition == null`). Set via `ModularApp.transition`.
final PageTransition defaultTransition;

/// Observers attached to the root [Navigator] (analytics, a `RouteObserver`
/// for route-aware widgets, etc.).
final List<NavigatorObserver> observers;
Expand Down Expand Up @@ -299,8 +304,10 @@ class ModularRouterDelegate extends RouterDelegate<RouteState>
params: const {},
arguments: entry.state.arguments,
routes: routes,
defaultTransition: defaultTransition,
);
return buildTransitionPage(chain.first.route.transition, entry.key, child);
final transition = chain.first.route.transition ?? defaultTransition;
return transition.buildPage(entry.key, child);
}
}

Expand Down
16 changes: 11 additions & 5 deletions lib/src/navigation/outlet.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Widget buildRouteLevel({
required Map<String, String> params,
required RouteCollection routes,
Object? arguments,
PageTransition defaultTransition = TransitionType.material,
}) {
final level = chain[index];
final merged = {...params, ...level.params};
Expand All @@ -39,6 +40,7 @@ Widget buildRouteLevel({
params: merged,
arguments: arguments,
routes: routes,
defaultTransition: defaultTransition,
child: page,
);
}
Expand All @@ -51,6 +53,7 @@ class _OutletScope extends InheritedWidget {
required this.uri,
required this.params,
required this.routes,
required this.defaultTransition,
required super.child,
this.arguments,
});
Expand All @@ -63,6 +66,10 @@ class _OutletScope extends InheritedWidget {
final Object? arguments;
final RouteCollection routes;

/// The app-wide fallback transition, carried down so a nested outlet's pages
/// inherit it for routes that declare none. See [buildRouteLevel].
final PageTransition defaultTransition;

static _OutletScope? of(BuildContext context) =>
context.dependOnInheritedWidgetOfExactType<_OutletScope>();

Expand Down Expand Up @@ -301,12 +308,11 @@ class RouterOutletState extends State<RouterOutlet> {
params: _scope.params,
arguments: entry.arguments,
routes: _scope.routes,
defaultTransition: _scope.defaultTransition,
);
return buildTransitionPage(
chain[_scope.index].route.transition,
entry.key,
child,
);
final transition =
chain[_scope.index].route.transition ?? _scope.defaultTransition;
return transition.buildPage(entry.key, child);
}
}

Expand Down
Loading
Loading