Repository: flutter
Issue: [go_router] Navigation within the same shell branch should work irrespective whether shell was rebuilt on not
Labels: c: new feature,package,c: proposal,P3,p: go_router,team-go_router,triaged-go_router

### Use case

Stateful tab navigation is convenient and widely used.
go_router provides support for it though the API is unnecessarily restrictive.
It forces developers to ensure the shell widget (Scaffold from example below) is rebuilt even if it does not need to.
For example, when navigation happens within the same branch and we don't need to update the NavigationBar.
Another use case would be when we use NavigationDrawer and don't need to actually know the the `currentIndex` until someone actually opens the drawer.
The code below does not work because the internals of go_router depend implicitly on mandatory shell rebuilding.

<details open><summary>Code sample</summary>

```dart
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

void main() {
  runApp(
    MaterialApp.router(
      routerConfig: GoRouter(
        initialLocation: "/parent",
        routes: [
          StatefulShellRoute.indexedStack(
            builder: (context, state, shell) {
              return ComparableBuilder(
                shell.currentIndex,
                (context) => Scaffold(body: shell),
              );
            },
            branches: [
              StatefulShellBranch(
                routes: [
                  GoRoute(
                    path: "/parent",
                    builder: (context, state) => OutlinedButton(
                      child: const Text('context.go("/parent/child")'),
                      onPressed: () => context.go("/parent/child"),
                    ),
                    routes: [
                      GoRoute(
                        path: "child",
                        builder: (context, state) => const Text("child route"),
                      ),
                    ],
                  ),
                ],
              ),
            ],
          ),
        ],
      ),
    ),
  );
}

class ComparableBuilder extends StatelessWidget {
  const ComparableBuilder(this.index, this.builder);

  final int index;
  final WidgetBuilder builder;

  @override
  Widget build(BuildContext context) {
    return builder(context);
  }

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is ComparableBuilder && index == other.index;

  @override
  int get hashCode => index;
}

```

</details>


### Proposal

Instead of relying on this implicit assumption, it should be possible to make descendants of the shell explicitly depend on whatever is necessary, for example, on GoRouterState by `GoRouterState.of(context)`.
This way even if the shell was not rebuilt - the navigation should still work.
Also, the API would be more flexible if we could explicitly declare a dependency to the `currentIndex`.
It will also minimize the number of created objects if we ensure that the actual widget tree construction is deferred till the moment when the shell is actually built.

So, the ideal shell builder method signature would look something like this provided the widget tree built by `builder` rebuilds itself automatically when necessary:
```dart
typedef ShellRoutePageBuilder = Page<dynamic> Function(
  BuildContext context,
  GoRouterState state,
  WidgetBuilder builder,
);
```

Also, the API to be able to explicitly depend on the `currentIndex` would provider more flexibility (below is the pseudocode):
```dart
CurrentIndex.of(context)
```