mirror of
https://github.com/flutter/samples.git
synced 2025-11-11 07:18:15 +00:00
Add flutter_web samples (#75)
This commit is contained in:
committed by
Andrew Brogdon
parent
42f2dce01b
commit
3fe927cb29
207
web/vision_challenge/lib/game.dart
Normal file
207
web/vision_challenge/lib/game.dart
Normal file
@@ -0,0 +1,207 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter_web/material.dart';
|
||||
import 'package:vision_challenge/packages/flutter_redux.dart';
|
||||
import 'package:vision_challenge/packages/redux.dart';
|
||||
|
||||
setText(text, size, color) => Text(text,
|
||||
style: TextStyle(
|
||||
fontSize: size,
|
||||
color: color,
|
||||
fontWeight: FontWeight.bold,
|
||||
decoration: TextDecoration.none));
|
||||
|
||||
pad(double left, double top) => EdgeInsets.fromLTRB(left, top, 0, 0);
|
||||
|
||||
setBg(name) => BoxDecoration(
|
||||
image: DecorationImage(
|
||||
fit: BoxFit.cover,
|
||||
alignment: Alignment.topLeft,
|
||||
image: AssetImage(name)));
|
||||
|
||||
class Game extends StatelessWidget {
|
||||
final Store<AppState> store;
|
||||
Game(this.store);
|
||||
_grade(int score) => [10, 20, 30, 35, 40, 45, 99]
|
||||
.where((i) => i > score)
|
||||
.reduce(min)
|
||||
.toString();
|
||||
|
||||
_createBoard(double size, List<List<int>> blocks, int depth,
|
||||
MaterialColor color) =>
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: blocks
|
||||
.map((cols) => Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: cols
|
||||
.map((item) => Flexible(
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
if (item == 1) store.dispatch(Action.next);
|
||||
},
|
||||
child: Container(
|
||||
width: size,
|
||||
height: size,
|
||||
color: item > 0 ? color[depth] : color)),
|
||||
))
|
||||
.toList()))
|
||||
.toList());
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => StoreConnector<AppState, AppState>(
|
||||
// onInit: (state) => ShakeDetector.autoStart(
|
||||
// onPhoneShake: () => store.dispatch(Action.shake)),
|
||||
converter: (store) => store.state,
|
||||
builder: (context, state) {
|
||||
var w = MediaQuery.of(context).size.height / 16 * 9,
|
||||
size = w / (state.board.length + 1),
|
||||
depth = [1 + state.score ~/ 5, 4].reduce(min) * 100,
|
||||
colors = [
|
||||
Colors.blue,
|
||||
Colors.orange,
|
||||
Colors.pink,
|
||||
Colors.purple,
|
||||
Colors.cyan
|
||||
];
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: Color(0xFFBCE1F6),
|
||||
body: Center(
|
||||
child: SizedBox(
|
||||
height: MediaQuery.of(context).size.height,
|
||||
width: MediaQuery.of(context).size.height / 16 * 9,
|
||||
child: Container(
|
||||
decoration: setBg(state.page < 0 ? 'p0.jpg' : 'p1.jpg'),
|
||||
child: state.page >= 0
|
||||
? Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Container(
|
||||
height: w * 0.325,
|
||||
padding: pad(0, w * 0.145),
|
||||
child: setText(state.score.toString(),
|
||||
w * 0.2, Colors.white)),
|
||||
Container(
|
||||
height: w * 0.35,
|
||||
padding: pad(w * 0.69, state.page * 7.0),
|
||||
child: state.page < 1
|
||||
? Timer(
|
||||
onEnd: () =>
|
||||
store.dispatch(Action.end),
|
||||
width: w)
|
||||
: setText('End', w * 0.08, Colors.red)),
|
||||
state.page < 1
|
||||
? Container(
|
||||
width: w,
|
||||
height: w * 1.05,
|
||||
padding: pad(0, w * 0.05),
|
||||
child: _createBoard(
|
||||
size,
|
||||
state.board,
|
||||
depth,
|
||||
colors[
|
||||
state.count % colors.length]))
|
||||
: Container(
|
||||
width: w,
|
||||
height: w,
|
||||
decoration:
|
||||
setBg(_grade(state.score) + '.png'))
|
||||
])
|
||||
: Container()),
|
||||
),
|
||||
),
|
||||
floatingActionButton: state.page != 0
|
||||
? Container(
|
||||
// width: w * 0.2,
|
||||
// height: w * 0.2,
|
||||
child: FloatingActionButton(
|
||||
child: Icon(
|
||||
state.page < 1 ? Icons.play_arrow : Icons.refresh),
|
||||
onPressed: () => store.dispatch(Action.start)))
|
||||
: Container());
|
||||
});
|
||||
}
|
||||
|
||||
class Timer extends StatefulWidget {
|
||||
Timer({this.onEnd, this.width});
|
||||
final VoidCallback onEnd;
|
||||
final double width;
|
||||
@override
|
||||
_TimerState createState() => _TimerState();
|
||||
}
|
||||
|
||||
class _TimerState extends State<Timer> with TickerProviderStateMixin {
|
||||
Animation _animate;
|
||||
int _sec = 31;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_animate = StepTween(begin: _sec, end: 0).animate(
|
||||
AnimationController(duration: Duration(seconds: _sec), vsync: this)
|
||||
..forward(from: 0.0))
|
||||
..addStatusListener((AnimationStatus s) {
|
||||
if (s == AnimationStatus.completed) widget.onEnd();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => AnimatedBuilder(
|
||||
animation: _animate,
|
||||
builder: (BuildContext context, Widget child) => setText(
|
||||
_animate.value.toString().padLeft(2, '0'),
|
||||
widget.width * 0.12,
|
||||
Colors.green));
|
||||
}
|
||||
|
||||
//REDUX
|
||||
@immutable
|
||||
class AppState {
|
||||
final int score, page, count;
|
||||
final List<List<int>> board;
|
||||
AppState({this.score, this.page, this.board, this.count});
|
||||
AppState.init()
|
||||
: score = 0,
|
||||
page = -1,
|
||||
count = 0,
|
||||
board = newBoard(0);
|
||||
}
|
||||
|
||||
enum Action { next, end, start, shake }
|
||||
|
||||
AppState reducer(AppState s, act) {
|
||||
switch (act) {
|
||||
case Action.next:
|
||||
return AppState(
|
||||
score: s.score + 1,
|
||||
page: s.page,
|
||||
count: s.count,
|
||||
board: newBoard(s.score + 1));
|
||||
case Action.end:
|
||||
return AppState(
|
||||
score: s.score, page: 1, count: s.count + 1, board: s.board);
|
||||
case Action.start:
|
||||
return AppState(score: 0, page: 0, count: s.count, board: newBoard(0));
|
||||
case Action.shake:
|
||||
return AppState(
|
||||
score: s.score, page: s.page, count: s.count + 1, board: s.board);
|
||||
default:
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
List<List<int>> newBoard(score) {
|
||||
var size = score < 7 ? score + 3 : 10,
|
||||
rng = Random(),
|
||||
bingoRow = rng.nextInt(size),
|
||||
bingoCol = rng.nextInt(size);
|
||||
List<List<int>> board = [];
|
||||
for (var i = 0; i < size; i++) {
|
||||
List<int> row = [];
|
||||
for (var j = 0; j < size; j++)
|
||||
row.add(i == bingoRow && j == bingoCol ? 1 : 0);
|
||||
board.add(row);
|
||||
}
|
||||
return board;
|
||||
}
|
||||
20
web/vision_challenge/lib/main.dart
Normal file
20
web/vision_challenge/lib/main.dart
Normal file
@@ -0,0 +1,20 @@
|
||||
import 'package:flutter_web/material.dart';
|
||||
import 'package:vision_challenge/packages/flutter_redux.dart';
|
||||
import 'package:vision_challenge/packages/redux.dart';
|
||||
import 'game.dart';
|
||||
|
||||
void main() {
|
||||
final store = Store<AppState>(
|
||||
reducer,
|
||||
initialState: AppState.init(),
|
||||
);
|
||||
|
||||
runApp(
|
||||
StoreProvider<AppState>(
|
||||
store: store,
|
||||
child: MaterialApp(
|
||||
home: Game(store),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
513
web/vision_challenge/lib/packages/flutter_redux.dart
Normal file
513
web/vision_challenge/lib/packages/flutter_redux.dart
Normal file
@@ -0,0 +1,513 @@
|
||||
// Package flutter_redux:
|
||||
// https://pub.dev/packages/flutter_redux
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter_web/widgets.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'redux.dart';
|
||||
|
||||
/// Provides a Redux [Store] to all descendants of this Widget. This should
|
||||
/// generally be a root widget in your App. Connect to the Store provided
|
||||
/// by this Widget using a [StoreConnector] or [StoreBuilder].
|
||||
class StoreProvider<S> extends InheritedWidget {
|
||||
final Store<S> _store;
|
||||
|
||||
/// Create a [StoreProvider] by passing in the required [store] and [child]
|
||||
/// parameters.
|
||||
const StoreProvider({
|
||||
Key key,
|
||||
@required Store<S> store,
|
||||
@required Widget child,
|
||||
}) : assert(store != null),
|
||||
assert(child != null),
|
||||
_store = store,
|
||||
super(key: key, child: child);
|
||||
|
||||
/// A method that can be called by descendant Widgets to retrieve the Store
|
||||
/// from the StoreProvider.
|
||||
///
|
||||
/// Important: When using this method, pass through complete type information
|
||||
/// or Flutter will be unable to find the correct StoreProvider!
|
||||
///
|
||||
/// ### Example
|
||||
///
|
||||
/// ```
|
||||
/// class MyWidget extends StatelessWidget {
|
||||
/// @override
|
||||
/// Widget build(BuildContext context) {
|
||||
/// final store = StoreProvider.of<int>(context);
|
||||
///
|
||||
/// return Text('${store.state}');
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
static Store<S> of<S>(BuildContext context) {
|
||||
final type = _typeOf<StoreProvider<S>>();
|
||||
final provider =
|
||||
context.inheritFromWidgetOfExactType(type) as StoreProvider<S>;
|
||||
|
||||
if (provider == null) throw StoreProviderError(type);
|
||||
|
||||
return provider._store;
|
||||
}
|
||||
|
||||
// Workaround to capture generics
|
||||
static Type _typeOf<T>() => T;
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(StoreProvider<S> oldWidget) =>
|
||||
_store != oldWidget._store;
|
||||
}
|
||||
|
||||
/// Build a Widget using the [BuildContext] and [ViewModel]. The [ViewModel] is
|
||||
/// derived from the [Store] using a [StoreConverter].
|
||||
typedef ViewModelBuilder<ViewModel> = Widget Function(
|
||||
BuildContext context,
|
||||
ViewModel vm,
|
||||
);
|
||||
|
||||
/// Convert the entire [Store] into a [ViewModel]. The [ViewModel] will be used
|
||||
/// to build a Widget using the [ViewModelBuilder].
|
||||
typedef StoreConverter<S, ViewModel> = ViewModel Function(
|
||||
Store<S> store,
|
||||
);
|
||||
|
||||
/// A function that will be run when the [StoreConnector] is initialized (using
|
||||
/// the [State.initState] method). This can be useful for dispatching actions
|
||||
/// that fetch data for your Widget when it is first displayed.
|
||||
typedef OnInitCallback<S> = void Function(
|
||||
Store<S> store,
|
||||
);
|
||||
|
||||
/// A function that will be run when the StoreConnector is removed from the
|
||||
/// Widget Tree.
|
||||
///
|
||||
/// It is run in the [State.dispose] method.
|
||||
///
|
||||
/// This can be useful for dispatching actions that remove stale data from
|
||||
/// your State tree.
|
||||
typedef OnDisposeCallback<S> = void Function(
|
||||
Store<S> store,
|
||||
);
|
||||
|
||||
/// A test of whether or not your `converter` function should run in response
|
||||
/// to a State change. For advanced use only.
|
||||
///
|
||||
/// Some changes to the State of your application will mean your `converter`
|
||||
/// function can't produce a useful ViewModel. In these cases, such as when
|
||||
/// performing exit animations on data that has been removed from your Store,
|
||||
/// it can be best to ignore the State change while your animation completes.
|
||||
///
|
||||
/// To ignore a change, provide a function that returns true or false. If the
|
||||
/// returned value is true, the change will be ignored.
|
||||
///
|
||||
/// If you ignore a change, and the framework needs to rebuild the Widget, the
|
||||
/// `builder` function will be called with the latest `ViewModel` produced by
|
||||
/// your `converter` function.
|
||||
typedef IgnoreChangeTest<S> = bool Function(S state);
|
||||
|
||||
/// A function that will be run on State change, before the build method.
|
||||
///
|
||||
/// This function is passed the `ViewModel`, and if `distinct` is `true`,
|
||||
/// it will only be called if the `ViewModel` changes.
|
||||
///
|
||||
/// This can be useful for imperative calls to things like Navigator,
|
||||
/// TabController, etc
|
||||
typedef OnWillChangeCallback<ViewModel> = void Function(ViewModel viewModel);
|
||||
|
||||
/// A function that will be run on State change, after the build method.
|
||||
///
|
||||
/// This function is passed the `ViewModel`, and if `distinct` is `true`,
|
||||
/// it will only be called if the `ViewModel` changes.
|
||||
///
|
||||
/// This can be useful for running certain animations after the build is
|
||||
/// complete.
|
||||
///
|
||||
/// Note: Using a [BuildContext] inside this callback can cause problems if
|
||||
/// the callback performs navigation. For navigation purposes, please use
|
||||
/// an [OnWillChangeCallback].
|
||||
typedef OnDidChangeCallback<ViewModel> = void Function(ViewModel viewModel);
|
||||
|
||||
/// A function that will be run after the Widget is built the first time.
|
||||
///
|
||||
/// This function is passed the initial `ViewModel` created by the `converter`
|
||||
/// function.
|
||||
///
|
||||
/// This can be useful for starting certain animations, such as showing
|
||||
/// Snackbars, after the Widget is built the first time.
|
||||
typedef OnInitialBuildCallback<ViewModel> = void Function(ViewModel viewModel);
|
||||
|
||||
/// Build a widget based on the state of the [Store].
|
||||
///
|
||||
/// Before the [builder] is run, the [converter] will convert the store into a
|
||||
/// more specific `ViewModel` tailored to the Widget being built.
|
||||
///
|
||||
/// Every time the store changes, the Widget will be rebuilt. As a performance
|
||||
/// optimization, the Widget can be rebuilt only when the [ViewModel] changes.
|
||||
/// In order for this to work correctly, you must implement [==] and [hashCode]
|
||||
/// for the [ViewModel], and set the [distinct] option to true when creating
|
||||
/// your StoreConnector.
|
||||
class StoreConnector<S, ViewModel> extends StatelessWidget {
|
||||
/// Build a Widget using the [BuildContext] and [ViewModel]. The [ViewModel]
|
||||
/// is created by the [converter] function.
|
||||
final ViewModelBuilder<ViewModel> builder;
|
||||
|
||||
/// Convert the [Store] into a [ViewModel]. The resulting [ViewModel] will be
|
||||
/// passed to the [builder] function.
|
||||
final StoreConverter<S, ViewModel> converter;
|
||||
|
||||
/// As a performance optimization, the Widget can be rebuilt only when the
|
||||
/// [ViewModel] changes. In order for this to work correctly, you must
|
||||
/// implement [==] and [hashCode] for the [ViewModel], and set the [distinct]
|
||||
/// option to true when creating your StoreConnector.
|
||||
final bool distinct;
|
||||
|
||||
/// A function that will be run when the StoreConnector is initially created.
|
||||
/// It is run in the [State.initState] method.
|
||||
///
|
||||
/// This can be useful for dispatching actions that fetch data for your Widget
|
||||
/// when it is first displayed.
|
||||
final OnInitCallback<S> onInit;
|
||||
|
||||
/// A function that will be run when the StoreConnector is removed from the
|
||||
/// Widget Tree.
|
||||
///
|
||||
/// It is run in the [State.dispose] method.
|
||||
///
|
||||
/// This can be useful for dispatching actions that remove stale data from
|
||||
/// your State tree.
|
||||
final OnDisposeCallback<S> onDispose;
|
||||
|
||||
/// Determines whether the Widget should be rebuilt when the Store emits an
|
||||
/// onChange event.
|
||||
final bool rebuildOnChange;
|
||||
|
||||
/// A test of whether or not your [converter] function should run in response
|
||||
/// to a State change. For advanced use only.
|
||||
///
|
||||
/// Some changes to the State of your application will mean your [converter]
|
||||
/// function can't produce a useful ViewModel. In these cases, such as when
|
||||
/// performing exit animations on data that has been removed from your Store,
|
||||
/// it can be best to ignore the State change while your animation completes.
|
||||
///
|
||||
/// To ignore a change, provide a function that returns true or false. If the
|
||||
/// returned value is true, the change will be ignored.
|
||||
///
|
||||
/// If you ignore a change, and the framework needs to rebuild the Widget, the
|
||||
/// [builder] function will be called with the latest [ViewModel] produced by
|
||||
/// your [converter] function.
|
||||
final IgnoreChangeTest<S> ignoreChange;
|
||||
|
||||
/// A function that will be run on State change, before the Widget is built.
|
||||
///
|
||||
/// This function is passed the `ViewModel`, and if `distinct` is `true`,
|
||||
/// it will only be called if the `ViewModel` changes.
|
||||
///
|
||||
/// This can be useful for imperative calls to things like Navigator,
|
||||
/// TabController, etc
|
||||
final OnWillChangeCallback<ViewModel> onWillChange;
|
||||
|
||||
/// A function that will be run on State change, after the Widget is built.
|
||||
///
|
||||
/// This function is passed the `ViewModel`, and if `distinct` is `true`,
|
||||
/// it will only be called if the `ViewModel` changes.
|
||||
///
|
||||
/// This can be useful for running certain animations after the build is
|
||||
/// complete.
|
||||
///
|
||||
/// Note: Using a [BuildContext] inside this callback can cause problems if
|
||||
/// the callback performs navigation. For navigation purposes, please use
|
||||
/// [onWillChange].
|
||||
final OnDidChangeCallback<ViewModel> onDidChange;
|
||||
|
||||
/// A function that will be run after the Widget is built the first time.
|
||||
///
|
||||
/// This function is passed the initial `ViewModel` created by the [converter]
|
||||
/// function.
|
||||
///
|
||||
/// This can be useful for starting certain animations, such as showing
|
||||
/// Snackbars, after the Widget is built the first time.
|
||||
final OnInitialBuildCallback<ViewModel> onInitialBuild;
|
||||
|
||||
/// Create a [StoreConnector] by passing in the required [converter] and
|
||||
/// [builder] functions.
|
||||
///
|
||||
/// You can also specify a number of additional parameters that allow you to
|
||||
/// modify the behavior of the StoreConnector. Please see the documentation
|
||||
/// for each option for more info.
|
||||
StoreConnector({
|
||||
Key key,
|
||||
@required this.builder,
|
||||
@required this.converter,
|
||||
this.distinct = false,
|
||||
this.onInit,
|
||||
this.onDispose,
|
||||
this.rebuildOnChange = true,
|
||||
this.ignoreChange,
|
||||
this.onWillChange,
|
||||
this.onDidChange,
|
||||
this.onInitialBuild,
|
||||
}) : assert(builder != null),
|
||||
assert(converter != null),
|
||||
super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _StoreStreamListener<S, ViewModel>(
|
||||
store: StoreProvider.of<S>(context),
|
||||
builder: builder,
|
||||
converter: converter,
|
||||
distinct: distinct,
|
||||
onInit: onInit,
|
||||
onDispose: onDispose,
|
||||
rebuildOnChange: rebuildOnChange,
|
||||
ignoreChange: ignoreChange,
|
||||
onWillChange: onWillChange,
|
||||
onDidChange: onDidChange,
|
||||
onInitialBuild: onInitialBuild,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Build a Widget by passing the [Store] directly to the build function.
|
||||
///
|
||||
/// Generally, it's considered best practice to use the [StoreConnector] and to
|
||||
/// build a `ViewModel` specifically for your Widget rather than passing through
|
||||
/// the entire [Store], but this is provided for convenience when that isn't
|
||||
/// necessary.
|
||||
class StoreBuilder<S> extends StatelessWidget {
|
||||
static Store<S> _identity<S>(Store<S> store) => store;
|
||||
|
||||
/// Builds a Widget using the [BuildContext] and your [Store].
|
||||
final ViewModelBuilder<Store<S>> builder;
|
||||
|
||||
/// Indicates whether or not the Widget should rebuild when the [Store] emits
|
||||
/// an `onChange` event.
|
||||
final bool rebuildOnChange;
|
||||
|
||||
/// A function that will be run when the StoreConnector is initially created.
|
||||
/// It is run in the [State.initState] method.
|
||||
///
|
||||
/// This can be useful for dispatching actions that fetch data for your Widget
|
||||
/// when it is first displayed.
|
||||
final OnInitCallback<S> onInit;
|
||||
|
||||
/// A function that will be run when the StoreBuilder is removed from the
|
||||
/// Widget Tree.
|
||||
///
|
||||
/// It is run in the [State.dispose] method.
|
||||
///
|
||||
/// This can be useful for dispatching actions that remove stale data from
|
||||
/// your State tree.
|
||||
final OnDisposeCallback<S> onDispose;
|
||||
|
||||
/// A function that will be run on State change, before the Widget is built.
|
||||
///
|
||||
/// This can be useful for imperative calls to things like Navigator,
|
||||
/// TabController, etc
|
||||
final OnWillChangeCallback<Store<S>> onWillChange;
|
||||
|
||||
/// A function that will be run on State change, after the Widget is built.
|
||||
///
|
||||
/// This can be useful for running certain animations after the build is
|
||||
/// complete
|
||||
///
|
||||
/// Note: Using a [BuildContext] inside this callback can cause problems if
|
||||
/// the callback performs navigation. For navigation purposes, please use
|
||||
/// [onWillChange].
|
||||
final OnDidChangeCallback<Store<S>> onDidChange;
|
||||
|
||||
/// A function that will be run after the Widget is built the first time.
|
||||
///
|
||||
/// This can be useful for starting certain animations, such as showing
|
||||
/// Snackbars, after the Widget is built the first time.
|
||||
final OnInitialBuildCallback<Store<S>> onInitialBuild;
|
||||
|
||||
/// Create's a Widget based on the Store.
|
||||
StoreBuilder({
|
||||
Key key,
|
||||
@required this.builder,
|
||||
this.onInit,
|
||||
this.onDispose,
|
||||
this.rebuildOnChange = true,
|
||||
this.onWillChange,
|
||||
this.onDidChange,
|
||||
this.onInitialBuild,
|
||||
}) : assert(builder != null),
|
||||
super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return StoreConnector<S, Store<S>>(
|
||||
builder: builder,
|
||||
converter: _identity,
|
||||
rebuildOnChange: rebuildOnChange,
|
||||
onInit: onInit,
|
||||
onDispose: onDispose,
|
||||
onWillChange: onWillChange,
|
||||
onDidChange: onDidChange,
|
||||
onInitialBuild: onInitialBuild,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Listens to the [Store] and calls [builder] whenever [store] changes.
|
||||
class _StoreStreamListener<S, ViewModel> extends StatefulWidget {
|
||||
final ViewModelBuilder<ViewModel> builder;
|
||||
final StoreConverter<S, ViewModel> converter;
|
||||
final Store<S> store;
|
||||
final bool rebuildOnChange;
|
||||
final bool distinct;
|
||||
final OnInitCallback<S> onInit;
|
||||
final OnDisposeCallback<S> onDispose;
|
||||
final IgnoreChangeTest<S> ignoreChange;
|
||||
final OnWillChangeCallback<ViewModel> onWillChange;
|
||||
final OnDidChangeCallback<ViewModel> onDidChange;
|
||||
final OnInitialBuildCallback<ViewModel> onInitialBuild;
|
||||
|
||||
_StoreStreamListener({
|
||||
Key key,
|
||||
@required this.builder,
|
||||
@required this.store,
|
||||
@required this.converter,
|
||||
this.distinct = false,
|
||||
this.onInit,
|
||||
this.onDispose,
|
||||
this.rebuildOnChange = true,
|
||||
this.ignoreChange,
|
||||
this.onWillChange,
|
||||
this.onDidChange,
|
||||
this.onInitialBuild,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return _StoreStreamListenerState<S, ViewModel>();
|
||||
}
|
||||
}
|
||||
|
||||
class _StoreStreamListenerState<S, ViewModel>
|
||||
extends State<_StoreStreamListener<S, ViewModel>> {
|
||||
Stream<ViewModel> stream;
|
||||
ViewModel latestValue;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_init();
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
if (widget.onDispose != null) {
|
||||
widget.onDispose(widget.store);
|
||||
}
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(_StoreStreamListener<S, ViewModel> oldWidget) {
|
||||
if (widget.store != oldWidget.store) {
|
||||
_init();
|
||||
}
|
||||
|
||||
super.didUpdateWidget(oldWidget);
|
||||
}
|
||||
|
||||
void _init() {
|
||||
if (widget.onInit != null) {
|
||||
widget.onInit(widget.store);
|
||||
}
|
||||
|
||||
latestValue = widget.converter(widget.store);
|
||||
|
||||
if (widget.onInitialBuild != null) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
widget.onInitialBuild(latestValue);
|
||||
});
|
||||
}
|
||||
|
||||
var _stream = widget.store.onChange;
|
||||
|
||||
if (widget.ignoreChange != null) {
|
||||
_stream = _stream.where((state) => !widget.ignoreChange(state));
|
||||
}
|
||||
|
||||
stream = _stream.map((_) => widget.converter(widget.store));
|
||||
|
||||
// Don't use `Stream.distinct` because it cannot capture the initial
|
||||
// ViewModel produced by the `converter`.
|
||||
if (widget.distinct) {
|
||||
stream = stream.where((vm) {
|
||||
final isDistinct = vm != latestValue;
|
||||
|
||||
return isDistinct;
|
||||
});
|
||||
}
|
||||
|
||||
// After each ViewModel is emitted from the Stream, we update the
|
||||
// latestValue. Important: This must be done after all other optional
|
||||
// transformations, such as ignoreChange.
|
||||
stream =
|
||||
stream.transform(StreamTransformer.fromHandlers(handleData: (vm, sink) {
|
||||
latestValue = vm;
|
||||
|
||||
if (widget.onWillChange != null) {
|
||||
widget.onWillChange(latestValue);
|
||||
}
|
||||
|
||||
if (widget.onDidChange != null) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
widget.onDidChange(latestValue);
|
||||
});
|
||||
}
|
||||
|
||||
sink.add(vm);
|
||||
}));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return widget.rebuildOnChange
|
||||
? StreamBuilder<ViewModel>(
|
||||
stream: stream,
|
||||
builder: (context, snapshot) => widget.builder(
|
||||
context,
|
||||
snapshot.hasData ? snapshot.data : latestValue,
|
||||
),
|
||||
)
|
||||
: widget.builder(context, latestValue);
|
||||
}
|
||||
}
|
||||
|
||||
/// If the StoreProvider.of method fails, this error will be thrown.
|
||||
///
|
||||
/// Often, when the `of` method fails, it is difficult to understand why since
|
||||
/// there can be multiple causes. This error explains those causes so the user
|
||||
/// can understand and fix the issue.
|
||||
class StoreProviderError extends Error {
|
||||
/// The type of the class the user tried to retrieve
|
||||
Type type;
|
||||
|
||||
/// Creates a StoreProviderError
|
||||
StoreProviderError(this.type);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '''Error: No $type found. To fix, please try:
|
||||
|
||||
* Wrapping your MaterialApp with the StoreProvider<State>,
|
||||
rather than an individual Route
|
||||
* Providing full type information to your Store<State>,
|
||||
StoreProvider<State> and StoreConnector<State, ViewModel>
|
||||
* Ensure you are using consistent and complete imports.
|
||||
E.g. always use `import 'package:my_app/app_state.dart';
|
||||
|
||||
If none of these solutions work, please file a bug at:
|
||||
https://github.com/brianegan/flutter_redux/issues/new
|
||||
''';
|
||||
}
|
||||
}
|
||||
519
web/vision_challenge/lib/packages/redux.dart
Normal file
519
web/vision_challenge/lib/packages/redux.dart
Normal file
@@ -0,0 +1,519 @@
|
||||
// Package redux:
|
||||
// https://pub.dev/packages/redux
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
/// Defines an application's state change
|
||||
///
|
||||
/// Implement this typedef to modify your app state in response to a given
|
||||
/// action.
|
||||
///
|
||||
/// ### Example
|
||||
///
|
||||
/// int counterReducer(int state, action) {
|
||||
/// switch (action) {
|
||||
/// case 'INCREMENT':
|
||||
/// return state + 1;
|
||||
/// case 'DECREMENT':
|
||||
/// return state - 1;
|
||||
/// default:
|
||||
/// return state;
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// final store = new Store<int>(counterReducer);
|
||||
typedef Reducer<State> = State Function(State state, dynamic action);
|
||||
|
||||
/// Defines a [Reducer] using a class interface.
|
||||
///
|
||||
/// Implement this class to modify your app state in response to a given action.
|
||||
///
|
||||
/// For some use cases, a class may be preferred to a function. In these
|
||||
/// instances, a ReducerClass can be used.
|
||||
///
|
||||
/// ### Example
|
||||
///
|
||||
/// class CounterReducer extends ReducerClass<int> {
|
||||
/// int call(int state, action) {
|
||||
/// switch (action) {
|
||||
/// case 'INCREMENT':
|
||||
/// return state + 1;
|
||||
/// case 'DECREMENT':
|
||||
/// return state - 1;
|
||||
/// default:
|
||||
/// return state;
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// final store = new Store<int>(new CounterReducer());
|
||||
abstract class ReducerClass<State> {
|
||||
State call(State state, dynamic action);
|
||||
}
|
||||
|
||||
/// A function that intercepts actions and potentially transform actions before
|
||||
/// they reach the reducer.
|
||||
///
|
||||
/// Middleware intercept actions before they reach the reducer. This gives them
|
||||
/// the ability to produce side-effects or modify the passed in action before
|
||||
/// they reach the reducer.
|
||||
///
|
||||
/// ### Example
|
||||
///
|
||||
/// loggingMiddleware(Store<int> store, action, NextDispatcher next) {
|
||||
/// print('${new DateTime.now()}: $action');
|
||||
///
|
||||
/// next(action);
|
||||
/// }
|
||||
///
|
||||
/// // Create your store with the loggingMiddleware
|
||||
/// final store = new Store<int>(
|
||||
/// counterReducer,
|
||||
/// middleware: [loggingMiddleware],
|
||||
/// );
|
||||
typedef Middleware<State> = void Function(
|
||||
Store<State> store,
|
||||
dynamic action,
|
||||
NextDispatcher next,
|
||||
);
|
||||
|
||||
/// Defines a [Middleware] using a Class interface.
|
||||
///
|
||||
/// Middleware intercept actions before they reach the reducer. This gives them
|
||||
/// the ability to produce side-effects or modify the passed in action before
|
||||
/// they reach the reducer.
|
||||
///
|
||||
/// For some use cases, a class may be preferred to a function. In these
|
||||
/// instances, a MiddlewareClass can be used.
|
||||
///
|
||||
/// ### Example
|
||||
/// class LoggingMiddleware extends MiddlewareClass<int> {
|
||||
/// call(Store<int> store, action, NextDispatcher next) {
|
||||
/// print('${new DateTime.now()}: $action');
|
||||
///
|
||||
/// next(action);
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// // Create your store with the loggingMiddleware
|
||||
/// final store = new Store<int>(
|
||||
/// counterReducer,
|
||||
/// middleware: [new LoggingMiddleware()],
|
||||
/// );
|
||||
abstract class MiddlewareClass<State> {
|
||||
void call(Store<State> store, dynamic action, NextDispatcher next);
|
||||
}
|
||||
|
||||
/// The contract between one piece of middleware and the next in the chain. Use
|
||||
/// it to send the current action in your [Middleware] to the next piece of
|
||||
/// [Middleware] in the chain.
|
||||
///
|
||||
/// Middleware can optionally pass the original action or a modified action to
|
||||
/// the next piece of middleware, or never call the next piece of middleware at
|
||||
/// all.
|
||||
typedef NextDispatcher = void Function(dynamic action);
|
||||
|
||||
/// Creates a Redux store that holds the app state tree.
|
||||
///
|
||||
/// The only way to change the state tree in the store is to [dispatch] an
|
||||
/// action. the action will then be intercepted by any provided [Middleware].
|
||||
/// After running through the middleware, the action will be sent to the given
|
||||
/// [Reducer] to update the state tree.
|
||||
///
|
||||
/// To access the state tree, call the [state] getter or listen to the
|
||||
/// [onChange] stream.
|
||||
///
|
||||
/// ### Basic Example
|
||||
///
|
||||
/// // Create a reducer
|
||||
/// final increment = 'INCREMENT';
|
||||
/// final decrement = 'DECREMENT';
|
||||
///
|
||||
/// int counterReducer(int state, action) {
|
||||
/// switch (action) {
|
||||
/// case increment:
|
||||
/// return state + 1;
|
||||
/// case decrement:
|
||||
/// return state - 1;
|
||||
/// default:
|
||||
/// return state;
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// // Create the store
|
||||
/// final store = new Store<int>(counterReducer, initialState: 0);
|
||||
///
|
||||
/// // Print the Store's state.
|
||||
/// print(store.state); // prints "0"
|
||||
///
|
||||
/// // Dispatch an action. This will be sent to the reducer to update the
|
||||
/// // state.
|
||||
/// store.dispatch(increment);
|
||||
///
|
||||
/// // Print the updated state. As an alternative, you can use the
|
||||
/// // `store.onChange.listen` to respond to all state change events.
|
||||
/// print(store.state); // prints "1"
|
||||
class Store<State> {
|
||||
/// The [Reducer] for your Store. Allows you to get the current reducer or
|
||||
/// replace it with a new one if need be.
|
||||
Reducer<State> reducer;
|
||||
|
||||
final StreamController<State> _changeController;
|
||||
State _state;
|
||||
List<NextDispatcher> _dispatchers;
|
||||
|
||||
Store(
|
||||
this.reducer, {
|
||||
State initialState,
|
||||
List<Middleware<State>> middleware = const [],
|
||||
bool syncStream = false,
|
||||
|
||||
/// If set to true, the Store will not emit onChange events if the new State
|
||||
/// that is returned from your [reducer] in response to an Action is equal
|
||||
/// to the previous state.
|
||||
///
|
||||
/// Under the hood, it will use the `==` method from your State class to
|
||||
/// determine whether or not the two States are equal.
|
||||
bool distinct = false,
|
||||
}) : _changeController = StreamController.broadcast(sync: syncStream) {
|
||||
_state = initialState;
|
||||
_dispatchers = _createDispatchers(
|
||||
middleware,
|
||||
_createReduceAndNotify(distinct),
|
||||
);
|
||||
}
|
||||
|
||||
/// Returns the current state of the app
|
||||
State get state => _state;
|
||||
|
||||
/// A stream that emits the current state when it changes.
|
||||
///
|
||||
/// ### Example
|
||||
///
|
||||
/// // First, create the Store
|
||||
/// final store = new Store<int>(counterReducer, 0);
|
||||
///
|
||||
/// // Next, listen to the Store's onChange stream, and print the latest
|
||||
/// // state to your console whenever the reducer produces a new State.
|
||||
/// //
|
||||
/// // We'll store the StreamSubscription as a variable so we can stop
|
||||
/// // listening later.
|
||||
/// final subscription = store.onChange.listen(print);
|
||||
///
|
||||
/// // Dispatch some actions, and see the printing magic!
|
||||
/// store.dispatch("INCREMENT"); // prints 1
|
||||
/// store.dispatch("INCREMENT"); // prints 2
|
||||
/// store.dispatch("DECREMENT"); // prints 1
|
||||
///
|
||||
/// // When you want to stop printing the state to the console, simply
|
||||
/// `cancel` your `subscription`.
|
||||
/// subscription.cancel();
|
||||
Stream<State> get onChange => _changeController.stream;
|
||||
|
||||
// Creates the base [NextDispatcher].
|
||||
//
|
||||
// The base NextDispatcher will be called after all other middleware provided
|
||||
// by the user have been run. Its job is simple: Run the current state through
|
||||
// the reducer, save the result, and notify any subscribers.
|
||||
NextDispatcher _createReduceAndNotify(bool distinct) {
|
||||
return (dynamic action) {
|
||||
final state = reducer(_state, action);
|
||||
|
||||
if (distinct && state == _state) return;
|
||||
|
||||
_state = state;
|
||||
_changeController.add(state);
|
||||
};
|
||||
}
|
||||
|
||||
List<NextDispatcher> _createDispatchers(
|
||||
List<Middleware<State>> middleware,
|
||||
NextDispatcher reduceAndNotify,
|
||||
) {
|
||||
final dispatchers = <NextDispatcher>[]..add(reduceAndNotify);
|
||||
|
||||
// Convert each [Middleware] into a [NextDispatcher]
|
||||
for (var nextMiddleware in middleware.reversed) {
|
||||
final next = dispatchers.last;
|
||||
|
||||
dispatchers.add(
|
||||
(dynamic action) => nextMiddleware(this, action, next),
|
||||
);
|
||||
}
|
||||
|
||||
return dispatchers.reversed.toList();
|
||||
}
|
||||
|
||||
/// Runs the action through all provided [Middleware], then applies an action
|
||||
/// to the state using the given [Reducer]. Please note: [Middleware] can
|
||||
/// intercept actions, and can modify actions or stop them from passing
|
||||
/// through to the reducer.
|
||||
void dispatch(dynamic action) {
|
||||
_dispatchers[0](action);
|
||||
}
|
||||
|
||||
/// Closes down the Store so it will no longer be operational. Only use this
|
||||
/// if you want to destroy the Store while your app is running. Do not use
|
||||
/// this method as a way to stop listening to [onChange] state changes. For
|
||||
/// that purpose, view the [onChange] documentation.
|
||||
Future teardown() async {
|
||||
_state = null;
|
||||
return _changeController.close();
|
||||
}
|
||||
}
|
||||
|
||||
/// A convenience class for binding Reducers to Actions of a given Type. This
|
||||
/// allows for type safe [Reducer]s and reduces boilerplate.
|
||||
///
|
||||
/// ### Example
|
||||
///
|
||||
/// In order to see what this utility function does, let's take a look at a
|
||||
/// regular example of using reducers based on the Type of an action.
|
||||
///
|
||||
/// ```
|
||||
/// // We define out State and Action classes.
|
||||
/// class AppState {
|
||||
/// final List<Item> items;
|
||||
///
|
||||
/// AppState(this.items);
|
||||
/// }
|
||||
///
|
||||
/// class LoadItemsAction {}
|
||||
/// class UpdateItemsAction {}
|
||||
/// class AddItemAction{}
|
||||
/// class RemoveItemAction {}
|
||||
/// class ShuffleItemsAction {}
|
||||
/// class ReverseItemsAction {}
|
||||
/// class ItemsLoadedAction<Item> {
|
||||
/// final List<Item> items;
|
||||
///
|
||||
/// ItemsLoadedAction(this.items);
|
||||
/// }
|
||||
///
|
||||
/// // Then we define our reducer. Since we handle different actions in our
|
||||
/// // reducer, we need to determine what kind of action we're working with
|
||||
/// // using if statements, and then run some computation in response.
|
||||
/// //
|
||||
/// // This isn't a big deal if we have relatively few cases to handle, but your
|
||||
/// // reducer function can quickly grow large and take on too many
|
||||
/// // responsibilities as demonstrated here with pseudo-code.
|
||||
/// final appReducer = (AppState state, action) {
|
||||
/// if (action is ItemsLoadedAction) {
|
||||
/// return new AppState(action.items);
|
||||
/// } else if (action is UpdateItemsAction) {
|
||||
/// return ...;
|
||||
/// } else if (action is AddItemAction) {
|
||||
/// return ...;
|
||||
/// } else if (action is RemoveItemAction) {
|
||||
/// return ...;
|
||||
/// } else if (action is ShuffleItemsAction) {
|
||||
/// return ...;
|
||||
/// } else if (action is ReverseItemsAction) {
|
||||
/// return ...;
|
||||
/// } else {
|
||||
/// return state;
|
||||
/// }
|
||||
/// };
|
||||
/// ```
|
||||
///
|
||||
/// What would be nice would be to break our big reducer up into smaller
|
||||
/// reducers. It would also be nice to bind specific Types of Actions to
|
||||
/// specific reducers so we can ensure type safety for our reducers while
|
||||
/// avoiding large trees of `if` statements.
|
||||
///
|
||||
/// ```
|
||||
/// // First, we'll break out all of our individual State Changes into
|
||||
/// // individual reducers. These can be easily tested or composed!
|
||||
/// final loadItemsReducer = (AppState state, LoadTodosAction action) =>
|
||||
/// return new AppState(action.items);
|
||||
///
|
||||
/// final updateItemsReducer = (AppState state, UpdateItemsAction action) {
|
||||
/// return ...;
|
||||
/// }
|
||||
///
|
||||
/// final addItemReducer = (AppState state, AddItemAction action) {
|
||||
/// return ...;
|
||||
/// }
|
||||
///
|
||||
/// final removeItemReducer = (AppState state, RemoveItemAction action) {
|
||||
/// return ...;
|
||||
/// }
|
||||
///
|
||||
/// final shuffleItemsReducer = (AppState state, ShuffleItemAction action) {
|
||||
/// return ...;
|
||||
/// }
|
||||
///
|
||||
/// final reverseItemsReducer = (AppState state, ReverseItemAction action) {
|
||||
/// return ...;
|
||||
/// }
|
||||
///
|
||||
/// // We will then wire up specific types of actions to our reducer functions
|
||||
/// // above. This will return a new Reducer<AppState> which puts everything
|
||||
/// // together!.
|
||||
/// final Reducer<AppState> appReducer = combineReducers([
|
||||
/// new TypedReducer<AppState, LoadTodosAction>(loadItemsReducer),
|
||||
/// new TypedReducer<AppState, UpdateItemsAction>(updateItemsReducer),
|
||||
/// new TypedReducer<AppState, AddItemAction>(addItemReducer),
|
||||
/// new TypedReducer<AppState, RemoveItemAction>(removeItemReducer),
|
||||
/// new TypedReducer<AppState, ShuffleItemAction>(shuffleItemsReducer),
|
||||
/// new TypedReducer<AppState, ReverseItemAction>(reverseItemsReducer),
|
||||
/// ]);
|
||||
/// ```
|
||||
class TypedReducer<State, Action> implements ReducerClass<State> {
|
||||
final State Function(State state, Action action) reducer;
|
||||
|
||||
TypedReducer(this.reducer);
|
||||
|
||||
@override
|
||||
State call(State state, dynamic action) {
|
||||
if (action is Action) {
|
||||
return reducer(state, action);
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
/// A convenience type for binding a piece of Middleware to an Action
|
||||
/// of a specific type. Allows for Type Safe Middleware and reduces boilerplate.
|
||||
///
|
||||
/// ### Example
|
||||
///
|
||||
/// In order to see what this utility function does, let's take a look at a
|
||||
/// regular example of running Middleware based on the Type of an action.
|
||||
///
|
||||
/// ```
|
||||
/// class AppState {
|
||||
/// final List<Item> items;
|
||||
///
|
||||
/// AppState(this.items);
|
||||
/// }
|
||||
/// class LoadItemsAction {}
|
||||
/// class UpdateItemsAction {}
|
||||
/// class AddItemAction{}
|
||||
/// class RemoveItemAction {}
|
||||
/// class ShuffleItemsAction {}
|
||||
/// class ReverseItemsAction {}
|
||||
/// class ItemsLoadedAction<Item> {
|
||||
/// final List<Item> items;
|
||||
///
|
||||
/// ItemsLoadedAction(this.items);
|
||||
/// }
|
||||
///
|
||||
/// final loadItems = () { /* Function that loads a Future<List<Item>> */}
|
||||
/// final saveItems = (List<Item> items) { /* Function that persists items */}
|
||||
///
|
||||
/// final middleware = (Store<AppState> store, action, NextDispatcher next) {
|
||||
/// if (action is LoadItemsAction) {
|
||||
/// loadItems()
|
||||
/// .then((items) => store.dispatch(new ItemsLoaded(items))
|
||||
/// .catchError((_) => store.dispatch(new ItemsNotLoaded());
|
||||
///
|
||||
/// next(action);
|
||||
/// } else if (action is UpdateItemsAction ||
|
||||
/// action is AddItemAction ||
|
||||
/// action is RemoveItemAction ||
|
||||
/// action is ShuffleItemsAction ||
|
||||
/// action is ReverseItemsAction) {
|
||||
/// next(action);
|
||||
///
|
||||
/// saveItems(store.state.items);
|
||||
/// } else {
|
||||
/// next(action);
|
||||
/// }
|
||||
/// };
|
||||
/// ```
|
||||
///
|
||||
/// This works fine if you have one or two actions to handle, but you might
|
||||
/// notice it's getting a bit messy already. Let's see how this lib helps clean
|
||||
/// it up.
|
||||
///
|
||||
/// ```
|
||||
/// // First, let's start by breaking up our functionality into two middleware
|
||||
/// // functions.
|
||||
/// //
|
||||
/// // The loadItemsMiddleware will only handle the `LoadItemsAction`s that
|
||||
/// // are dispatched, so we can annotate the Type of action.
|
||||
/// final loadItemsMiddleware = (
|
||||
/// Store<AppState> store,
|
||||
/// LoadItemsAction action,
|
||||
/// NextDispatcher next,
|
||||
/// ) {
|
||||
/// loadItems()
|
||||
/// .then((items) => store.dispatch(new ItemsLoaded(items))
|
||||
/// .catchError((_) => store.dispatch(new ItemsNotLoaded());
|
||||
///
|
||||
/// next(action);
|
||||
/// }
|
||||
///
|
||||
/// // The saveItemsMiddleware handles all actions that change the Items, but
|
||||
/// // does not depend on the payload of the action. Therefore, `action` will
|
||||
/// // remain dynamic.
|
||||
/// final saveItemsMiddleware = (
|
||||
/// Store<AppState> store,
|
||||
/// dynamic action,
|
||||
/// NextDispatcher next,
|
||||
/// ) {
|
||||
/// next(action);
|
||||
///
|
||||
/// saveItems(store.state.items);
|
||||
/// }
|
||||
///
|
||||
/// // We will then wire up specific types of actions to a List of Middleware
|
||||
/// // that handle those actions.
|
||||
/// final List<Middleware<AppState>> middleware = [
|
||||
/// new TypedMiddleware<AppState, LoadTodosAction>(loadItemsMiddleware),
|
||||
/// new TypedMiddleware<AppState, AddTodoAction>(saveItemsMiddleware),
|
||||
/// new TypedMiddleware<AppState, ClearCompletedAction>(saveItemsMiddleware),
|
||||
/// new TypedMiddleware<AppState, ToggleAllAction>(saveItemsMiddleware),
|
||||
/// new TypedMiddleware<AppState, UpdateTodoAction>(saveItemsMiddleware),
|
||||
/// new TypedMiddleware<AppState, TodosLoadedAction>(saveItemsMiddleware),
|
||||
/// ];
|
||||
/// ```
|
||||
class TypedMiddleware<State, Action> implements MiddlewareClass<State> {
|
||||
final void Function(
|
||||
Store<State> store,
|
||||
Action action,
|
||||
NextDispatcher next,
|
||||
) middleware;
|
||||
|
||||
TypedMiddleware(this.middleware);
|
||||
|
||||
@override
|
||||
void call(Store<State> store, dynamic action, NextDispatcher next) {
|
||||
if (action is Action) {
|
||||
middleware(store, action, next);
|
||||
} else {
|
||||
next(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines a utility function that combines several reducers.
|
||||
///
|
||||
/// In order to prevent having one large, monolithic reducer in your app, it can
|
||||
/// be convenient to break reducers up into smaller parts that handle more
|
||||
/// specific functionality that can be decoupled and easily tested.
|
||||
///
|
||||
/// ### Example
|
||||
///
|
||||
/// helloReducer(state, action) {
|
||||
/// return "hello";
|
||||
/// }
|
||||
///
|
||||
/// friendReducer(state, action) {
|
||||
/// return state + " friend";
|
||||
/// }
|
||||
///
|
||||
/// final helloFriendReducer = combineReducers(
|
||||
/// helloReducer,
|
||||
/// friendReducer,
|
||||
/// );
|
||||
Reducer<State> combineReducers<State>(Iterable<Reducer<State>> reducers) {
|
||||
return (State state, dynamic action) {
|
||||
for (final reducer in reducers) {
|
||||
state = reducer(state, action);
|
||||
}
|
||||
return state;
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user