FilterConditionsBloc<T extends ItemSourceState<ItemClassWithAccessor>> constructor

FilterConditionsBloc<T extends ItemSourceState<ItemClassWithAccessor>>({
  1. required List<String> filterProperties,
  2. required Bloc sourceBloc,
})

Attaches to the provided _sourceBloc in order to dynamically generate groupings of available conditions that correspond to the provided _filterProperties.

Additionally exposes events to add or remove active conditions from the state.

There should be no need to ever manually construct a FilterConditionsBloc. It should, instead, be retrieved from within the ListManager in order to construct whatever filtering UI you desire.

Implementation

FilterConditionsBloc({
  required List<String> filterProperties,
  required Bloc sourceBloc,
})  : _filterProperties = filterProperties,
      _sourceBloc = sourceBloc,
      super(const ConditionsUninitialized()) {
  _sourceSubscription = _sourceBloc.stream.listen((sourceState) {
    if (sourceState is! T) {
      return;
    }

    final modifiedFilterConditions = Set.from(_filterProperties);
    final booleanProperties = <String>{};

    final availableConditions = _generateFilterPropertiesMap();
    final availableConditionKeys = <String>{};

    for (final item in sourceState.items) {
      for (final property in modifiedFilterConditions) {
        final value = item[property];

        if (value is bool) {
          booleanProperties.add(property);

          availableConditions[property]!.add('True');
          availableConditions[property]!.add('False');

          availableConditionKeys.add(generateConditionKey(property, 'True'));
          availableConditionKeys.add(generateConditionKey(property, 'False'));
        }

        if (value is String && value.isNotEmpty) {
          final conditionKey = generateConditionKey(property, value);

          availableConditions[property]!.add(value);
          availableConditionKeys.add(conditionKey);
        }
      }

      // We don't want to repeatedly loop through boolean properties that
      // have already been parsed. Nor do we want to sort them (below).
      if (booleanProperties.isNotEmpty) {
        modifiedFilterConditions.removeWhere(booleanProperties.contains);
        booleanProperties.clear();
      }
    }

    final currentState = state;
    final activeAndConditions = currentState is ConditionsInitialized
        ? currentState.activeAndConditions
        : <String>{};
    final activeOrConditions = currentState is ConditionsInitialized
        ? currentState.activeOrConditions
        : <String>{};

    for (final property in modifiedFilterConditions) {
      // Ensure only unique entries are present and that entries are sorted.
      // Removing duplicates before sorting will save a few cycles.
      availableConditions[property] =
          availableConditions[property]!.toSet().toList()..sort();
    }

    _conditionKeyTracker
        .removeWhere((key, _) => !availableConditionKeys.contains(key));

    add(RefreshConditions(
      activeAndConditions:
          activeAndConditions.intersection(availableConditionKeys),
      activeOrConditions:
          activeOrConditions.intersection(availableConditionKeys),
      availableConditions: availableConditions,
    ));
  });

  on<RefreshConditions>((event, emit) => emit(ConditionsInitialized(
        activeAndConditions: event.activeAndConditions,
        activeOrConditions: event.activeOrConditions,
        availableConditions: event.availableConditions,
      )));

  on<AddCondition>(
      (event, emit) => emit(_addConditionToActiveConditions(event)));
  on<RemoveCondition>(
      (event, emit) => emit(_removeConditionFromActiveConditions(event)));
}