nc-photos/app/lib/bloc/search_suggestion.dart

131 lines
3.9 KiB
Dart
Raw Normal View History

2022-07-25 07:51:52 +02:00
import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart';
2023-05-23 18:47:32 +02:00
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:logging/logging.dart';
2022-12-16 16:01:04 +01:00
import 'package:np_codegen/np_codegen.dart';
import 'package:np_collection/np_collection.dart';
import 'package:np_log/np_log.dart';
2023-08-25 18:37:17 +02:00
import 'package:np_string/np_string.dart';
2022-12-08 16:39:13 +01:00
import 'package:to_string/to_string.dart';
import 'package:woozy_search/woozy_search.dart';
2022-12-08 16:39:13 +01:00
part 'search_suggestion.g.dart';
abstract class SearchSuggestionBlocEvent<T> {
const SearchSuggestionBlocEvent();
}
2022-12-08 16:39:13 +01:00
@toString
class SearchSuggestionBlocUpdateItemsEvent<T>
extends SearchSuggestionBlocEvent<T> {
const SearchSuggestionBlocUpdateItemsEvent(this.items);
@override
2022-12-08 16:39:13 +01:00
String toString() => _$toString();
final List<T> items;
}
2022-12-08 16:39:13 +01:00
@toString
class SearchSuggestionBlocSearchEvent<T> extends SearchSuggestionBlocEvent<T> {
const SearchSuggestionBlocSearchEvent(this.phrase);
@override
2022-12-08 16:39:13 +01:00
String toString() => _$toString();
final CiString phrase;
}
2022-12-08 16:39:13 +01:00
@toString
abstract class SearchSuggestionBlocState<T> {
const SearchSuggestionBlocState(this.results);
@override
2022-12-08 16:39:13 +01:00
String toString() => _$toString();
final List<T> results;
}
class SearchSuggestionBlocInit<T> extends SearchSuggestionBlocState<T> {
const SearchSuggestionBlocInit() : super(const []);
}
class SearchSuggestionBlocLoading<T> extends SearchSuggestionBlocState<T> {
2024-05-28 17:10:33 +02:00
const SearchSuggestionBlocLoading(super.results);
}
class SearchSuggestionBlocSuccess<T> extends SearchSuggestionBlocState<T> {
2024-05-28 17:10:33 +02:00
const SearchSuggestionBlocSuccess(super.results);
}
2022-12-16 16:01:04 +01:00
@npLog
class SearchSuggestionBloc<T>
extends Bloc<SearchSuggestionBlocEvent, SearchSuggestionBlocState<T>> {
SearchSuggestionBloc({
required this.itemToKeywords,
2022-07-09 07:59:09 +02:00
}) : super(SearchSuggestionBlocInit<T>()) {
on<SearchSuggestionBlocEvent>(_onEvent);
}
2022-07-09 07:59:09 +02:00
Future<void> _onEvent(SearchSuggestionBlocEvent event,
Emitter<SearchSuggestionBlocState<T>> emit) async {
_log.info("[_onEvent] $event");
if (event is SearchSuggestionBlocSearchEvent) {
2022-07-09 07:59:09 +02:00
await _onEventSearch(event, emit);
} else if (event is SearchSuggestionBlocUpdateItemsEvent<T>) {
2022-07-09 07:59:09 +02:00
await _onEventUpdateItems(event, emit);
}
}
2022-07-09 07:59:09 +02:00
Future<void> _onEventSearch(SearchSuggestionBlocSearchEvent ev,
Emitter<SearchSuggestionBlocState<T>> emit) async {
emit(SearchSuggestionBlocLoading(state.results));
// doesn't work with upper case
final results = _search.search(ev.phrase.toCaseInsensitiveString());
if (isDevMode) {
final str = results.map((e) => "${e.score}: ${e.text}").join("\n");
_log.info("[_onEventSearch] Search '${ev.phrase}':\n$str");
}
final matches = results
.where((element) => element.score > 0)
.map((e) {
if (itemToKeywords(e.value as T)
.any((k) => k.startsWith(ev.phrase))) {
// prefer names that start exactly with the search phrase
2024-06-30 21:00:11 +02:00
return (score: e.score + 1, item: e.value as T);
} else {
2024-06-30 21:00:11 +02:00
return (score: e.score, item: e.value as T);
}
})
2024-06-30 21:00:11 +02:00
.sorted((a, b) => a.score.compareTo(b.score))
.reversed
.distinctIf(
2024-06-30 21:00:11 +02:00
(a, b) => identical(a.item, b.item),
(a) => a.item.hashCode,
)
2024-06-30 21:00:11 +02:00
.map((e) => e.item)
.toList();
2022-07-09 07:59:09 +02:00
emit(SearchSuggestionBlocSuccess(matches));
_lastSearch = ev;
}
2022-07-09 07:59:09 +02:00
Future<void> _onEventUpdateItems(SearchSuggestionBlocUpdateItemsEvent<T> ev,
Emitter<SearchSuggestionBlocState<T>> emit) async {
_search.setEntries([]);
for (final a in ev.items) {
for (final k in itemToKeywords(a)) {
_search.addEntry(k.toCaseInsensitiveString(), value: a);
}
}
if (_lastSearch != null) {
// search again
2022-07-09 07:59:09 +02:00
await _onEventSearch(_lastSearch!, emit);
}
}
final List<CiString> Function(T item) itemToKeywords;
final _search = Woozy(limit: 5);
SearchSuggestionBlocSearchEvent? _lastSearch;
}