Skip to content

Commit

Permalink
Split to smaller files (canonical#10)
Browse files Browse the repository at this point in the history
  • Loading branch information
jpnurmi authored May 16, 2022
1 parent 37a4068 commit e6faaf0
Show file tree
Hide file tree
Showing 5 changed files with 189 additions and 174 deletions.
26 changes: 26 additions & 0 deletions lib/src/route.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import 'package:flutter/widgets.dart';

/// The signature of [WizardRoute.onNext] and [WizardRoute.onBack] callbacks.
typedef WizardRouteCallback = String? Function(RouteSettings settings);

class WizardRoute {
const WizardRoute({
required this.builder,
this.onNext,
this.onBack,
});

final WidgetBuilder builder;

/// The callback invoked when the next page is requested.
///
/// If `onNext` is not specified or it returns `null`, the order is determined
/// from [routes].
final WizardRouteCallback? onNext;

/// The callback invoked when the previous page is requested.
///
/// If `onBack` is not specified or it returns `null`, the order is determined
/// from [routes].
final WizardRouteCallback? onBack;
}
141 changes: 141 additions & 0 deletions lib/src/scope.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import 'dart:async';

import 'package:flutter/widgets.dart';
import 'package:flow_builder/flow_builder.dart';

import 'route.dart';
import 'settings.dart';

/// The scope of a wizard page.
///
/// Each page is enclosed by a `WizardScope` widget.
class WizardScope extends StatefulWidget {
const WizardScope({
Key? key,
required int index,
required WizardRoute route,
required List<String> routes,
}) : _index = index,
_route = route,
_routes = routes,
super(key: key);

final int _index;
final WizardRoute _route;
final List<String> _routes;

@override
State<WizardScope> createState() => WizardScopeState();
}

/// The state of a `WizardScope`, accessed via `Wizard.of(context)`.
class WizardScopeState extends State<WizardScope> {
/// Arguments passed from the previous page.
///
/// ```dart
/// final something = Wizard.of(context).arguments as Something;
/// ```
Object? get arguments => ModalRoute.of(context)?.settings.arguments;

/// Requests the wizard to show the first page.
///
/// ```dart
/// onPressed: Wizard.of(context).home
/// ```
void home() {
final routes = _getRoutes();
assert(routes.length > 1,
'`Wizard.home()` called from the first route ${routes.last.name}');

_updateRoutes((state) {
final copy = List<WizardRouteSettings>.of(state);
return copy..replaceRange(1, routes.length, []);
});
}

/// Requests the wizard to show the previous page. Optionally, `result` can be
/// returned to the previous page.
///
/// ```dart
/// onPressed: Wizard.of(context).back
/// ```
void back<T extends Object?>([T? result]) {
final routes = _getRoutes();
assert(routes.length > 1,
'`Wizard.back()` called from the first route ${routes.last.name}');

// go back to a specific route, or pick the previous route on the list
final previous = widget._route.onBack?.call(routes.last);
if (previous != null) {
assert(widget._routes.contains(previous),
'`Wizard.routes` is missing route \'$previous\'.');
}

final start = previous != null
? routes.lastIndexWhere((settings) => settings.name == previous) + 1
: routes.length - 1;

_updateRoutes((state) {
final copy = List<WizardRouteSettings>.of(state);
copy[start].result.complete(result);
return copy..replaceRange(start, routes.length, []);
});
}

/// Requests the wizard to show the next page. Optionally, `arguments` can be
/// passed to the next page.
///
/// ```dart
/// onPressed: Wizard.of(context).next
/// ```
Future<T?> next<T extends Object?>({Object? arguments}) {
final routes = _getRoutes();
assert(routes.isNotEmpty, routes.length.toString());

final previous = routes.last.copyWith(arguments: arguments);

// advance to a specific route
String? onNext() => widget._route.onNext?.call(previous);

// pick the next route on the list
String nextRoute() {
final index = widget._routes.indexOf(previous.name!);
assert(index < widget._routes.length - 1,
'`Wizard.next()` called from the last route ${previous.name}.');
return widget._routes[index + 1];
}

final next = WizardRouteSettings<T?>(
name: onNext() ?? nextRoute(),
arguments: arguments,
);

assert(widget._routes.contains(next.name),
'`Wizard.routes` is missing route \'${next.name}\'.');

_updateRoutes((state) {
final copy = List<WizardRouteSettings>.of(state);
return copy..add(next);
});

return next.result.future;
}

List<WizardRouteSettings> _getRoutes() =>
context.flow<List<WizardRouteSettings>>().state;

void _updateRoutes(
List<WizardRouteSettings> Function(List<WizardRouteSettings>) callback,
) {
context.flow<List<WizardRouteSettings>>().update(callback);
}

/// Returns `false` if the wizard page is the first page.
bool get hasPrevious => widget._index > 0;

/// Returns `false` if the wizard page is the last page.
bool get hasNext => widget._index < widget._routes.length - 1;

@override
Widget build(BuildContext context) => Builder(builder: widget._route.builder);
}
12 changes: 12 additions & 0 deletions lib/src/settings.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import 'dart:async';

import 'package:flutter/widgets.dart';

class WizardRouteSettings<T extends Object?> extends RouteSettings {
WizardRouteSettings({
String? name,
Object? arguments,
}) : super(name: name, arguments: arguments);

final result = Completer<T?>();
}
182 changes: 8 additions & 174 deletions lib/src/wizard.dart
Original file line number Diff line number Diff line change
@@ -1,33 +1,10 @@
import 'dart:async';

import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:flow_builder/flow_builder.dart';

/// The signature of [WizardRoute.onNext] and [WizardRoute.onBack] callbacks.
typedef WizardRouteCallback = String? Function(RouteSettings settings);

class WizardRoute {
const WizardRoute({
required this.builder,
this.onNext,
this.onBack,
});

final WidgetBuilder builder;

/// The callback invoked when the next page is requested.
///
/// If `onNext` is not specified or it returns `null`, the order is determined
/// from [routes].
final WizardRouteCallback? onNext;

/// The callback invoked when the previous page is requested.
///
/// If `onBack` is not specified or it returns `null`, the order is determined
/// from [routes].
final WizardRouteCallback? onBack;
}
import 'route.dart';
import 'scope.dart';
import 'settings.dart';

/// A wizard is a flow of steps that the user can navigate through.
///
Expand Down Expand Up @@ -199,19 +176,19 @@ class Wizard extends StatefulWidget {
}

class _WizardState extends State<Wizard> {
late List<_WizardRouteSettings> _routes;
late List<WizardRouteSettings> _routes;

@override
void initState() {
super.initState();
_routes = <_WizardRouteSettings>[
_WizardRouteSettings(
_routes = <WizardRouteSettings>[
WizardRouteSettings(
name: widget.initialRoute ?? widget.routes.keys.first),
];
}

Page _createPage(BuildContext context,
{required int index, required _WizardRouteSettings settings}) {
{required int index, required WizardRouteSettings settings}) {
return MaterialPage(
name: settings.name,
arguments: settings.arguments,
Expand All @@ -226,7 +203,7 @@ class _WizardState extends State<Wizard> {

@override
Widget build(BuildContext context) {
return FlowBuilder<List<_WizardRouteSettings>>(
return FlowBuilder<List<WizardRouteSettings>>(
state: _routes,
onGeneratePages: (state, __) {
_routes = state;
Expand All @@ -239,146 +216,3 @@ class _WizardState extends State<Wizard> {
);
}
}

/// The scope of a wizard page.
///
/// Each page is enclosed by a `WizardScope` widget.
class WizardScope extends StatefulWidget {
const WizardScope({
Key? key,
required int index,
required WizardRoute route,
required List<String> routes,
}) : _index = index,
_route = route,
_routes = routes,
super(key: key);

final int _index;
final WizardRoute _route;
final List<String> _routes;

@override
State<WizardScope> createState() => WizardScopeState();
}

/// The state of a `WizardScope`, accessed via `Wizard.of(context)`.
class WizardScopeState extends State<WizardScope> {
/// Arguments passed from the previous page.
///
/// ```dart
/// final something = Wizard.of(context).arguments as Something;
/// ```
Object? get arguments => ModalRoute.of(context)?.settings.arguments;

/// Requests the wizard to show the first page.
///
/// ```dart
/// onPressed: Wizard.of(context).home
/// ```
void home() {
final routes = _getRoutes();
assert(routes.length > 1,
'`Wizard.home()` called from the first route ${routes.last.name}');

_updateRoutes((state) {
final copy = List<_WizardRouteSettings>.of(state);
return copy..replaceRange(1, routes.length, []);
});
}

/// Requests the wizard to show the previous page. Optionally, `result` can be
/// returned to the previous page.
///
/// ```dart
/// onPressed: Wizard.of(context).back
/// ```
void back<T extends Object?>([T? result]) {
final routes = _getRoutes();
assert(routes.length > 1,
'`Wizard.back()` called from the first route ${routes.last.name}');

// go back to a specific route, or pick the previous route on the list
final previous = widget._route.onBack?.call(routes.last);
if (previous != null) {
assert(widget._routes.contains(previous),
'`Wizard.routes` is missing route \'$previous\'.');
}

final start = previous != null
? routes.lastIndexWhere((settings) => settings.name == previous) + 1
: routes.length - 1;

_updateRoutes((state) {
final copy = List<_WizardRouteSettings>.of(state);
copy[start].result.complete(result);
return copy..replaceRange(start, routes.length, []);
});
}

/// Requests the wizard to show the next page. Optionally, `arguments` can be
/// passed to the next page.
///
/// ```dart
/// onPressed: Wizard.of(context).next
/// ```
Future<T?> next<T extends Object?>({Object? arguments}) {
final routes = _getRoutes();
assert(routes.isNotEmpty, routes.length.toString());

final previous = routes.last.copyWith(arguments: arguments);

// advance to a specific route
String? onNext() => widget._route.onNext?.call(previous);

// pick the next route on the list
String nextRoute() {
final index = widget._routes.indexOf(previous.name!);
assert(index < widget._routes.length - 1,
'`Wizard.next()` called from the last route ${previous.name}.');
return widget._routes[index + 1];
}

final next = _WizardRouteSettings<T?>(
name: onNext() ?? nextRoute(),
arguments: arguments,
);

assert(widget._routes.contains(next.name),
'`Wizard.routes` is missing route \'${next.name}\'.');

_updateRoutes((state) {
final copy = List<_WizardRouteSettings>.of(state);
return copy..add(next);
});

return next.result.future;
}

List<_WizardRouteSettings> _getRoutes() =>
context.flow<List<_WizardRouteSettings>>().state;

void _updateRoutes(
List<_WizardRouteSettings> Function(List<_WizardRouteSettings>) callback,
) {
context.flow<List<_WizardRouteSettings>>().update(callback);
}

/// Returns `false` if the wizard page is the first page.
bool get hasPrevious => widget._index > 0;

/// Returns `false` if the wizard page is the last page.
bool get hasNext => widget._index < widget._routes.length - 1;

@override
Widget build(BuildContext context) => Builder(builder: widget._route.builder);
}

class _WizardRouteSettings<T extends Object?> extends RouteSettings {
_WizardRouteSettings({
String? name,
Object? arguments,
}) : super(name: name, arguments: arguments);

final result = Completer<T?>();
}
Loading

0 comments on commit e6faaf0

Please sign in to comment.