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

## 2.0.0

### Breaking Changes
- **Builder signature updated**: `PlayxRoute` and `PlayxShellBranch` now use `PlayxRouteWidgetBuilder` which includes an `isInitialized` parameter:
```dart
// Before (1.x):
builder: (context, state) => MyPage()
// After (2.0):
builder: (context, state, isInitialized) => MyPage()
```

### New Features
- **Shell Builder**: Added `shellBuilder` parameter to `PlayxRoute`, `PlayxShellBranch`, and `PlayxPageConfig`. The shell (AppBar, Drawer, Scaffold) renders immediately during navigation transitions, preventing blank frames. Only the body content waits for the binding's `onEnter` to complete.
```dart
PlayxRoute(
path: '/channels',
shellBuilder: (context, state, isInitialized, child) => Scaffold(
appBar: AppBar(title: Text('Channels')),
drawer: MyDrawer(),
body: child,
),
builder: (context, state, isInitialized) => ChannelsListView(),
binding: ChannelsBinding(),
)
```
- **Non-blocking initialization**: Added `waitForBinding` parameter to `PlayxRoute`, `PlayxShellBranch`, and `PlayxPageConfig`. When set to `false`, the page renders immediately with `isInitialized = false` while `onEnter` runs in the background.
- **Global page configuration via `PlayxPageConfig`**: Added `config` parameter to `PlayxNavigationBuilder` to set global defaults for `loadingWidget`, `waitForBinding`, and `shellBuilder`. Individual routes can override any of these settings.
```dart
PlayxNavigationBuilder(
router: router,
config: PlayxPageConfig(
loadingWidget: Center(child: CircularProgressIndicator()),
waitForBinding: false,
shellBuilder: (context, state, isInitialized, child) => Scaffold(
appBar: AppBar(title: Text('My App')),
body: child,
),
),
builder: (context) => MyApp(),
)
```
- **New typedefs**: `PlayxRouteWidgetBuilder` and `PlayxShellWidgetBuilder` for type-safe builder signatures.
- **Initialization transition animation**: Added `initTransitionDuration` parameter to `PlayxRoute`, `PlayxShellBranch`, and `PlayxPageConfig`. When set, an `AnimatedSwitcher` crossfade smoothly transitions from the loading widget to the page content.

### Configuration Resolution
Route-level parameter → Global `PlayxPageConfig` → Built-in default:
- `loadingWidget`: Route > Global > `SizedBox.shrink()`
- `waitForBinding`: Route > Global > `true`
- `shellBuilder`: Route > Global > `null`
- `initTransitionDuration`: Route > Global > `null` (no animation)

## 1.0.0

### New Features
Expand Down
116 changes: 107 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
- **App Initialization Lifecycle**: Register app-level dependencies (repositories, datasources, services) via `onInitApp` in your bindings, called automatically during boot.
- **Initialization Awaiting**: Use `PlayxNavigation.ensureInitialized` to gate startup logic until all bindings are initialized.
- **Binding Registry**: Access any registered binding by type via `PlayxNavigation.findBinding<T>()`.
- **Shell Builder**: Render page chrome (AppBar, Drawer, Scaffold) immediately during transitions — no more blank frames while bindings initialize.
- **Non-Blocking Initialization**: Optionally render pages immediately with `waitForBinding: false`, letting the builder react to `isInitialized` state.
- **Global Page Configuration**: Set default loading widgets, shell builders, and initialization behavior for all routes via `PlayxPageConfig`.
- **Advanced Route Configuration**: Fine-tune the behavior of your routes with extensive configuration options, including custom transitions, modal behavior, and state management.
- **Route Management**: Easily navigate to routes, replace routes, and handle navigation stacks without the need for buildcontext.
- **Custom Page Transitions**: Use predefined transitions or create your own to enhance the user experience.
Expand All @@ -20,7 +23,7 @@ Add `Playx Navigation` to your `pubspec.yaml`:

```yaml
dependencies:
playx_navigation: ^0.0.1
playx_navigation: ^2.0.0
```
Then, run:

