diff --git a/CHANGELOG.md b/CHANGELOG.md index b9da6df..d0b91e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,51 +1,59 @@ # Changelog -## 2.0.0 +## 2.1.0 -### Breaking Changes -- **Builder signature updated**: `PlayxRoute` and `PlayxShellBranch` now use `PlayxRouteWidgetBuilder` which includes an `isInitialized` parameter: +### New Features +- **Init-aware builder (`initBuilder`)**: Added an optional `initBuilder` parameter to `PlayxRoute` and `PlayxShellBranch` that receives `(context, state, isInitialized)`. When provided, the user has full control over what to render based on the binding's initialization state. The existing `builder` with `(context, state)` signature remains unchanged — the library continues to manage loading/content switching automatically. ```dart - // Before (1.x): - builder: (context, state) => MyPage() - // After (2.0): - builder: (context, state, isInitialized) => MyPage() - ``` + // Standard builder — library manages loading (unchanged from 1.0.0): + PlayxRoute( + path: '/products', + builder: (context, state) => ProductsPage(), + binding: ProductsBinding(), + ) -### 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. + // Init-aware builder — user handles everything: + PlayxRoute( + path: '/profile', + initBuilder: (context, state, isInitialized) { + if (!isInitialized) return ProfileSkeleton(); + return ProfilePage(); + }, + binding: ProfileBinding(), + ) + ``` +- **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. Only applies when using `builder`, not `initBuilder`. ```dart PlayxRoute( path: '/channels', + builder: (context, state) => ChannelsListView(), 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. +- **Non-blocking initialization**: Added `waitForBinding` parameter to `PlayxRoute`, `PlayxShellBranch`, and `PlayxPageConfig`. When set to `false`, the page content renders immediately while `onEnter` runs in the background. Only applies when using `builder`. +- **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. Only applies when using `builder`. +- **Global page configuration via `PlayxPageConfig`**: Added `config` parameter to `PlayxNavigationBuilder` to set global defaults for `loadingWidget`, `waitForBinding`, `shellBuilder`, and `initTransitionDuration`. 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, - ), + waitForBinding: true, + initTransitionDuration: Duration(milliseconds: 300), ), 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: +Route-level parameter → Global `PlayxPageConfig` → Built-in default. +These settings only apply when using the standard `builder`, not `initBuilder`: - `loadingWidget`: Route > Global > `SizedBox.shrink()` - `waitForBinding`: Route > Global > `true` - `shellBuilder`: Route > Global > `null` diff --git a/README.md b/README.md index 9c19e8f..c496c81 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,8 @@ - **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()`. - **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. +- **Init-Aware Builder**: Use `initBuilder` with `(context, state, isInitialized)` for full control over rendering during initialization. +- **Non-Blocking Initialization**: Optionally render pages immediately with `waitForBinding: false`. - **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. @@ -23,7 +24,7 @@ Add `Playx Navigation` to your `pubspec.yaml`: ```yaml dependencies: - playx_navigation: ^2.0.0 + playx_navigation: ^2.1.0 ``` Then, run: @@ -86,19 +87,19 @@ final router = GoRouter( PlayxRoute( path: Paths.home, name: Routes.home, - builder: (context, state, isInitialized) => const HomePage(), + builder: (context, state) => const HomePage(), binding: HomeBinding(), ), PlayxRoute( path: Paths.products, name: Routes.products, - builder: (context, state, isInitialized) => ProductsPage(), + builder: (context, state) => ProductsPage(), binding: ProductsBinding(), routes: [ PlayxRoute( path: Paths.details, name: Routes.details, - builder: (context, state, isInitialized) => + builder: (context, state) => ProductDetailsPage(product: state.extra as Product), binding: DetailsBinding(), ), @@ -412,42 +413,57 @@ PlayxNavigationBuilder( ### 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. +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. Only applies when using the standard `builder`. ```dart PlayxRoute( path: '/channels', name: 'channels', + builder: (context, state) => ChannelsListView(), 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: +Set `waitForBinding: false` to render the page content immediately while `onEnter` runs in the background: ```dart PlayxRoute( path: '/profile', name: 'profile', - builder: (context, state, isInitialized) { + builder: (context, state) => ProfilePage(), + binding: ProfileBinding(), + waitForBinding: false, +) +``` + +### Init-Aware Builder + +For full control over what renders during initialization, use `initBuilder` instead of `builder`. The `initBuilder` receives `isInitialized` so the user can handle loading states: + +```dart +PlayxRoute( + path: '/profile', + name: 'profile', + initBuilder: (context, state, isInitialized) { if (!isInitialized) return ProfileSkeleton(); return ProfilePage(); }, binding: ProfileBinding(), - waitForBinding: false, ) ``` +When using `initBuilder`, the library does **not** apply `shellBuilder`, `loadingWidget`, `waitForBinding`, or `initTransitionDuration` — the user handles everything. + ### Initialization Animation -Set `initTransitionDuration` to smoothly crossfade from the loading widget to the page content using an `AnimatedSwitcher`: +Set `initTransitionDuration` to smoothly crossfade from the loading widget to the page content using an `AnimatedSwitcher`. Only applies when using the standard `builder`: ```dart // Global: apply to all routes @@ -458,7 +474,7 @@ PlayxPageConfig( // Per-route: override for a specific route PlayxRoute( path: '/dashboard', - builder: (context, state, isInitialized) => DashboardPage(), + builder: (context, state) => DashboardPage(), binding: DashboardBinding(), initTransitionDuration: Duration(milliseconds: 500), ) @@ -477,7 +493,8 @@ The `PlayxRoute` class extends the functionality of the `GoRoute` class, providi - **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. +- **Init-Aware Builder**: Use `initBuilder` with `isInitialized` for full control over rendering during initialization. +- **Non-Blocking Initialization**: Render pages immediately with `waitForBinding: false`. - **Page Configuration**: Customize various settings like page title, transition duration, and modal behavior. - **Custom Transitions**: Apply predefined or custom animations for transitioning between pages. @@ -492,7 +509,7 @@ The `PlayxRoute` class extends the functionality of the `GoRoute` class, providi PlayxRoute( path: '/dashboard', name: 'dashboard', - builder: (context, state, isInitialized) => DashboardPage(), + builder: (context, state) => DashboardPage(), binding: DashboardBinding(), ); ``` @@ -541,7 +558,7 @@ Example: PlayxRoute( path: '/custom', name: 'customRoute', - builder: (context, state, isInitialized) => CustomPage(), + builder: (context, state) => CustomPage(), binding: CustomBinding(), pageConfiguration: PlayxPageConfiguration.customTransition( transitionsBuilder: (context, animation, secondaryAnimation, child) { diff --git a/example/lib/navigation/pages.dart b/example/lib/navigation/pages.dart index 6c336e1..e55e84b 100644 --- a/example/lib/navigation/pages.dart +++ b/example/lib/navigation/pages.dart @@ -34,7 +34,7 @@ class AppPages { PlayxRoute( path: Paths.splash, name: Routes.splash, - builder: (context, state, isInitialized) => const SplashPage(), + builder: (context, state) => const SplashPage(), binding: SplashBinding(), ), @@ -68,7 +68,7 @@ class AppPages { PlayxShellBranch( name: Routes.home, path: Paths.home, - builder: (context, state, isInitialized) => const HomePage(), + builder: (context, state) => const HomePage(), binding: HomeBinding(), ), @@ -76,13 +76,13 @@ class AppPages { PlayxShellBranch( path: Paths.products, name: Routes.products, - builder: (context, state, isInitialized) => const ProductsPage(), + builder: (context, state) => const ProductsPage(), binding: ProductsBinding(), routes: [ PlayxRoute( path: Paths.productDetails, name: Routes.productDetails, - builder: (context, state, isInitialized) => + builder: (context, state) => ProductDetailsPage(product: state.extra as Product?), binding: DetailsBinding(), loadingWidget: const Center( @@ -96,13 +96,13 @@ class AppPages { PlayxShellBranch( path: Paths.explore, name: Routes.explore, - builder: (context, state, isInitialized) => const ExplorePage(), + builder: (context, state) => const ExplorePage(), binding: ExploreBinding(), routes: [ PlayxRoute( path: Paths.exploreDetails, name: Routes.exploreDetails, - builder: (context, state, isInitialized) => + builder: (context, state) => ExploreDetailsPage(product: state.extra as Product?), binding: ExploreDetailsBinding(), ), diff --git a/lib/src/routes/playx_page.dart b/lib/src/routes/playx_page.dart index a53cc15..2b25286 100644 --- a/lib/src/routes/playx_page.dart +++ b/lib/src/routes/playx_page.dart @@ -13,17 +13,22 @@ import 'package:playx_navigation/src/models/playx_page_config.dart'; /// the visible route. This prevents unnecessary controller registration /// and API calls for pages the user hasn't navigated to yet. /// -/// **Shell builder support:** +/// **Two builder paths:** +/// +/// 1. **Standard builder** (`childBuilder`): The library automatically manages +/// loading state — showing the loading widget / shell while `onEnter` runs +/// and revealing the content once initialized. +/// +/// 2. **Init-aware builder** (`initBuilder`): The user receives `isInitialized` +/// and takes full control of what to render during and after initialization. +/// Shell builder, loading widget, and transition animation are NOT applied. +/// +/// **Shell builder support (standard builder only):** /// When a [shellBuilder] is provided (either per-route or globally via /// [PlayxPageConfig]), the shell (AppBar, Drawer, Scaffold) is rendered /// immediately during the page transition. Only the body content waits /// for initialization, preventing blank frames during navigation. /// -/// **Non-blocking initialization:** -/// When [waitForBinding] is `false`, the page content renders immediately -/// with `isInitialized = false`. The binding's `onEnter` still runs in the -/// background and triggers a rebuild when complete. -/// /// **Lifecycle:** /// - Mount as top route → `onEnter` fires immediately. /// - Mount as backstack → deferred, shows loading/shell, fires `onEnter` @@ -33,30 +38,39 @@ class PlayxPage extends StatefulWidget { final PlayxBinding binding; final GoRouterState state; - /// Builder function for the page content. Receives [isInitialized] to allow - /// the content to react to the binding's initialization state. - final PlayxRouteWidgetBuilder childBuilder; + /// Standard builder — library manages loading/content switching automatically. + /// Receives only `(context, state)`. + final GoRouterWidgetBuilder? childBuilder; + + /// Init-aware builder — user gets `isInitialized` and handles everything. + /// Receives `(context, state, isInitialized)`. + /// When provided, [shellBuilder], [loadingWidget], [waitForBinding], and + /// [initTransitionDuration] are not applied (user has full control). + final PlayxRouteWidgetBuilder? initBuilder; /// Optional shell builder for persistent chrome (AppBar, Drawer, Scaffold). /// When provided, the shell is rendered immediately — only the body content /// depends on initialization state. /// Overrides the global [PlayxPageConfig.shellBuilder]. + /// Only applies when using [childBuilder], not [initBuilder]. final PlayxShellWidgetBuilder? shellBuilder; /// An optional widget that is displayed while the [binding]'s `onEnter` is /// being initialized. Overrides the global [PlayxPageConfig.loadingWidget]. /// Defaults to [SizedBox.shrink()] when neither route-level nor global is set. + /// Only applies when using [childBuilder], not [initBuilder]. final Widget? loadingWidget; /// Whether to block the page build until `onEnter` completes. + /// Only applies when using [childBuilder], not [initBuilder]. /// /// - `null`: Use the global default from [PlayxPageConfig.waitForBinding]. /// - `true`: Block the build — show loading/shell until `onEnter` completes. - /// - `false`: Render immediately — `onEnter` runs in background, builder - /// receives `isInitialized = false` until ready. + /// - `false`: Render immediately — content is shown before `onEnter` completes. final bool? waitForBinding; /// Duration for the crossfade animation between loading and content. + /// Only applies when using [childBuilder], not [initBuilder]. /// /// When set, an [AnimatedSwitcher] smoothly transitions from the loading /// widget to the page content after `onEnter` completes. @@ -70,12 +84,16 @@ class PlayxPage extends StatefulWidget { super.key, required this.binding, required this.state, - required this.childBuilder, + this.childBuilder, + this.initBuilder, this.shellBuilder, this.loadingWidget, this.waitForBinding, this.initTransitionDuration, - }); + }) : assert( + childBuilder != null || initBuilder != null, + 'Either childBuilder or initBuilder must be provided.', + ); @override State createState() => _PlayxPageState(); @@ -170,7 +188,12 @@ class _PlayxPageState extends State { @override Widget build(BuildContext context) { - // Resolve effective configuration: route-level > global > defaults. + // --- Init-aware builder: user has full control --- + if (widget.initBuilder != null) { + return widget.initBuilder!(context, widget.state, _initialized); + } + + // --- Standard builder: library manages loading state --- final globalConfig = PlayxPageConfigProvider.of(context); final effectiveShell = widget.shellBuilder ?? globalConfig?.shellBuilder; @@ -182,10 +205,10 @@ class _PlayxPageState extends State { final effectiveDuration = widget.initTransitionDuration ?? globalConfig?.initTransitionDuration; - // --- Shell builder present: shell always renders, child depends on init --- + // Shell builder present: shell always renders, child depends on init. if (effectiveShell != null) { final child = _initialized - ? widget.childBuilder(context, widget.state, true) + ? widget.childBuilder!(context, widget.state) : effectiveLoading; return effectiveShell( context, @@ -195,16 +218,15 @@ class _PlayxPageState extends State { ); } - // --- No shell: respect waitForBinding --- + // No shell: respect waitForBinding. if (!_initialized && effectiveWait) { - // Blocking mode (current default behavior): show loading until ready. + // Blocking mode (default): show loading until ready. return _withTransition(effectiveLoading, effectiveDuration); } // Either initialized, or non-blocking mode: render the child. - // The child receives _initialized so it can handle its own loading state. return _withTransition( - widget.childBuilder(context, widget.state, _initialized), + widget.childBuilder!(context, widget.state), effectiveDuration, ); } diff --git a/lib/src/routes/playx_route.dart b/lib/src/routes/playx_route.dart index c5a51a7..51e6739 100644 --- a/lib/src/routes/playx_route.dart +++ b/lib/src/routes/playx_route.dart @@ -14,62 +14,70 @@ import '../models/playx_page_transition.dart'; /// capabilities, including custom animations for page transitions, configurable /// settings for pages, and bindings for route-specific lifecycle events. /// -/// **Shell builder support:** +/// **Two builder modes:** +/// +/// 1. **Standard builder** (`builder`): Uses the classic `(context, state)` signature. +/// The library automatically manages loading/content switching using +/// [loadingWidget], [shellBuilder], [waitForBinding], and [initTransitionDuration]. +/// +/// 2. **Init-aware builder** (`initBuilder`): Uses `(context, state, isInitialized)`. +/// The user receives the initialization state and takes full control of +/// what to render. Shell builder, loading widget, and other auto-management +/// features are not applied. +/// +/// Only one of [builder] or [initBuilder] should be provided. +/// +/// **Shell builder support (standard builder only):** /// When [shellBuilder] is provided, the page's outer chrome (AppBar, Drawer, /// Scaffold) is rendered immediately during navigation transitions. Only the /// body content waits for the binding's `onEnter` to complete, preventing /// blank frames during page transitions. /// -/// **Non-blocking initialization:** -/// Set [waitForBinding] to `false` to render the page content immediately while -/// `onEnter` runs in the background. The builder receives `isInitialized = false` -/// until the binding is ready. -/// /// **Example Usage:** /// ```dart -/// // With shell builder — AppBar shows immediately: +/// // Standard builder — library manages loading: /// PlayxRoute( /// path: '/channels', /// name: 'channels', +/// builder: (context, state) => ChannelsListView(), +/// binding: ChannelsBinding(), /// shellBuilder: (context, state, isInitialized, child) => Scaffold( /// appBar: AppBar(title: Text('Channels')), -/// drawer: MyDrawer(), /// body: child, /// ), -/// builder: (context, state, isInitialized) => ChannelsListView(), -/// binding: ChannelsBinding(), /// ) /// -/// // Without shell — uses isInitialized to handle loading: +/// // Init-aware builder — user handles everything: /// PlayxRoute( /// path: '/profile', /// name: 'profile', -/// builder: (context, state, isInitialized) { +/// initBuilder: (context, state, isInitialized) { /// if (!isInitialized) return ProfileSkeleton(); /// return ProfilePage(); /// }, /// binding: ProfileBinding(), -/// waitForBinding: false, /// ) /// ``` /// /// **Parameters:** /// - `path`: The URL path of the route, for example, `/profile`. /// - `name`: An optional name for the route, which is useful for navigation and redirection. -/// - `builder`: A [PlayxRouteWidgetBuilder] that builds the widget for this route, -/// receiving the current context, state, and initialization status. +/// - `builder`: A [GoRouterWidgetBuilder] — classic `(context, state)` builder. +/// The library automatically manages loading and content switching. +/// - `initBuilder`: A [PlayxRouteWidgetBuilder] — receives `(context, state, isInitialized)`. +/// The user controls what to render based on initialization state. /// - `shellBuilder`: An optional [PlayxShellWidgetBuilder] that wraps the page -/// content with persistent chrome (AppBar, Drawer). Overrides global default. -/// - `transition`: Specifies the page transition animation to be used. Defaults to [PlayxPageTransition.cupertino]. -/// - `pageConfiguration`: Configures various settings for the page, such as title and key. Defaults to [PlayxPageConfiguration()]. +/// content with persistent chrome (AppBar, Drawer). Only applies with [builder]. +/// - `transition`: Specifies the page transition animation. Defaults to [PlayxPageTransition.cupertino]. +/// - `pageConfiguration`: Configures page settings such as title and key. Defaults to [PlayxPageConfiguration()]. /// - `parentNavigatorKey`: An optional key for the parent navigator. -/// - `binding`: An optional [PlayxBinding] instance used to handle route-specific lifecycle events. -/// - `loadingWidget`: An optional widget shown while `onEnter` is executing. Overrides global default. -/// - `waitForBinding`: Whether to block the build on `onEnter`. `null` uses the global default. -/// - `initTransitionDuration`: Duration for crossfade animation between loading and content. `null` uses global default. -/// - `redirect`: An optional callback function for custom redirection logic. -/// - `onExit`: An optional callback function for handling logic when the route is exited. -/// - `routes`: A list of nested subroutes for this route. Defaults to an empty list. +/// - `binding`: An optional [PlayxBinding] for route-specific lifecycle events. +/// - `loadingWidget`: Widget shown while `onEnter` is executing. Only applies with [builder]. +/// - `waitForBinding`: Whether to block the build on `onEnter`. Only applies with [builder]. +/// - `initTransitionDuration`: Duration for crossfade animation. Only applies with [builder]. +/// - `redirect`: An optional callback for custom redirection logic. +/// - `onExit`: An optional callback for handling logic when the route is exited. +/// - `routes`: A list of nested subroutes. Defaults to an empty list. /// /// **Behavior:** /// - **Page Transition:** The [transition] parameter allows for custom animations when transitioning between pages. @@ -101,28 +109,29 @@ class PlayxRoute extends GoRoute { /// Specifies the page transition animation to be used. /// /// The [transition] determines the animation effect applied when navigating between pages for this route. - /// Defaults to [PlayxPageTransition.cupertino]. Other options might include [PlayxPageTransition.material], [PlayxPageTransition.native], etc. + /// Defaults to [PlayxPageTransition.cupertino]. final PlayxPageTransition transition; /// Configures various settings for the page. /// - /// The [pageConfiguration] parameter allows you to set page-specific configurations such as title, key, and other properties. - /// Or Add custom transitions, animations, and other page-specific settings. + /// The [pageConfiguration] parameter allows you to set page-specific configurations + /// such as title, key, and other properties, or add custom transitions. /// Defaults to [PlayxPageConfiguration()]. final PlayxPageConfiguration pageConfiguration; - /// An optional widget that is displayed while the [binding]'s `onEnter` is being initialized. + /// An optional widget displayed while the [binding]'s `onEnter` is being initialized. + /// Only applies when using [builder], not [initBuilder]. /// /// Overrides the global [PlayxPageConfig.loadingWidget]. /// Defaults to [SizedBox.shrink()] when neither route-level nor global is set. final Widget? loadingWidget; /// Optional shell builder for persistent page chrome (AppBar, Drawer, Scaffold). + /// Only applies when using [builder], not [initBuilder]. /// /// When provided, the shell is rendered **immediately** during navigation - /// transitions. The `child` parameter passed to the shell builder contains - /// either the actual page content (when initialized) or a loading widget - /// (while `onEnter` is running). + /// transitions. The `child` parameter contains either the page content + /// (when initialized) or a loading widget (while `onEnter` is running). /// /// Overrides the global [PlayxPageConfig.shellBuilder]. /// @@ -136,28 +145,26 @@ class PlayxRoute extends GoRoute { final PlayxShellWidgetBuilder? shellBuilder; /// Whether to block the page build until `onEnter` completes. + /// Only applies when using [builder], not [initBuilder]. /// /// - `null` (default): Use the global default from [PlayxPageConfig.waitForBinding]. /// - `true`: Block the build — show loading widget or shell until `onEnter` completes. - /// - `false`: Render content immediately — `onEnter` runs in the background, - /// and the builder receives `isInitialized = false` until ready. + /// - `false`: Render content immediately — `onEnter` runs in the background. final bool? waitForBinding; - /// Duration for the crossfade animation between the loading widget and - /// the initialized page content. - /// - /// When set, an [AnimatedSwitcher] smoothly transitions from the loading - /// state to the actual content after `onEnter` completes. + /// Duration for the crossfade animation between loading and content. + /// Only applies when using [builder], not [initBuilder]. /// /// - `null` (default): Use the global default from [PlayxPageConfig.initTransitionDuration]. /// - [Duration.zero]: No animation (instant swap). - /// - Any positive duration: Crossfade with that duration (e.g., `Duration(milliseconds: 300)`). + /// - Any positive duration: Crossfade with that duration. final Duration? initTransitionDuration; PlayxRoute({ required super.path, super.name, - required PlayxRouteWidgetBuilder builder, + GoRouterWidgetBuilder? builder, + PlayxRouteWidgetBuilder? initBuilder, this.transition = PlayxPageTransition.cupertino, this.pageConfiguration = const PlayxPageConfiguration(), super.parentNavigatorKey, @@ -169,21 +176,40 @@ class PlayxRoute extends GoRoute { super.redirect, super.onExit, super.routes = const [], - }) : super( + }) : assert( + builder != null || initBuilder != null, + 'Either builder or initBuilder must be provided.', + ), + assert( + builder == null || initBuilder == null, + 'Cannot provide both builder and initBuilder. ' + 'Use builder for library-managed loading, or initBuilder for full control.', + ), + super( pageBuilder: (ctx, state) { + final Widget pageChild; + + if (binding == null) { + // No binding — always initialized. + pageChild = initBuilder != null + ? initBuilder(ctx, state, true) + : builder!(ctx, state); + } else { + pageChild = PlayxPage( + binding: binding, + state: state, + childBuilder: builder, + initBuilder: initBuilder, + shellBuilder: shellBuilder, + loadingWidget: loadingWidget, + waitForBinding: waitForBinding, + initTransitionDuration: initTransitionDuration, + ); + } + return transition.buildPage( config: pageConfiguration, - child: binding == null - ? builder(ctx, state, true) - : PlayxPage( - binding: binding, - state: state, - childBuilder: builder, - shellBuilder: shellBuilder, - loadingWidget: loadingWidget, - waitForBinding: waitForBinding, - initTransitionDuration: initTransitionDuration, - ), + child: pageChild, state: state, ); }, diff --git a/lib/src/routes/playx_shell_branch.dart b/lib/src/routes/playx_shell_branch.dart index a5ae72b..c4e3465 100644 --- a/lib/src/routes/playx_shell_branch.dart +++ b/lib/src/routes/playx_shell_branch.dart @@ -5,17 +5,14 @@ import 'package:playx_navigation/playx_navigation.dart'; class PlayxShellBranch extends StatefulShellBranch { /// Creates a new [PlayxShellBranch] with a single route. /// - /// The [path] is the route path that this branch will handle. - /// The [name] is the name of the route. - /// The [builder] is the widget builder for the route, receiving `isInitialized` state. + /// Provide either [builder] or [initBuilder], but not both: + /// - [builder]: Classic `(context, state)` — library manages loading state. + /// - [initBuilder]: New `(context, state, isInitialized)` — user has full control. + /// /// The [shellBuilder] wraps the page content with persistent chrome (AppBar, Drawer). /// The [waitForBinding] controls whether to block the build on `onEnter`. - /// The [transition] is the page transition for the route. - /// The [pageConfiguration] is the page configuration for the route. - /// The [parentNavigatorKey] is the parent navigator key. - /// The [binding] is the binding for the route. - /// The [redirect] is the redirect callback for the route. - /// The [onExit] is the exit callback for the route. + /// The [initTransitionDuration] sets the crossfade animation duration. + /// These only apply when using [builder], not [initBuilder]. PlayxShellBranch({ super.navigatorKey, super.initialLocation, @@ -24,7 +21,8 @@ class PlayxShellBranch extends StatefulShellBranch { super.preload = false, required String path, String? name, - required PlayxRouteWidgetBuilder builder, + GoRouterWidgetBuilder? builder, + PlayxRouteWidgetBuilder? initBuilder, PlayxPageTransition transition = PlayxPageTransition.cupertino, PlayxPageConfiguration pageConfiguration = const PlayxPageConfiguration(), GlobalKey? parentNavigatorKey, @@ -36,11 +34,21 @@ class PlayxShellBranch extends StatefulShellBranch { GoRouterRedirect? redirect, ExitCallback? onExit, List routes = const [], - }) : super(routes: [ + }) : assert( + builder != null || initBuilder != null, + 'Either builder or initBuilder must be provided.', + ), + assert( + builder == null || initBuilder == null, + 'Cannot provide both builder and initBuilder. ' + 'Use builder for library-managed loading, or initBuilder for full control.', + ), + super(routes: [ PlayxRoute( path: path, name: name, builder: builder, + initBuilder: initBuilder, transition: transition, pageConfiguration: pageConfiguration, parentNavigatorKey: parentNavigatorKey, diff --git a/pubspec.yaml b/pubspec.yaml index 0e42ee0..babdde3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: playx_navigation description: Playx Navigation is a Flutter package that enhances app navigation with advanced features like route lifecycle management, custom transitions, and flexible configuration. -version: 2.0.0 +version: 2.1.0 homepage: https://sourcya.io repository: https://github.com/playx-flutter/playx_navigation issue_tracker: https://github.com/playx-flutter/playx_navigation/issues