Skip to content

Commit

Permalink
Add redirection
Browse files Browse the repository at this point in the history
  • Loading branch information
johnpryan committed Mar 28, 2022
1 parent 5902e0c commit bc9a470
Show file tree
Hide file tree
Showing 11 changed files with 145 additions and 22 deletions.
9 changes: 5 additions & 4 deletions lib/src/delegate.dart
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,16 @@ class TreeRouterDelegate extends RouterDelegate<Uri>
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

@override
Future<void> setNewRoutePath(Uri configuration) {
Future<void> setNewRoutePath(Uri configuration) async {
// TODO: This is probably using decodeComponent incorrectly.
_globalRouteState.goTo(Uri.decodeComponent(configuration.toString()));
await _globalRouteState.goTo(Uri.decodeComponent(configuration.toString()));
return SynchronousFuture(null);
}

@override
Future<void> setInitialRoutePath(Uri configuration) {
_globalRouteState.goTo('$configuration', isInitial: true);
Future<void> setInitialRoutePath(Uri configuration) async {
await _globalRouteState.goTo(Uri.decodeComponent(configuration.toString()),
isInitial: true);
return SynchronousFuture(null);
}
}
14 changes: 14 additions & 0 deletions lib/src/match.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:collection/collection.dart';
import 'package:path/path.dart' as p;
import 'package:quiver/core.dart';
import 'package:tree_router/src/matching.dart';

import 'parameters.dart';
import 'route.dart';

class RouteMatch {
static const _listEquality = ListEquality();

final List<Route> routes;
final Parameters parameters;

Expand All @@ -23,6 +27,16 @@ class RouteMatch {
return fillParameters(p.joinAll(routes.map((r) => r.path)), parameters);
}

@override
bool operator ==(Object other) {
return other is RouteMatch &&
_listEquality.equals(other.routes, routes) &&
other.parameters == parameters;
}

@override
int get hashCode => hash2(path, _listEquality.hash(routes));

@override
String toString() {
return 'RouteMatch: $routes, $parameters';
Expand Down
4 changes: 4 additions & 0 deletions lib/src/matching.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ bool hasExactMatch(String template, String path) {

bool _hasMatch(String template, String path, bool prefix) {
final parameters = <String>[];

// remove query parameters
path = Uri.decodeComponent(Uri.parse(path).path);

var pathRegExp = pathToRegExp(template,
parameters: parameters, prefix: prefix, caseSensitive: true);
return pathRegExp.hasMatch(path);
Expand Down
23 changes: 20 additions & 3 deletions lib/src/route.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,13 @@ class StackedRoute extends Route {
StackedRoute({
required String path,
required this.builder,
Redirect? redirect,
List<Route> children = const [],
}) : super(path: path, children: children);
}) : super(
path: path,
children: children,
redirect: redirect,
);
}

class SwitcherRoute extends Route {
Expand All @@ -25,8 +30,13 @@ class SwitcherRoute extends Route {
required String path,
required this.builder,
this.defaultChild,
Redirect? redirect,
List<Route> children = const [],
}) : super(path: path, children: children);
}) : super(
path: path,
children: children,
redirect: redirect,
);
}

class NestedNavigatorRoute extends Route {
Expand All @@ -35,19 +45,26 @@ class NestedNavigatorRoute extends Route {
NestedNavigatorRoute({
required String path,
required this.builder,
Redirect? redirect,
List<Route> children = const [],
}) : super(path: path, children: children);
}) : super(
path: path,
children: children,
redirect: redirect,
);
}

abstract class Route {
static const _listEquality = ListEquality();

final String path;
final List<Route> children;
final Redirect? redirect;

const Route({
required this.path,
this.children = const [],
this.redirect,
});

@override
Expand Down
33 changes: 23 additions & 10 deletions lib/src/state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart' hide Route;
import 'package:tree_router/src/inheritance.dart';

Expand All @@ -13,12 +14,12 @@ class GlobalRouteState extends ChangeNotifier {
final RouteTree _routeTree;
late RouteMatch _match;

GlobalRouteState(List<Route> routes) : _routeTree = RouteTree(routes);

set match(RouteMatch match) {
// Don't notify listeners if the destination is the same
if (_match == match) return;
GlobalRouteState(List<Route> routes) : _routeTree = RouteTree(routes) {
_match = _routeTree.get('/');
}

Future setMatch(RouteMatch match) async {
match = await _handleRedirects(match);
_match = match;
notifyListeners();
}
Expand All @@ -30,14 +31,26 @@ class GlobalRouteState extends ChangeNotifier {
notifyListeners();
}

void goTo(String path, {Route? parentRoute, bool isInitial = false}) {
Future goTo(String path, {Route? parentRoute, bool isInitial = false}) async {
if (isInitial) {
_match = _routeTree.get(path);
notifyListeners();
await setMatch(_routeTree.get(path));
} else {
match =
_routeTree.get(path, parentRoute: parentRoute, previousMatch: _match);
await setMatch(_routeTree.get(path,
parentRoute: parentRoute, previousMatch: _match));
}
}

Future<RouteMatch> _handleRedirects(RouteMatch match) async {
for (var route in match.routes) {
final redirect = route.redirect;
if (redirect != null) {
final newPath = await redirect(match);
if (newPath != null ) {
return _routeTree.get(newPath, parentRoute: route, previousMatch: match);
}
}
}
return SynchronousFuture(match);
}

static GlobalRouteState? of(BuildContext context) {
Expand Down
4 changes: 4 additions & 0 deletions lib/src/typedefs.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

import 'package:flutter/widgets.dart';

import 'match.dart';

typedef StackedRouteBuilder = Widget Function(
BuildContext context,
);
Expand All @@ -16,3 +18,5 @@ typedef SwitcherRouteBuilder = Widget Function(
typedef NestedNavigatorRouteBuilder = Widget Function(
BuildContext context,
);

typedef Redirect = Future<String?> Function(RouteMatch routeState);
2 changes: 1 addition & 1 deletion test/builder_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ void main() {
),
);

globalRouteState.match = routeMatch;
await globalRouteState.setMatch(routeMatch);
await tester.pumpAndSettle();

expect(find.text('Screen B'), findsOneWidget);
Expand Down
14 changes: 11 additions & 3 deletions test/delegate_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ void main() {
initialRoute: '/',
),
);
await tester.pumpAndSettle();
expect(find.text('Home Screen'), findsOneWidget);
});

Expand Down Expand Up @@ -241,7 +242,7 @@ void main() {
),
);

expect(find.text('AScreen'), findsOneWidget);
// expect(find.text('AScreen'), findsOneWidget);
await tester.pumpAndSettle();
expect(find.text('AScreen'), findsOneWidget);
});
Expand Down Expand Up @@ -346,7 +347,6 @@ void main() {
),
);

expect(find.text('AScreen'), findsOneWidget);
await tester.pumpAndSettle();
expect(find.text('AScreen'), findsOneWidget);
});
Expand All @@ -358,13 +358,21 @@ void main() {
builder: (context) => const _QueryParamsScreen(),
),
];

final routeState = GlobalRouteState(routes);

await tester.pumpWidget(
TestWidget(
routes: routes,
initialRoute: '/?q=foo',
routeState: routeState,
),
);

await tester.pumpAndSettle();
expect(routeState.match.parameters.query, hasLength(1));
await tester.pumpAndSettle();
expect(routeState.match.parameters.query, hasLength(1));
expect(find.text('q: foo'), findsOneWidget);
});
}
Expand Down Expand Up @@ -410,7 +418,7 @@ class _QueryParamsScreen extends StatelessWidget {

@override
Widget build(BuildContext context) {
final queryParams = GlobalRouteState.of(context)!.match.parameters.query;
final queryParams = RouteState.of(context)!.queryParameters;
return Column(
children: [
...queryParams.keys.map((key) => Text('$key: ${queryParams[key]}')),
Expand Down
6 changes: 5 additions & 1 deletion test/helpers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'package:tree_router/src/parser.dart';
import 'package:tree_router/tree_router.dart';

Widget emptyBuilder(context) => const EmptyWidget();

Widget emptySwitcherBuilder(context, child) => child;

class EmptyWidget extends StatelessWidget {
Expand All @@ -26,8 +27,11 @@ class TestWidget extends StatefulWidget {
Key? key,
required List<Route> routes,
TestRouteInformationProvider? informationProvider,
GlobalRouteState? routeState,
String initialRoute = '/',
}) : routerDelegate = TreeRouterDelegate(routes),
}) : routerDelegate = routeState == null
? TreeRouterDelegate(routes)
: TreeRouterDelegate.withState(routeState),
routeInformationParser = TreeRouteInformationParser(),
informationProvider = informationProvider ??
TestRouteInformationProvider(initialRoute: initialRoute),
Expand Down
1 change: 1 addition & 0 deletions test/matcher_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ void main() {
hasExactMatch('Left Hand of Darkness',
Uri.decodeComponent('Left%20Hand%20of%20Darkness')),
true);
expect(hasExactMatch('/', '/?q=foo'), true);
});

test('extractParameters', () {
Expand Down
57 changes: 57 additions & 0 deletions test/redirect_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart' hide Route;
import 'package:flutter_test/flutter_test.dart';
import 'package:tree_router/tree_router.dart';

import 'helpers.dart';

void main() {
group('Redirect', () {
testWidgets('redirects to a relative path at startup',
(WidgetTester tester) async {
final routes = <Route>[
SwitcherRoute(
path: '/',
builder: (_, child) => child,
redirect: (match) {
return SynchronousFuture('a');
},
children: [
StackedRoute(
path: 'a',
builder: (context) {
return const AScreen();
},
),
],
),
];
await tester.pumpWidget(
TestWidget(
routes: routes,
initialRoute: '/',
),
);
await tester.pumpAndSettle();
expect(find.text('Screen A'), findsOneWidget);
});
});
}

class HomeScreen extends StatelessWidget {
const HomeScreen({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
return const Text('Home');
}
}

class AScreen extends StatelessWidget {
const AScreen({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
return const Text('Screen A');
}
}

0 comments on commit bc9a470

Please sign in to comment.