From 2cfbef2431eee8aceeb03187f70ecfb56138c211 Mon Sep 17 00:00:00 2001 From: toly <1981462002@qq.com> Date: Sat, 28 Nov 2020 09:56:18 +0800 Subject: [PATCH] =?UTF-8?q?:sparkles:=20=E6=9B=B4=E6=96=B0flutter=5Fbloc?= =?UTF-8?q?=206.1.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/app/utils/stream_ext/backpressure.dart | 356 ++++++++++++++++++ lib/app/utils/stream_ext/ext.dart | 84 +++++ .../utils/stream_ext/forwarding_stream.dart | 82 ++++ lib/app/utils/stream_ext/timer_stream.dart | 56 +++ lib/blocs/category/category_bloc.dart | 4 +- .../category_widget/category_widget_bloc.dart | 6 +- lib/blocs/detail/detail_bloc.dart | 4 +- lib/blocs/global/global_bloc.dart | 4 +- lib/blocs/like/like_bloc.dart | 4 +- lib/blocs/point/point_bloc.dart | 3 +- .../point_comment/point_comment_bloc.dart | 5 +- lib/blocs/search/search_bloc.dart | 23 +- lib/blocs/search/search_event.dart | 4 +- lib/blocs/widgets/widgets_bloc.dart | 4 +- lib/views/pages/home/home_page.dart | 2 +- lib/views/pages/search/app_search_bar.dart | 2 +- lib/views/pages/search/serach_page.dart | 4 +- pubspec.lock | 15 +- pubspec.yaml | 60 +-- 19 files changed, 623 insertions(+), 99 deletions(-) create mode 100755 lib/app/utils/stream_ext/backpressure.dart create mode 100644 lib/app/utils/stream_ext/ext.dart create mode 100755 lib/app/utils/stream_ext/forwarding_stream.dart create mode 100644 lib/app/utils/stream_ext/timer_stream.dart diff --git a/lib/app/utils/stream_ext/backpressure.dart b/lib/app/utils/stream_ext/backpressure.dart new file mode 100755 index 0000000..29bebe9 --- /dev/null +++ b/lib/app/utils/stream_ext/backpressure.dart @@ -0,0 +1,356 @@ +import 'dart:async'; +import 'dart:collection'; + +import 'forwarding_stream.dart'; + + +/// A [Sink] that supports event hooks. +/// +/// This makes it suitable for certain rx transformers that need to +/// take action after onListen, onPause, onResume or onCancel. +/// +/// The [ForwardingSink] has been designed to handle asynchronous events from +/// [Stream]s. See, for example, [Stream.eventTransformed] which uses +/// `EventSink`s to transform events. +abstract class ForwardingSink { + /// Handle data event + void add(EventSink sink, T data); + + /// Handle error event + void addError(EventSink sink, dynamic error, [StackTrace st]); + + /// Handle close event + void close(EventSink sink); + + /// Fires when a listener subscribes on the underlying [Stream]. + void onListen(EventSink sink); + + /// Fires when a subscriber pauses. + void onPause(EventSink sink, [Future resumeSignal]); + + /// Fires when a subscriber resumes after a pause. + void onResume(EventSink sink); + + /// Fires when a subscriber cancels. + FutureOr onCancel(EventSink sink); +} + + +// import 'package:rxdart/src/utils/forwarding_sink.dart'; +// import 'package:rxdart/src/utils/forwarding_stream.dart'; + +/// The strategy that is used to determine how and when a new window is created. +enum WindowStrategy { + /// cancels the open window (if any) and immediately opens a fresh one. + everyEvent, + + /// waits until the current open window completes, then when the + /// source [Stream] emits a next event, it opens a new window. + eventAfterLastWindow, + + /// opens a recurring window right after the very first event on + /// the source [Stream] is emitted. + firstEventOnly, + + /// does not open any windows, rather all events are buffered and emitted + /// whenever the handler triggers, after this trigger, the buffer is cleared. + onHandler +} + +class _BackpressureStreamSink implements ForwardingSink { + final WindowStrategy _strategy; + final Stream Function(S event) _windowStreamFactory; + final T Function(S event) _onWindowStart; + final T Function(List queue) _onWindowEnd; + final int _startBufferEvery; + final bool Function(List queue) _closeWindowWhen; + final bool _ignoreEmptyWindows; + final bool _dispatchOnClose; + final queue = []; + var skip = 0; + var _hasData = false; + StreamSubscription _windowSubscription; + + _BackpressureStreamSink( + this._strategy, + this._windowStreamFactory, + this._onWindowStart, + this._onWindowEnd, + this._startBufferEvery, + this._closeWindowWhen, + this._ignoreEmptyWindows, + this._dispatchOnClose); + + @override + void add(EventSink sink, S data) { + _hasData = true; + maybeCreateWindow(data, sink); + + if (skip == 0) { + queue.add(data); + } + + if (skip > 0) { + skip--; + } + + maybeCloseWindow(sink); + } + + @override + void addError(EventSink sink, dynamic e, [st]) => sink.addError(e, st); + + @override + void close(EventSink sink) { + // treat the final event as a Window that opens + // and immediately closes again + if (_dispatchOnClose && queue.isNotEmpty) { + resolveWindowStart(queue.last, sink); + } + + resolveWindowEnd(sink, true); + + queue.clear(); + + _windowSubscription?.cancel(); + sink.close(); + } + + @override + FutureOr onCancel(EventSink sink) => _windowSubscription?.cancel(); + + @override + void onListen(EventSink sink) {} + + @override + void onPause(EventSink sink, [Future resumeSignal]) => + _windowSubscription?.pause(resumeSignal); + + @override + void onResume(EventSink sink) => _windowSubscription?.resume(); + + void maybeCreateWindow(S event, EventSink sink) { + switch (_strategy) { + // for example throttle + case WindowStrategy.eventAfterLastWindow: + if (_windowSubscription != null) return; + + _windowSubscription = singleWindow(event, sink); + + resolveWindowStart(event, sink); + + break; + // for example scan + case WindowStrategy.firstEventOnly: + if (_windowSubscription != null) return; + + _windowSubscription = multiWindow(event, sink); + + resolveWindowStart(event, sink); + + break; + // for example debounce + case WindowStrategy.everyEvent: + _windowSubscription?.cancel(); + + _windowSubscription = singleWindow(event, sink); + + resolveWindowStart(event, sink); + + break; + case WindowStrategy.onHandler: + break; + } + } + + void maybeCloseWindow(EventSink sink) { + if (_closeWindowWhen != null && + _closeWindowWhen(UnmodifiableListView(queue))) { + resolveWindowEnd(sink); + } + } + + StreamSubscription singleWindow(S event, EventSink sink) => + buildStream(event, sink).take(1).listen( + null, + onError: sink.addError, + onDone: () => resolveWindowEnd(sink), + ); + + // opens a new Window which is kept open until the main Stream + // closes. + StreamSubscription multiWindow(S event, EventSink sink) => + buildStream(event, sink).listen( + (dynamic _) => resolveWindowEnd(sink), + onError: sink.addError, + onDone: () => resolveWindowEnd(sink), + ); + + Stream buildStream(S event, EventSink sink) { + Stream stream; + + _windowSubscription?.cancel(); + + stream = _windowStreamFactory(event); + + if (stream == null) { + sink.addError(ArgumentError.notNull('windowStreamFactory')); + } + + return stream; + } + + void resolveWindowStart(S event, EventSink sink) { + if (_onWindowStart != null) { + sink.add(_onWindowStart(event)); + } + } + + void resolveWindowEnd(EventSink sink, [bool isControllerClosing = false]) { + if (isControllerClosing || + _strategy == WindowStrategy.eventAfterLastWindow || + _strategy == WindowStrategy.everyEvent) { + _windowSubscription?.cancel(); + _windowSubscription = null; + } + + if (isControllerClosing && !_dispatchOnClose) { + return; + } + + if (_hasData && (queue.isNotEmpty || !_ignoreEmptyWindows)) { + if (_onWindowEnd != null) { + sink.add(_onWindowEnd(List.unmodifiable(queue))); + } + + // prepare the buffer for the next window. + // by default, this is just a cleared buffer + if (!isControllerClosing && _startBufferEvery > 0) { + // ...unless startBufferEvery is provided. + // here we backtrack to the first event of the last buffer + // and count forward using startBufferEvery until we reach + // the next event. + // + // if the next event is found inside the current buffer, + // then this event and any later events in the buffer + // become the starting values of the next buffer. + // if the next event is not yet available, then a skip + // count is calculated. + // this count will skip the next Future n-events. + // when skip is reset to 0, then we start adding events + // again into the new buffer. + // + // example: + // startBufferEvery = 2 + // last buffer: [0, 1, 2, 3, 4] + // 0 is the first event, + // 2 is the n-th event + // new buffer starts with [2, 3, 4] + // + // example: + // startBufferEvery = 3 + // last buffer: [0, 1] + // 0 is the first event, + // the n-the event is not yet dispatched at this point + // skip becomes 1 + // event 2 is skipped, skip becomes 0 + // event 3 is now added to the buffer + final startWith = (_startBufferEvery < queue.length) + ? queue.sublist(_startBufferEvery) + : []; + + skip = _startBufferEvery > queue.length + ? _startBufferEvery - queue.length + : 0; + + queue + ..clear() + ..addAll(startWith); + } else { + queue.clear(); + } + } + } +} + +/// A highly customizable [StreamTransformer] which can be configured +/// to serve any of the common rx backpressure operators. +/// +/// The [StreamTransformer] works by creating windows, during which it +/// buffers events to a [Queue]. +/// +/// The [StreamTransformer] works by creating windows, during which it +/// buffers events to a [Queue]. It uses a [WindowStrategy] to determine +/// how and when a new window is created. +/// +/// onWindowStart and onWindowEnd are handlers that fire when a window +/// opens and closes, right before emitting the transformed event. +/// +/// startBufferEvery allows to skip events coming from the source [Stream]. +/// +/// ignoreEmptyWindows can be set to true, to allow events to be emitted +/// at the end of a window, even if the current buffer is empty. +/// If the buffer is empty, then an empty [List] will be emitted. +/// If false, then nothing is emitted on an empty buffer. +/// +/// dispatchOnClose will cause the remaining values in the buffer to be +/// emitted when the source [Stream] closes. +/// When false, the remaining buffer is discarded on close. +class BackpressureStreamTransformer extends StreamTransformerBase { + /// Determines how the window is created + final WindowStrategy strategy; + + /// Factory method used to create the [Stream] which will be buffered + final Stream Function(S event) windowStreamFactory; + + /// Handler which fires when the window opens + final T Function(S event) onWindowStart; + + /// Handler which fires when the window closes + final T Function(List queue) onWindowEnd; + + /// Used to skip an amount of events + final int startBufferEvery; + + /// Predicate which determines when the current window should close + final bool Function(List queue) closeWindowWhen; + + /// Toggle to prevent, or allow windows that contain + /// no events to be dispatched + final bool ignoreEmptyWindows; + + /// Toggle to prevent, or allow the final set of events to be dispatched + /// when the source [Stream] closes + final bool dispatchOnClose; + + /// Constructs a [StreamTransformer] which buffers events emitted by the + /// [Stream] that is created by [windowStreamFactory]. + /// + /// Use the various optional parameters to precisely determine how and when + /// this buffer should be created. + /// + /// For more info on the parameters, see [BackpressureStreamTransformer], + /// or see the various back pressure [StreamTransformer]s for examples. + BackpressureStreamTransformer(this.strategy, this.windowStreamFactory, + {this.onWindowStart, + this.onWindowEnd, + this.startBufferEvery = 0, + this.closeWindowWhen, + this.ignoreEmptyWindows = true, + this.dispatchOnClose = true}); + + @override + Stream bind(Stream stream) { + var sink = _BackpressureStreamSink( + strategy, + windowStreamFactory, + onWindowStart, + onWindowEnd, + startBufferEvery, + closeWindowWhen, + ignoreEmptyWindows, + dispatchOnClose, + ); + return forwardStream(stream, sink); + } +} diff --git a/lib/app/utils/stream_ext/ext.dart b/lib/app/utils/stream_ext/ext.dart new file mode 100644 index 0000000..deb1cf9 --- /dev/null +++ b/lib/app/utils/stream_ext/ext.dart @@ -0,0 +1,84 @@ +import 'backpressure.dart'; +import 'timer_stream.dart'; + +/// create by 张风捷特烈 on 2020/11/17 +/// contact me by email 1981462002@qq.com +/// 说明: + +/// Transforms a [Stream] so that will only emit items from the source sequence +/// if a window has completed, without the source sequence emitting +/// another item. +/// +/// This window is created after the last debounced event was emitted. +/// You can use the value of the last debounced event to determine +/// the length of the next window. +/// +/// A window is open until the first window event emits. +/// +/// The debounce [StreamTransformer] filters out items emitted by the source +/// Stream that are rapidly followed by another emitted item. +/// +/// [Interactive marble diagram](http://rxmarbles.com/#debounce) +/// +/// ### Example +/// +/// Stream.fromIterable([1, 2, 3, 4]) +/// .debounceTime(Duration(seconds: 1)) +/// .listen(print); // prints 4 +class DebounceStreamTransformer extends BackpressureStreamTransformer { + /// Constructs a [StreamTransformer] which buffers events into a [List] and + /// emits this [List] whenever the current [window] fires. + /// + /// The [window] is reset whenever the [Stream] that is being transformed + /// emits an event. + DebounceStreamTransformer(Stream Function(T event) window) + : super(WindowStrategy.everyEvent, window, + onWindowEnd: (Iterable queue) => queue.last) { + assert(window != null, 'window stream factory cannot be null'); + } +} + +/// Extends the Stream class with the ability to debounce events in various ways +extension DebounceExtensions on Stream { + /// Transforms a [Stream] so that will only emit items from the source sequence + /// if a [window] has completed, without the source sequence emitting + /// another item. + /// + /// This [window] is created after the last debounced event was emitted. + /// You can use the value of the last debounced event to determine + /// the length of the next [window]. + /// + /// A [window] is open until the first [window] event emits. + /// + /// debounce filters out items emitted by the source [Stream] + /// that are rapidly followed by another emitted item. + /// + /// [Interactive marble diagram](http://rxmarbles.com/#debounce) + /// + /// ### Example + /// + /// Stream.fromIterable([1, 2, 3, 4]) + /// .debounce((_) => TimerStream(true, Duration(seconds: 1))) + /// .listen(print); // prints 4 + Stream debounce(Stream Function(T event) window) => + transform(DebounceStreamTransformer(window)); + + /// Transforms a [Stream] so that will only emit items from the source + /// sequence whenever the time span defined by [duration] passes, without the + /// source sequence emitting another item. + /// + /// This time span start after the last debounced event was emitted. + /// + /// debounceTime filters out items emitted by the source [Stream] that are + /// rapidly followed by another emitted item. + /// + /// [Interactive marble diagram](http://rxmarbles.com/#debounceTime) + /// + /// ### Example + /// + /// Stream.fromIterable([1, 2, 3, 4]) + /// .debounceTime(Duration(seconds: 1)) + /// .listen(print); // prints 4 + Stream debounceTime(Duration duration) => transform( + DebounceStreamTransformer((_) => TimerStream(true, duration))); +} diff --git a/lib/app/utils/stream_ext/forwarding_stream.dart b/lib/app/utils/stream_ext/forwarding_stream.dart new file mode 100755 index 0000000..6c46812 --- /dev/null +++ b/lib/app/utils/stream_ext/forwarding_stream.dart @@ -0,0 +1,82 @@ +import 'dart:async'; + +import 'backpressure.dart'; + +// import 'package:rxdart/src/utils/forwarding_sink.dart'; + +// import 'forwarding_sink.dart'; + +/// @private +/// Helper method which forwards the events from an incoming [Stream] +/// to a new [StreamController]. +/// It captures events such as onListen, onPause, onResume and onCancel, +/// which can be used in pair with a [ForwardingSink] +Stream forwardStream( + Stream stream, + ForwardingSink connectedSink, +) { + ArgumentError.checkNotNull(stream, 'stream'); + ArgumentError.checkNotNull(connectedSink, 'connectedSink'); + + StreamController controller; + StreamSubscription subscription; + + void runCatching(void Function() block) { + try { + block(); + } catch (e, s) { + connectedSink.addError(controller, e, s); + } + } + + final onListen = () { + runCatching(() => connectedSink.onListen(controller)); + + subscription = stream.listen( + (data) => runCatching(() => connectedSink.add(controller, data)), + onError: (dynamic e, StackTrace st) => + runCatching(() => connectedSink.addError(controller, e, st)), + onDone: () => runCatching(() => connectedSink.close(controller)), + ); + }; + + final onCancel = () { + final onCancelSelfFuture = subscription.cancel(); + final onCancelConnectedFuture = connectedSink.onCancel(controller); + final futures = [ + if (onCancelSelfFuture is Future) onCancelSelfFuture, + if (onCancelConnectedFuture is Future) onCancelConnectedFuture, + ]; + return Future.wait(futures); + }; + + final onPause = ([Future resumeSignal]) { + subscription.pause(resumeSignal); + runCatching(() => connectedSink.onPause(controller, resumeSignal)); + }; + + final onResume = () { + subscription.resume(); + runCatching(() => connectedSink.onResume(controller)); + }; + + // Create a new Controller, which will serve as a trampoline for + // forwarded events. + if (stream.isBroadcast) { + controller = StreamController.broadcast( + onListen: onListen, + onCancel: onCancel, + sync: true, + ); + } else { + controller = StreamController( + onListen: onListen, + onPause: onPause, + onResume: onResume, + onCancel: onCancel, + sync: true, + ); + } + + return controller.stream; +} diff --git a/lib/app/utils/stream_ext/timer_stream.dart b/lib/app/utils/stream_ext/timer_stream.dart new file mode 100644 index 0000000..be2ea0b --- /dev/null +++ b/lib/app/utils/stream_ext/timer_stream.dart @@ -0,0 +1,56 @@ +import 'dart:async'; + +/// Emits the given value after a specified amount of time. +/// +/// ### Example +/// +/// TimerStream('hi', Duration(minutes: 1)) +/// .listen((i) => print(i)); // print 'hi' after 1 minute +class TimerStream extends Stream { + final StreamController _controller; + + /// Constructs a [Stream] which emits [value] after the specified [Duration]. + TimerStream(T value, Duration duration) + : _controller = _buildController(value, duration); + + @override + StreamSubscription listen(void Function(T event) onData, + {Function onError, void Function() onDone, bool cancelOnError}) { + return _controller.stream.listen( + onData, + onError: onError, + onDone: onDone, + cancelOnError: cancelOnError, + ); + } + + static StreamController _buildController(T value, Duration duration) { + if (duration == null) { + throw ArgumentError('duration cannot be null'); + } + + StreamSubscription subscription; + StreamController controller; + + controller = StreamController( + sync: true, + onListen: () { + subscription = + Stream.fromFuture(Future.delayed(duration, () => value)).listen( + controller.add, + onError: controller.addError, + onDone: () { + if (!controller.isClosed) { + controller.close(); + } + }, + ); + }, + onPause: ([Future resumeSignal]) => + subscription.pause(resumeSignal), + onResume: () => subscription.resume(), + onCancel: () => subscription.cancel(), + ); + return controller; + } +} diff --git a/lib/blocs/category/category_bloc.dart b/lib/blocs/category/category_bloc.dart index 5cd011f..61aeba9 100644 --- a/lib/blocs/category/category_bloc.dart +++ b/lib/blocs/category/category_bloc.dart @@ -15,10 +15,8 @@ import 'category_state.dart'; class CategoryBloc extends Bloc { final CategoryRepository repository; - CategoryBloc({@required this.repository}); + CategoryBloc({@required this.repository}):super(CategoryEmptyState()); - @override - CategoryState get initialState => CategoryEmptyState(); //初始状态 @override Stream mapEventToState(CategoryEvent event) async* { diff --git a/lib/blocs/category_widget/category_widget_bloc.dart b/lib/blocs/category_widget/category_widget_bloc.dart index 0757abc..288cd5c 100644 --- a/lib/blocs/category_widget/category_widget_bloc.dart +++ b/lib/blocs/category_widget/category_widget_bloc.dart @@ -15,13 +15,11 @@ class CategoryWidgetBloc extends Bloc { final CategoryBloc categoryBloc; - CategoryWidgetBloc({@required this.categoryBloc}); + CategoryWidgetBloc({@required this.categoryBloc}) + : super(CategoryWidgetEmptyState()); CategoryRepository get repository => categoryBloc.repository; - @override - CategoryWidgetState get initialState => CategoryWidgetEmptyState(); //初始状态 - @override Stream mapEventToState( CategoryWidgetEvent event) async* { diff --git a/lib/blocs/detail/detail_bloc.dart b/lib/blocs/detail/detail_bloc.dart index a169c9b..2025d72 100644 --- a/lib/blocs/detail/detail_bloc.dart +++ b/lib/blocs/detail/detail_bloc.dart @@ -13,10 +13,8 @@ import 'detail_state.dart'; class DetailBloc extends Bloc { final WidgetRepository repository; - DetailBloc({@required this.repository}); + DetailBloc({@required this.repository}):super(DetailLoading()); - @override - DetailState get initialState => DetailLoading(); @override Stream mapEventToState(DetailEvent event) async* { diff --git a/lib/blocs/global/global_bloc.dart b/lib/blocs/global/global_bloc.dart index 2368593..97cb9b1 100644 --- a/lib/blocs/global/global_bloc.dart +++ b/lib/blocs/global/global_bloc.dart @@ -12,12 +12,10 @@ import 'global_state.dart'; /// 说明: 全局信息的bloc class GlobalBloc extends Bloc { - @override - GlobalState get initialState => GlobalState(); final AppStorage storage; - GlobalBloc(this.storage); + GlobalBloc(this.storage):super(GlobalState()); Future get sp => storage.sp; diff --git a/lib/blocs/like/like_bloc.dart b/lib/blocs/like/like_bloc.dart index 24ad761..823cdba 100644 --- a/lib/blocs/like/like_bloc.dart +++ b/lib/blocs/like/like_bloc.dart @@ -14,10 +14,8 @@ import 'like_state.dart'; class LikeWidgetBloc extends Bloc { final WidgetRepository repository; - LikeWidgetBloc({@required this.repository}); + LikeWidgetBloc({@required this.repository}):super(LikeWidgetState(widgets: [])); - @override - LikeWidgetState get initialState => LikeWidgetState(widgets: []); //初始状态 @override Stream mapEventToState( diff --git a/lib/blocs/point/point_bloc.dart b/lib/blocs/point/point_bloc.dart index 3f47bb0..5014963 100644 --- a/lib/blocs/point/point_bloc.dart +++ b/lib/blocs/point/point_bloc.dart @@ -12,9 +12,8 @@ import 'point_event.dart'; /// 说明: class PointBloc extends Bloc { + PointBloc() : super(PointLoading()); - @override - PointState get initialState => PointLoading(); @override Stream mapEventToState(PointEvent event) async* { diff --git a/lib/blocs/point_comment/point_comment_bloc.dart b/lib/blocs/point_comment/point_comment_bloc.dart index 904bf50..4e5e151 100644 --- a/lib/blocs/point_comment/point_comment_bloc.dart +++ b/lib/blocs/point_comment/point_comment_bloc.dart @@ -14,8 +14,9 @@ import 'point_comment_state.dart'; class PointCommentBloc extends Bloc { - @override - PointCommentState get initialState => PointCommentInitial(); + PointCommentBloc() : super(PointCommentInitial()); + + @override Stream mapEventToState(PointCommentEvent event) async* { diff --git a/lib/blocs/search/search_bloc.dart b/lib/blocs/search/search_bloc.dart index 703cfcc..c9e2328 100644 --- a/lib/blocs/search/search_bloc.dart +++ b/lib/blocs/search/search_bloc.dart @@ -6,29 +6,25 @@ import 'package:flutter_unit/repositories/itf/widget_repository.dart'; import 'search_event.dart'; import 'search_state.dart'; import 'package:bloc/bloc.dart'; -import 'package:rxdart/rxdart.dart'; - +import 'package:flutter_unit/app/utils/stream_ext/ext.dart'; class SearchBloc extends Bloc { final WidgetRepository repository; - SearchBloc({@required this.repository}); - @override - SearchState get initialState => SearchStateNoSearch();//初始状态 + SearchBloc({@required this.repository}):super(SearchStateNoSearch()); @override - Stream transformEvents( - Stream events, - Stream Function(SearchEvent event) next,) { - return super.transformEvents(events - .debounceTime(Duration(milliseconds: 500),), - next, - ); + Stream> transformEvents( + Stream events, TransitionFunction transitionFn) { + return super.transformEvents(events + .debounceTime(Duration(milliseconds: 500),), + transitionFn, + ); } @override Stream mapEventToState(SearchEvent event,) async* { - if (event is EventTextChanged) { + if (event is SearchWidgetEvent) { if (event.args.name.isEmpty&&event.args.stars.every((e)=>e==-1)) { yield SearchStateNoSearch(); } else { @@ -36,7 +32,6 @@ class SearchBloc extends Bloc { try { final results = await repository.searchWidgets(event.args); yield results.length==0?SearchStateEmpty():SearchStateSuccess(results); - print('mapEventToState'); } catch (error) { print(error); yield SearchStateError(); diff --git a/lib/blocs/search/search_event.dart b/lib/blocs/search/search_event.dart index 9738098..0f6a466 100644 --- a/lib/blocs/search/search_event.dart +++ b/lib/blocs/search/search_event.dart @@ -6,7 +6,7 @@ abstract class SearchEvent{//事件基 const SearchEvent(); } -class EventTextChanged extends SearchEvent { +class SearchWidgetEvent extends SearchEvent { final SearchArgs args;//参数 - const EventTextChanged({this.args}); + const SearchWidgetEvent({this.args}); } diff --git a/lib/blocs/widgets/widgets_bloc.dart b/lib/blocs/widgets/widgets_bloc.dart index f14f5b1..62af227 100644 --- a/lib/blocs/widgets/widgets_bloc.dart +++ b/lib/blocs/widgets/widgets_bloc.dart @@ -17,10 +17,8 @@ import 'widgets_state.dart'; class WidgetsBloc extends Bloc { final WidgetRepository repository; - WidgetsBloc({@required this.repository}); + WidgetsBloc({@required this.repository}):super(WidgetsLoading()); - @override - WidgetsState get initialState => WidgetsLoading(); Color get activeHomeColor { diff --git a/lib/views/pages/home/home_page.dart b/lib/views/pages/home/home_page.dart index 908d47f..7b45581 100644 --- a/lib/views/pages/home/home_page.dart +++ b/lib/views/pages/home/home_page.dart @@ -115,7 +115,7 @@ class _HomePageState extends State Widget _buildHomeItem(WidgetModel model) => BlocBuilder( - condition: (p, c) => (p.itemStyleIndex != c.itemStyleIndex), + buildWhen: (p, c) => (p.itemStyleIndex != c.itemStyleIndex), builder: (_, state) { return FeedbackWidget( a: 0.95, diff --git a/lib/views/pages/search/app_search_bar.dart b/lib/views/pages/search/app_search_bar.dart index 7be8d2d..7ab245e 100644 --- a/lib/views/pages/search/app_search_bar.dart +++ b/lib/views/pages/search/app_search_bar.dart @@ -35,7 +35,7 @@ class _AppSearchBarState extends State { hintStyle: TextStyle(fontSize: 14)//提示样式 ), onChanged: (str) => BlocProvider.of(context) - .add(EventTextChanged(args:SearchArgs(name: str,stars: [1,2,3,4,5]))), + .add(SearchWidgetEvent(args:SearchArgs(name: str,stars: [1,2,3,4,5]))), onSubmitted: (str) {//提交后 FocusScope.of(context).requestFocus(FocusNode()); //收起键盘 diff --git a/lib/views/pages/search/serach_page.dart b/lib/views/pages/search/serach_page.dart index bf333eb..b67e03c 100644 --- a/lib/views/pages/search/serach_page.dart +++ b/lib/views/pages/search/serach_page.dart @@ -28,7 +28,7 @@ class _SearchPageState extends State { body: WillPopScope( onWillPop: () async { //返回时 情空搜索 - BlocProvider.of(context).add(EventTextChanged(args: SearchArgs())); + BlocProvider.of(context).add(SearchWidgetEvent(args: SearchArgs())); return true; }, child: CustomScrollView( @@ -119,7 +119,7 @@ class _SearchPageState extends State { temp.addAll(List.generate(5 - temp.length, (e) => -1)); } BlocProvider.of(context) - .add(EventTextChanged(args: SearchArgs(name: '', stars: temp))); + .add(SearchWidgetEvent(args: SearchArgs(name: '', stars: temp))); } _toDetailPage(WidgetModel model) { diff --git a/pubspec.lock b/pubspec.lock index 03dc800..0de7e32 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -21,7 +21,7 @@ packages: name: bloc url: "https://pub.flutter-io.cn" source: hosted - version: "3.0.0" + version: "6.1.0" boolean_selector: dependency: transitive description: @@ -98,7 +98,7 @@ packages: name: equatable url: "https://pub.flutter-io.cn" source: hosted - version: "1.1.1" + version: "1.2.5" fake_async: dependency: transitive description: @@ -124,7 +124,7 @@ packages: name: flutter_bloc url: "https://pub.flutter-io.cn" source: hosted - version: "3.2.0" + version: "6.1.1" flutter_markdown: dependency: "direct main" description: @@ -260,14 +260,7 @@ packages: name: provider url: "https://pub.flutter-io.cn" source: hosted - version: "4.0.4" - rxdart: - dependency: transitive - description: - name: rxdart - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.23.1" + version: "4.3.2+2" share: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index e89510b..84f66c8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,78 +1,48 @@ name: flutter_unit description: A new Flutter application. -# The following defines the version and build number for your application. -# A version number is three numbers separated by dots, like 1.2.43 -# followed by an optional build number separated by a +. -# Both the version and the builder number may be overridden in flutter -# build by specifying --build-name and --build-number, respectively. -# In Android, build-name is used as versionName while build-number used as versionCode. -# Read more about Android versioning at https://developer.android.com/studio/publish/versioning -# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. -# Read more about iOS versioning at -# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html version: 1.1.0+1 +author: 张风捷特烈 <1981462002@qq.com> +homepage: https://juejin.cn/user/149189281194766/posts environment: - sdk: ">=2.3.0 <3.0.0" + sdk: ">=2.7.0 <3.0.0" dependencies: flutter: sdk: flutter - flutter_bloc: ^3.2.0 - equatable: ^1.0.3 - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^0.1.2 - sqflite: ^1.2.1 #数据库 + + flutter_bloc: ^6.1.1 # 状态管理 + equatable: ^1.2.5 # 相等辅助 + + sqflite: ^1.2.1 # 数据库 + shared_preferences: ^0.5.6+3 # xml 固化 + toggle_rotate: ^0.0.5 flutter_star: ^0.1.2 # 星星组件 - shared_preferences: ^0.5.6+3 # xml 固化 + url_launcher: ^5.4.2 # url - share: ^0.6.3+6 + share: ^0.6.3+6 # 文字分享 intl: ^0.16.1 - path_provider: ^1.6.11 + path_provider: ^1.6.11 # 路径 connectivity: ^0.4.8+6 #网络状态 - flutter_spinkit: ^4.1.2+1 #loading - flutter_markdown: ^0.4.4 #markdown + flutter_spinkit: ^4.1.2+1 # loading + flutter_markdown: ^0.4.4 # markdown dio: ^3.0.9 #dio dev_dependencies: flutter_test: sdk: flutter -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter. flutter: - - # The following line ensures that the Material Icons font is - # included with your application, so that you can use the icons in - # the material Icons class. uses-material-design: true assets: - assets/images/ - assets/images/head_icon/ - assets/images/widgets/ - # - assets/data/widget.json - assets/flutter.db - # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware. - - # For details regarding adding assets from package dependencies, see - # https://flutter.dev/assets-and-images/#from-packages - - # To add custom fonts to your application, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: fonts: # 配置字体,可配置多个,支持ttf和otf,ttc等字体资源 - family: IndieFlower #字体名 fonts: