Line data Source code
1 : import 'dart:async';
2 :
3 : import 'package:meta/meta.dart';
4 :
5 : /// {@template stream.stream_extensions}
6 : /// Stream extension methods.
7 : /// {@endtemplate}
8 : extension BatteriesStreamX<A> on Stream<A> {
9 : /// {@macro stream.relieve_stream_transformer}
10 1 : Stream<A> relieve([
11 : Duration duration = const Duration(milliseconds: 4),
12 : ]) =>
13 2 : transform<A>(RelieveStreamTransformer<A>(duration));
14 :
15 : /// {@macro stream.calm_stream_transformer}
16 1 : Stream<A> calm(Duration duration) =>
17 2 : transform<A>(CalmStreamTransformer<A>(duration));
18 :
19 : /// {@macro stream.transform_on_type.transformer}
20 1 : Stream<A> transformOnType<B extends A>(
21 : Stream<B> Function(Stream<B> selected) transform,
22 : ) =>
23 2 : this.transform(TransformOnTypeTransformer<A, B>(transform));
24 : }
25 :
26 : /// {@template stream.relieve_stream_transformer}
27 : /// Allow relieve impact on event loop on large collections.
28 : /// Parallelize the event queue and free up time for processing animation,
29 : /// user gestures without using isolates.
30 : ///
31 : /// Thats transformer makes stream low priority.
32 : ///
33 : /// [duration] - elapsed time of iterations before releasing
34 : /// the event queue and microtasks.
35 : /// {@endtemplate}
36 : @immutable
37 : class RelieveStreamTransformer<T> extends StreamTransformerBase<T, T> {
38 : /// {@macro stream.relieve_stream_transformer}
39 1 : const RelieveStreamTransformer([
40 : this.duration = const Duration(milliseconds: 4),
41 : ]);
42 :
43 : /// Elapsed time of iterations before releasing
44 : /// the event queue and microtasks.
45 : final Duration duration;
46 :
47 1 : @override
48 : Stream<T> bind(Stream<T> stream) {
49 : /*
50 : final sw = Stopwatch()..start();
51 : StreamSubscription<A>? sub;
52 : final sc = stream.isBroadcast
53 : ? StreamController<A>.broadcast(
54 : onCancel: () => sub?.cancel(),
55 : sync: true,
56 : )
57 : : StreamController<A>(
58 : onCancel: () => sub?.cancel(),
59 : sync: true,
60 : );
61 : sub = stream.listen(
62 : (A value) {
63 : if (sw.elapsed > duration) {
64 : sub?.pause();
65 : Future<void>.delayed(Duration.zero).then<void>(
66 : (_) {
67 : sc.add(value);
68 : sw.reset();
69 : sub?.resume();
70 : },
71 : onError: sc.addError,
72 : );
73 : } else {
74 : sc.add(value);
75 : }
76 : },
77 : onDone: () {
78 : sw.stop();
79 : sc.close();
80 : },
81 : onError: sc.addError,
82 : cancelOnError: false,
83 : );
84 : return sc.stream;
85 : */
86 1 : final controller = stream.isBroadcast
87 1 : ? StreamController<T>.broadcast(
88 : onListen: null,
89 : onCancel: null,
90 : sync: false,
91 : )
92 1 : : StreamController<T>(
93 : onListen: null,
94 : onCancel: null,
95 : onPause: null,
96 : onResume: null,
97 : sync: false,
98 : );
99 1 : final add = controller.add;
100 1 : final addError = controller.addError;
101 1 : final close = controller.close;
102 2 : controller.onListen = () {
103 2 : final stopwatch = Stopwatch()..start();
104 1 : final subscription = stream.listen(
105 : null,
106 : onError: addError,
107 1 : onDone: () {
108 1 : stopwatch.stop();
109 1 : close();
110 : },
111 : );
112 1 : final resume = subscription.resume;
113 2 : subscription.onData((T event) {
114 3 : if (stopwatch.elapsed > duration) {
115 1 : subscription.pause();
116 2 : Future<void>.delayed(Duration.zero).then<void>(
117 1 : (_) {
118 1 : add(event);
119 1 : stopwatch.reset();
120 1 : resume();
121 : },
122 : onError: addError,
123 : );
124 : } else {
125 1 : add(event);
126 : }
127 : });
128 2 : controller.onCancel = () {
129 1 : stopwatch.stop();
130 1 : subscription.cancel();
131 : };
132 1 : if (!stream.isBroadcast) {
133 : controller
134 2 : ..onPause = subscription.pause
135 1 : ..onResume = resume;
136 : }
137 : };
138 1 : return controller.stream;
139 : }
140 : }
141 :
142 : /// {@template stream.transform_on_type.transformer}
143 : /// Allows to transform a stream on a specific subtype of events.
144 : ///
145 : /// The transformer performs the transformation on the events of the specified
146 : /// type, while merging the rest of the stream in the output stream.
147 : ///
148 : /// The following set of actions are performed:
149 : /// 1) The stream that contains only the specified subtype of events is
150 : /// filtered out from the rest.
151 : /// 2) The newly filtered stream is passed to the specified [transform]
152 : /// callback.
153 : /// 3) The rest of the stream is filtered out.
154 : /// 4) Two stream are merged into one.
155 : ///
156 : /// [transform] –
157 : /// {@template stream.transform_on_type.transform}
158 : /// Callback that performs an endomorphic transformation on the
159 : /// stream of specified subtype.
160 : /// {@endtemplate}
161 : /// {@endtemplate}
162 : @immutable
163 : class TransformOnTypeTransformer<A, B extends A>
164 : extends StreamTransformerBase<A, A> {
165 : /// {@macro stream.transform_on_type.transform}
166 : final Stream<B> Function(Stream<B> selected) transform;
167 :
168 : /// {@macro stream.transform_on_type.transformer}
169 1 : const TransformOnTypeTransformer(this.transform);
170 :
171 1 : @override
172 : Stream<A> bind(Stream<A> stream) {
173 1 : final source = stream.asBroadcastStream();
174 :
175 : StreamSubscription<A>? remainingSubscription;
176 : StreamSubscription<A>? transformedSubscription;
177 :
178 0 : void pauseSubscriptions() {
179 0 : remainingSubscription?.pause();
180 0 : transformedSubscription?.pause();
181 : }
182 :
183 0 : void resumeSubscriptions() {
184 0 : remainingSubscription?.resume();
185 0 : transformedSubscription?.resume();
186 : }
187 :
188 1 : final controller = stream.isBroadcast
189 1 : ? StreamController<A>.broadcast(sync: true)
190 1 : : StreamController<A>(
191 : onPause: pauseSubscriptions,
192 : onResume: resumeSubscriptions,
193 : sync: true,
194 : );
195 :
196 1 : void subscribe({
197 : required Stream<A> stream,
198 : required void Function(StreamSubscription<A> subscription) store,
199 : void Function()? onDone,
200 : }) {
201 1 : store(
202 1 : stream.listen(
203 1 : controller.add,
204 1 : onError: controller.addError,
205 : onDone: onDone,
206 : cancelOnError: false,
207 : ),
208 : );
209 : }
210 :
211 1 : void cancelSubscriptions() {
212 1 : remainingSubscription?.cancel();
213 1 : transformedSubscription?.cancel();
214 : }
215 :
216 1 : void wrapUp() {
217 1 : cancelSubscriptions();
218 1 : controller.close();
219 : }
220 :
221 1 : void startListening() {
222 1 : subscribe(
223 3 : stream: source.where((event) => event is! B),
224 1 : store: (subscription) => remainingSubscription = subscription,
225 : onDone: wrapUp,
226 : );
227 1 : subscribe(
228 6 : stream: transform(source.where((event) => event is B).cast<B>()),
229 1 : store: (subscription) => transformedSubscription = subscription,
230 : );
231 : }
232 :
233 : controller
234 1 : ..onListen = startListening
235 1 : ..onCancel = cancelSubscriptions;
236 :
237 1 : return controller.stream;
238 : }
239 : }
240 :
241 : /// {@template stream.calm_stream_transformer}
242 : /// Calm stream transformer.
243 : /// For example, when you need a pause between events no less than specified.
244 : /// e.g sending messages to the messenger with intervals and pauses,
245 : /// in order not to be banned for spam.
246 : /// {@endtemplate}
247 : @immutable
248 : class CalmStreamTransformer<T> extends StreamTransformerBase<T, T> {
249 : /// {@macro stream.calm_stream_transformer}
250 1 : const CalmStreamTransformer(this.duration);
251 :
252 : /// Elapsed time of iterations before releasing next event.
253 : final Duration duration;
254 :
255 1 : @override
256 : Stream<T> bind(Stream<T> stream) {
257 1 : final controller = stream.isBroadcast
258 1 : ? StreamController<T>.broadcast(
259 : onListen: null,
260 : onCancel: null,
261 : sync: false,
262 : )
263 1 : : StreamController<T>(
264 : onListen: null,
265 : onCancel: null,
266 : onPause: null,
267 : onResume: null,
268 : sync: false,
269 : );
270 1 : final add = controller.add;
271 1 : final addError = controller.addError;
272 1 : final close = controller.close;
273 2 : controller.onListen = () {
274 1 : final subscription = stream.listen(
275 : null,
276 : onError: addError,
277 : onDone: close,
278 : );
279 1 : final pause = subscription.pause;
280 1 : final resume = subscription.resume;
281 2 : subscription.onData((T event) {
282 1 : add(event);
283 1 : pause();
284 2 : Future<void>.delayed(duration)
285 1 : .catchError(addError)
286 1 : .whenComplete(resume);
287 : });
288 2 : controller.onCancel = subscription.cancel;
289 1 : if (!stream.isBroadcast) {
290 : controller
291 1 : ..onPause = pause
292 1 : ..onResume = resume;
293 : }
294 : };
295 1 : return controller.stream;
296 : }
297 : }
|