Expand Down Expand Up @@ -83,19 +86,19 @@ final router = GoRouter(
PlayxRoute(
path: Paths.home,
name: Routes.home,
builder: (context, state) => const HomePage(),
builder: (context, state, isInitialized) => const HomePage(),
binding: HomeBinding(),
),
PlayxRoute(
path: Paths.products,
name: Routes.products,
builder: (context, state) => ProductsPage(),
builder: (context, state, isInitialized) => ProductsPage(),
binding: ProductsBinding(),
routes: [
PlayxRoute(
path: Paths.details,
name: Routes.details,
builder: (context, state) =>
builder: (context, state, isInitialized) =>
ProductDetailsPage(product: state.extra as Product),
binding: DetailsBinding(),
),
Expand All @@ -116,7 +119,10 @@ class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return PlayxNavigationBuilder(
router: router,
router: router,
config: PlayxPageConfig(
loadingWidget: Center(child: CircularProgressIndicator()),
),
builder: (context) {
return MaterialApp.router(
title: 'Playx',
Expand Down Expand Up @@ -279,8 +285,9 @@ After initialization, all discovered `PlayxBinding` instances are stored and can

- **App Initialization:** Register app-level dependencies (repositories, datasources, services) via `onInitApp`, called once at startup.
- **Widget-Lifecycle Driven:** `onEnter` fires from `PlayxPage.initState` (once on mount), `onExit` fires from `PlayxPage.dispose` (only when truly removed from the tree).
- **Initialization Blocking:** The route's child widget is only built after its binding's `onEnter` completes. This guarantees any dependencies registered in `onEnter` (like GetX controllers) are available during the first build.
- **Custom Loading Widget:** Each route can provide a `loadingWidget` to be displayed while `onEnter` is initializing (defaults to `SizedBox.shrink()`).
- **Initialization Blocking:** By default, the route's child widget is only built after its binding's `onEnter` completes. This guarantees any dependencies registered in `onEnter` (like GetX controllers) are available during the first build. Set `waitForBinding: false` to render immediately.
- **Shell Builder:** Provide a `shellBuilder` to render page chrome (AppBar, Drawer, Scaffold) immediately during transitions while only the body waits for initialization.
- **Custom Loading Widget:** Each route can provide a `loadingWidget` to be displayed while `onEnter` is initializing (defaults to `SizedBox.shrink()`). Set a global default via `PlayxPageConfig.loadingWidget`.
- **Route-Change Driven:** `onHidden` and `onReEnter` fire from the route-change listener when the top route changes.
- **Shell Route Aware:** In `StatefulShellRoute`, branch switching fires `onHidden`/`onReEnter` (not `onExit`/`onEnter`) because the widget stays alive in its branch.
- **Binding Access:** Retrieve any binding by type via `PlayxNavigation.findBinding<T>()` after initialization.
Expand Down Expand Up @@ -370,6 +377,95 @@ class MyRouteBinding extends PlayxBinding {

By extending `PlayxBinding`, you can efficiently manage both app-level initialization and route-specific lifecycle, ensuring that resources are used optimally.

## Global Page Configuration

### `PlayxPageConfig`

Provide a `PlayxPageConfig` to `PlayxNavigationBuilder` to set global defaults for all routes. Individual route parameters override these globals.

```dart
PlayxNavigationBuilder(
router: router,
config: PlayxPageConfig(
// Default loading widget for all routes:
loadingWidget: Center(child: CircularProgressIndicator()),
// Don't block page build by default:
waitForBinding: false,
// Global shell for persistent AppBar:
shellBuilder: (context, state, isInitialized, child) => Scaffold(
appBar: AppBar(title: Text('My App')),
body: child,
),
),
builder: (context) => MyApp(),
)
```

**Resolution order:** Route-level parameter → Global `PlayxPageConfig` → Built-in default.

| Setting | Route Param | Global Config | Default |
|---|---|---|---|
| Loading widget | `PlayxRoute.loadingWidget` | `PlayxPageConfig.loadingWidget` | `SizedBox.shrink()` |
| Wait for binding | `PlayxRoute.waitForBinding` | `PlayxPageConfig.waitForBinding` | `true` |
| Shell builder | `PlayxRoute.shellBuilder` | `PlayxPageConfig.shellBuilder` | `null` |
| Init transition | `PlayxRoute.initTransitionDuration` | `PlayxPageConfig.initTransitionDuration` | `null` (no animation) |

### Shell Builder

The `shellBuilder` renders page chrome (AppBar, Drawer, Scaffold) **immediately** during navigation transitions. Only the body content waits for the binding's `onEnter` to complete, preventing blank frames.

```dart
PlayxRoute(
path: '/channels',
name: 'channels',
shellBuilder: (context, state, isInitialized, child) => Scaffold(
appBar: AppBar(title: Text('Channels')),
drawer: isInitialized ? MyDrawer() : null,
body: child, // loading widget or actual content
),
builder: (context, state, isInitialized) => ChannelsListView(),
binding: ChannelsBinding(),
)
```

### Non-Blocking Initialization

Set `waitForBinding: false` to render the page immediately while `onEnter` runs in the background. The builder receives `isInitialized` so it can handle its own loading state:

```dart
PlayxRoute(
path: '/profile',
name: 'profile',
builder: (context, state, isInitialized) {
if (!isInitialized) return ProfileSkeleton();
return ProfilePage();
},
binding: ProfileBinding(),
waitForBinding: false,
)
```

### Initialization Animation

Set `initTransitionDuration` to smoothly crossfade from the loading widget to the page content using an `AnimatedSwitcher`:

```dart
// Global: apply to all routes
PlayxPageConfig(
initTransitionDuration: Duration(milliseconds: 300),
)

// Per-route: override for a specific route
PlayxRoute(
path: '/dashboard',
builder: (context, state, isInitialized) => DashboardPage(),
binding: DashboardBinding(),
initTransitionDuration: Duration(milliseconds: 500),
)
```

Set to `Duration.zero` to explicitly disable animation for a specific route when a global duration is set.

## Configuring Routes

### Advanced Routing and Custom Transitions with `PlayxRoute`
Expand All @@ -380,6 +476,8 @@ The `PlayxRoute` class extends the functionality of the `GoRoute` class, providi
`PlayxRoute` is designed to enhance navigation by offering:

- **Lifecycle Management**: Attach custom logic that runs when a route is entered or exited, enabling better control over the state and behavior of your app.
- **Shell Builder**: Render page chrome immediately during transitions to prevent blank frames.
- **Non-Blocking Initialization**: Render pages immediately with `isInitialized` state for custom loading UIs.
- **Page Configuration**: Customize various settings like page title, transition duration, and modal behavior.
- **Custom Transitions**: Apply predefined or custom animations for transitioning between pages.

Expand All @@ -394,7 +492,7 @@ The `PlayxRoute` class extends the functionality of the `GoRoute` class, providi
PlayxRoute(
path: '/dashboard',
name: 'dashboard',
builder: (context, state) => DashboardPage(),
builder: (context, state, isInitialized) => DashboardPage(),
binding: DashboardBinding(),
);
```
Expand Down Expand Up @@ -443,7 +541,7 @@ Example:
PlayxRoute(
path: '/custom',
name: 'customRoute',
builder: (context, state) => CustomPage(),
builder: (context, state, isInitialized) => CustomPage(),
binding: CustomBinding(),
pageConfiguration: PlayxPageConfiguration.customTransition(
transitionsBuilder: (context, animation, secondaryAnimation, child) {
Expand Down
12 changes: 6 additions & 6 deletions example/lib/navigation/pages.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class AppPages {
PlayxRoute(
path: Paths.splash,
name: Routes.splash,
builder: (context, state) => const SplashPage(),
builder: (context, state, isInitialized) => const SplashPage(),
binding: SplashBinding(),
),

Expand Down Expand Up @@ -68,21 +68,21 @@ class AppPages {
PlayxShellBranch(
name: Routes.home,
path: Paths.home,
builder: (context, state) => const HomePage(),
builder: (context, state, isInitialized) => const HomePage(),
binding: HomeBinding(),
),

// Tab 2: Products → Product Details (parent-child push/pop)
PlayxShellBranch(
path: Paths.products,
name: Routes.products,
builder: (context, state) => const ProductsPage(),
builder: (context, state, isInitialized) => const ProductsPage(),
binding: ProductsBinding(),
routes: [
PlayxRoute(
path: Paths.productDetails,
name: Routes.productDetails,
builder: (context, state) =>
builder: (context, state, isInitialized) =>
ProductDetailsPage(product: state.extra as Product?),
binding: DetailsBinding(),
loadingWidget: const Center(
Expand All @@ -96,13 +96,13 @@ class AppPages {
PlayxShellBranch(
path: Paths.explore,
name: Routes.explore,
builder: (context, state) => const ExplorePage(),
builder: (context, state, isInitialized) => const ExplorePage(),
binding: ExploreBinding(),
routes: [
PlayxRoute(
path: Paths.exploreDetails,
name: Routes.exploreDetails,
builder: (context, state) =>
builder: (context, state, isInitialized) =>
ExploreDetailsPage(product: state.extra as Product?),
binding: ExploreDetailsBinding(),
),
Expand Down
22 changes: 11 additions & 11 deletions example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ packages:
dependency: transitive
description:
name: characters
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
url: "https://pub.dev"
source: hosted
version: "1.4.0"
version: "1.4.1"
clock:
dependency: transitive
description:
Expand Down Expand Up @@ -84,10 +84,10 @@ packages:
dependency: transitive
description:
name: go_router
sha256: "7974313e217a7771557add6ff2238acb63f635317c35fa590d348fb238f00896"
sha256: "5540e4a3f416dd4a93458257b908eb88353cbd0fb5b0a3d1bd7d849ba1e88735"
url: "https://pub.dev"
source: hosted
version: "17.1.0"
version: "17.2.1"
leak_tracker:
dependency: transitive
description:
Expand Down Expand Up @@ -132,18 +132,18 @@ packages:
dependency: transitive
description:
name: matcher
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
url: "https://pub.dev"
source: hosted
version: "0.12.17"
version: "0.12.19"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
url: "https://pub.dev"
source: hosted
version: "0.11.1"
version: "0.13.0"
meta:
dependency: transitive
description:
Expand All @@ -166,7 +166,7 @@ packages:
path: ".."
relative: true
source: path
version: "1.0.0"
version: "2.0.0"
sky_engine:
dependency: transitive
description: flutter
Expand Down Expand Up @@ -216,10 +216,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
url: "https://pub.dev"
source: hosted
version: "0.7.7"
version: "0.7.10"
vector_math:
dependency: transitive
description:
Expand Down
2 changes: 2 additions & 0 deletions lib/playx_navigation.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ library;
export 'package:go_router/go_router.dart';

export 'src/binding/playx_binding.dart';
export 'src/models/playx_page_config.dart'
show PlayxPageConfig, PlayxRouteWidgetBuilder, PlayxShellWidgetBuilder;
export 'src/models/playx_page_configuration.dart';
export 'src/models/playx_page_transition.dart';
export 'src/playx_navigation.dart';
Expand Down
Loading
Loading