mirror of
https://gitlab.com/veilid/veilidchat.git
synced 2025-08-13 00:15:42 -04:00
refactor bloc tools to its own package
This commit is contained in:
parent
8e7619677a
commit
43b01c7555
34 changed files with 284 additions and 146 deletions
|
@ -1,42 +0,0 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:async_tools/async_tools.dart';
|
||||
import 'package:bloc/bloc.dart';
|
||||
|
||||
class AsyncTransformerCubit<T, S> extends Cubit<AsyncValue<T>> {
|
||||
AsyncTransformerCubit(this.input, {required this.transform})
|
||||
: super(const AsyncValue.loading()) {
|
||||
_asyncTransform(input.state);
|
||||
_subscription = input.stream.listen(_asyncTransform);
|
||||
}
|
||||
void _asyncTransform(AsyncValue<S> newInputState) {
|
||||
_singleStateProcessor.updateState(newInputState, closure: (newState) async {
|
||||
// Emit the transformed state
|
||||
try {
|
||||
if (newState is AsyncLoading<S>) {
|
||||
emit(const AsyncValue.loading());
|
||||
} else if (newState is AsyncError<S>) {
|
||||
emit(AsyncValue.error(newState.error, newState.stackTrace));
|
||||
} else {
|
||||
final transformedState = await transform(newState.data!.value);
|
||||
emit(transformedState);
|
||||
}
|
||||
} on Exception catch (e, st) {
|
||||
emit(AsyncValue.error(e, st));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
await _subscription.cancel();
|
||||
await input.close();
|
||||
await super.close();
|
||||
}
|
||||
|
||||
Cubit<AsyncValue<S>> input;
|
||||
final SingleStateProcessor<AsyncValue<S>> _singleStateProcessor =
|
||||
SingleStateProcessor();
|
||||
Future<AsyncValue<T>> Function(S) transform;
|
||||
late final StreamSubscription<AsyncValue<S>> _subscription;
|
||||
}
|
|
@ -1,112 +0,0 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:async_tools/async_tools.dart';
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
|
||||
typedef BlocMapState<K, S> = IMap<K, S>;
|
||||
|
||||
class _ItemEntry<S, B> {
|
||||
_ItemEntry({required this.bloc, required this.subscription});
|
||||
final B bloc;
|
||||
final StreamSubscription<S> subscription;
|
||||
}
|
||||
|
||||
// Streaming container cubit that is a map from some immutable key
|
||||
// to a some other cubit's output state. Output state for this container
|
||||
// cubit is an immutable map of the key to the output state of the contained
|
||||
// cubits.
|
||||
//
|
||||
// K = Key type for the bloc map, used to look up some mapped cubit
|
||||
// S = State type for the value, keys will look up values of this type
|
||||
// B = Bloc/cubit type for the value, output states of type S
|
||||
abstract class BlocMapCubit<K, S, B extends BlocBase<S>>
|
||||
extends Cubit<BlocMapState<K, S>> {
|
||||
BlocMapCubit()
|
||||
: _entries = {},
|
||||
_tagLock = AsyncTagLock(),
|
||||
super(IMap<K, S>());
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
await _entries.values.map((e) => e.subscription.cancel()).wait;
|
||||
await _entries.values.map((e) => e.bloc.close()).wait;
|
||||
await super.close();
|
||||
}
|
||||
|
||||
Future<void> add(MapEntry<K, B> Function() create) {
|
||||
// Create new element
|
||||
final newElement = create();
|
||||
final key = newElement.key;
|
||||
final bloc = newElement.value;
|
||||
|
||||
return _tagLock.protect(key, closure: () async {
|
||||
// Remove entry with the same key if it exists
|
||||
await _internalRemove(key);
|
||||
|
||||
// Add entry with this key
|
||||
_entries[key] = _ItemEntry(
|
||||
bloc: bloc,
|
||||
subscription: bloc.stream.listen((data) {
|
||||
// Add sub-cubit's state to the map state
|
||||
emit(state.add(key, data));
|
||||
}));
|
||||
|
||||
emit(state.add(key, bloc.state));
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> addState(K key, S value) =>
|
||||
_tagLock.protect(key, closure: () async {
|
||||
// Remove entry with the same key if it exists
|
||||
await _internalRemove(key);
|
||||
|
||||
emit(state.add(key, value));
|
||||
});
|
||||
|
||||
Future<void> _internalRemove(K key) async {
|
||||
final sub = _entries.remove(key);
|
||||
if (sub != null) {
|
||||
await sub.subscription.cancel();
|
||||
await sub.bloc.close();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> remove(K key) => _tagLock.protect(key, closure: () async {
|
||||
await _internalRemove(key);
|
||||
emit(state.remove(key));
|
||||
});
|
||||
|
||||
R operate<R>(K key, {required R Function(B bloc) closure}) {
|
||||
final bloc = _entries[key]!.bloc;
|
||||
return closure(bloc);
|
||||
}
|
||||
|
||||
R? tryOperate<R>(K key, {required R Function(B bloc) closure}) {
|
||||
final entry = _entries[key];
|
||||
if (entry == null) {
|
||||
return null;
|
||||
}
|
||||
return closure(entry.bloc);
|
||||
}
|
||||
|
||||
Future<R> operateAsync<R>(K key,
|
||||
{required Future<R> Function(B bloc) closure}) =>
|
||||
_tagLock.protect(key, closure: () async {
|
||||
final bloc = _entries[key]!.bloc;
|
||||
return closure(bloc);
|
||||
});
|
||||
|
||||
Future<R?> tryOperateAsync<R>(K key,
|
||||
{required Future<R> Function(B bloc) closure}) =>
|
||||
_tagLock.protect(key, closure: () async {
|
||||
final entry = _entries[key];
|
||||
if (entry == null) {
|
||||
return null;
|
||||
}
|
||||
return closure(entry.bloc);
|
||||
});
|
||||
|
||||
final Map<K, _ItemEntry<S, B>> _entries;
|
||||
final AsyncTagLock<K> _tagLock;
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
import 'package:bloc/bloc.dart';
|
||||
|
||||
mixin BlocTools<State> on BlocBase<State> {
|
||||
void withStateListen(void Function(State event)? onData,
|
||||
{Function? onError, void Function()? onDone, bool? cancelOnError}) {
|
||||
if (onData != null) {
|
||||
onData(state);
|
||||
}
|
||||
stream.listen(onData,
|
||||
onError: onError, onDone: onDone, cancelOnError: cancelOnError);
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:async_tools/async_tools.dart';
|
||||
import 'package:bloc/bloc.dart';
|
||||
|
||||
abstract class FutureCubit<State> extends Cubit<AsyncValue<State>> {
|
||||
FutureCubit(Future<State> fut) : super(const AsyncValue.loading()) {
|
||||
unawaited(fut.then((value) {
|
||||
emit(AsyncValue.data(value));
|
||||
// ignore: avoid_types_on_closure_parameters
|
||||
}, onError: (Object e, StackTrace stackTrace) {
|
||||
emit(AsyncValue.error(e, stackTrace));
|
||||
}));
|
||||
}
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:async_tools/async_tools.dart';
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
|
||||
// Mixin that automatically keeps two blocs/cubits in sync with each other
|
||||
// Useful for having a BlocMapCubit 'follow' the state of another input cubit.
|
||||
// As the state of the input cubit changes, the BlocMapCubit can add/remove
|
||||
// mapped Cubits that automatically process the input state reactively.
|
||||
//
|
||||
// S = Input state type
|
||||
// K = Key derived from elements of input state
|
||||
// V = Value derived from elements of input state
|
||||
abstract mixin class StateFollower<S extends Object, K, V> {
|
||||
void follow({
|
||||
required S initialInputState,
|
||||
required Stream<S> stream,
|
||||
}) {
|
||||
//
|
||||
_lastInputStateMap = getStateMap(initialInputState);
|
||||
_subscription = stream.listen(_updateFollow);
|
||||
}
|
||||
|
||||
Future<void> close() async {
|
||||
await _subscription.cancel();
|
||||
}
|
||||
|
||||
IMap<K, V> getStateMap(S state);
|
||||
Future<void> removeFromState(K key);
|
||||
Future<void> updateState(K key, V value);
|
||||
|
||||
void _updateFollow(S newInputState) {
|
||||
_singleStateProcessor.updateState(getStateMap(newInputState),
|
||||
closure: (newStateMap) async {
|
||||
for (final k in _lastInputStateMap.keys) {
|
||||
if (!newStateMap.containsKey(k)) {
|
||||
// deleted
|
||||
await removeFromState(k);
|
||||
}
|
||||
}
|
||||
for (final newEntry in newStateMap.entries) {
|
||||
final v = _lastInputStateMap.get(newEntry.key);
|
||||
if (v == null || v != newEntry.value) {
|
||||
// added or changed
|
||||
await updateState(newEntry.key, newEntry.value);
|
||||
}
|
||||
}
|
||||
|
||||
// Keep this state map for the next time
|
||||
_lastInputStateMap = newStateMap;
|
||||
});
|
||||
}
|
||||
|
||||
late IMap<K, V> _lastInputStateMap;
|
||||
final SingleStateProcessor<IMap<K, V>> _singleStateProcessor =
|
||||
SingleStateProcessor();
|
||||
late final StreamSubscription<S> _subscription;
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:async_tools/async_tools.dart';
|
||||
import 'package:bloc/bloc.dart';
|
||||
|
||||
abstract class StreamWrapperCubit<State> extends Cubit<AsyncValue<State>> {
|
||||
StreamWrapperCubit(Stream<State> stream, {State? defaultState})
|
||||
: super(defaultState != null
|
||||
? AsyncValue.data(defaultState)
|
||||
: const AsyncValue.loading()) {
|
||||
_subscription = stream.listen((event) => emit(AsyncValue.data(event)),
|
||||
// ignore: avoid_types_on_closure_parameters
|
||||
onError: (Object error, StackTrace stackTrace) {
|
||||
emit(AsyncValue.error(error, stackTrace));
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
await _subscription.cancel();
|
||||
await super.close();
|
||||
}
|
||||
|
||||
late final StreamSubscription<State> _subscription;
|
||||
}
|
|
@ -1,19 +1,12 @@
|
|||
export 'animations.dart';
|
||||
export 'async_transformer_cubit.dart';
|
||||
export 'bloc_map_cubit.dart';
|
||||
export 'bloc_tools.dart';
|
||||
export 'enter_password.dart';
|
||||
export 'enter_pin.dart';
|
||||
export 'future_cubit.dart';
|
||||
export 'loggy.dart';
|
||||
export 'phono_byte.dart';
|
||||
export 'responsive.dart';
|
||||
export 'scanner_error_widget.dart';
|
||||
export 'shared_preferences.dart';
|
||||
export 'state_follower.dart';
|
||||
export 'state_logger.dart';
|
||||
export 'stream_listenable.dart';
|
||||
export 'stream_wrapper_cubit.dart';
|
||||
export 'transformer_cubit.dart';
|
||||
export 'widget_helpers.dart';
|
||||
export 'window_control.dart';
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
|
||||
class TransformerCubit<T, S> extends Cubit<T> {
|
||||
TransformerCubit(this.input, {required this.transform})
|
||||
: super(transform(input.state)) {
|
||||
_subscription = input.stream.listen((event) => emit(transform(event)));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
await _subscription.cancel();
|
||||
await input.close();
|
||||
await super.close();
|
||||
}
|
||||
|
||||
Cubit<S> input;
|
||||
T Function(S) transform;
|
||||
late final StreamSubscription<S> _subscription;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue