import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:logging/logging.dart'; import 'package:nc_photos/account.dart'; import 'package:nc_photos/bloc/home_search_suggestion.dart'; import 'package:nc_photos/controller/account_controller.dart'; import 'package:nc_photos/entity/collection.dart'; import 'package:nc_photos/entity/collection/builder.dart'; import 'package:nc_photos/entity/person.dart'; import 'package:nc_photos/entity/tag.dart'; import 'package:nc_photos/exception_util.dart' as exception_util; import 'package:nc_photos/k.dart' as k; import 'package:nc_photos/object_extension.dart'; import 'package:nc_photos/snack_bar_manager.dart'; import 'package:nc_photos/use_case/list_location_group.dart'; import 'package:nc_photos/widget/collection_browser.dart'; import 'package:nc_photos/widget/page_visibility_mixin.dart'; import 'package:np_codegen/np_codegen.dart'; import 'package:np_common/ci_string.dart'; part 'home_search_suggestion.g.dart'; class HomeSearchSuggestionController { void search(String phrase) { _bloc?.add(HomeSearchSuggestionBlocSearch(phrase.toCi())); } HomeSearchSuggestionBloc? _bloc; } class HomeSearchSuggestion extends StatefulWidget { const HomeSearchSuggestion({ Key? key, required this.account, required this.controller, }) : super(key: key); @override createState() => _HomeSearchSuggestionState(); final Account account; final HomeSearchSuggestionController controller; } @npLog class _HomeSearchSuggestionState extends State with RouteAware, PageVisibilityMixin { @override initState() { super.initState(); _initBloc(); } @override build(BuildContext context) { return BlocListener( bloc: _bloc, listener: (context, state) => _onStateChange(context, state), child: BlocBuilder( bloc: _bloc, builder: (context, state) => Theme( data: Theme.of(context).run((t) { return t.copyWith( listTileTheme: ListTileThemeData( iconColor: t.colorScheme.onBackground, textColor: t.colorScheme.onBackground, ), ); }), child: _buildContent(context, state), ), ), ); } void _initBloc() { _bloc = (widget.controller._bloc ??= HomeSearchSuggestionBloc( widget.account, context.read().collectionsController, context.read().serverController, context.read().accountPrefController, )); if (_bloc.state is! HomeSearchSuggestionBlocInit) { // process the current state WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted) { setState(() { _onStateChange(context, _bloc.state); }); } }); } } Widget _buildContent( BuildContext context, HomeSearchSuggestionBlocState state) { if (_items.isEmpty) { return const SizedBox.shrink(); } else { return Material( type: MaterialType.transparency, child: Column( mainAxisSize: MainAxisSize.min, children: _items.map((e) => e.buildWidget(context)).toList(), ), ); } } void _onStateChange( BuildContext context, HomeSearchSuggestionBlocState state) { if (state is HomeSearchSuggestionBlocInit) { _items = []; } else if (state is HomeSearchSuggestionBlocSuccess || state is HomeSearchSuggestionBlocLoading) { _transformItems(state.results); } else if (state is HomeSearchSuggestionBlocFailure) { _transformItems(state.results); if (isPageVisible()) { SnackBarManager().showSnackBar(SnackBar( content: Text(exception_util.toUserString(state.exception)), duration: k.snackBarDurationNormal, )); } } } void _onCollectionPressed(_CollectionListItem item) { if (mounted) { Navigator.of(context).pushNamed( CollectionBrowser.routeName, arguments: CollectionBrowserArguments(item.collection), ); } } void _onTagPressed(_TagListItem item) { if (mounted) { Navigator.of(context).pushNamed( CollectionBrowser.routeName, arguments: CollectionBrowserArguments( CollectionBuilder.byTags(widget.account, [item.tag]), ), ); } } void _onPersonPressed(_PersonListItem item) { if (mounted) { Navigator.pushNamed( context, CollectionBrowser.routeName, arguments: CollectionBrowserArguments( CollectionBuilder.byPerson(widget.account, item.person), ), ); } } void _onLocationPressed(_LocationListItem item) { if (mounted) { Navigator.pushNamed( context, CollectionBrowser.routeName, arguments: CollectionBrowserArguments( CollectionBuilder.byLocationGroup(widget.account, item.location), ), ); } } void _transformItems(List results) { final items = () sync* { for (final r in results) { if (r is HomeSearchCollectionResult) { yield _CollectionListItem(r.collection, onTap: _onCollectionPressed); } else if (r is HomeSearchTagResult) { yield _TagListItem(r.tag, onTap: _onTagPressed); } else if (r is HomeSearchPersonResult) { yield _PersonListItem(r.person, onTap: _onPersonPressed); } else if (r is HomeSearchLocationResult) { yield _LocationListItem(r.location, onTap: _onLocationPressed); } else { _log.warning("[_transformItems] Unknown type: ${r.runtimeType}"); } } }() .toList(); _items = items; } late final HomeSearchSuggestionBloc _bloc; var _items = <_ListItem>[]; } abstract class _ListItem { Widget buildWidget(BuildContext context); } class _CollectionListItem implements _ListItem { const _CollectionListItem( this.collection, { this.onTap, }); @override Widget buildWidget(BuildContext context) => ListTile( leading: const Icon(Icons.photo_album_outlined), title: Text(collection.name), onTap: onTap == null ? null : () => onTap!(this), ); final Collection collection; final void Function(_CollectionListItem item)? onTap; } class _TagListItem implements _ListItem { const _TagListItem( this.tag, { this.onTap, }); @override buildWidget(BuildContext context) => ListTile( leading: const Icon(Icons.local_offer_outlined), title: Text(tag.displayName), onTap: onTap == null ? null : () => onTap!(this), ); final Tag tag; final void Function(_TagListItem)? onTap; } class _PersonListItem implements _ListItem { const _PersonListItem( this.person, { this.onTap, }); @override buildWidget(BuildContext context) => ListTile( leading: const Icon(Icons.person_outline), title: Text(person.name), onTap: onTap == null ? null : () => onTap!(this), ); final Person person; final void Function(_PersonListItem)? onTap; } class _LocationListItem implements _ListItem { const _LocationListItem( this.location, { this.onTap, }); @override buildWidget(BuildContext context) => ListTile( leading: const Icon(Icons.location_on_outlined), title: Text(location.place), onTap: onTap == null ? null : () => onTap!(this), ); final LocationGroup location; final void Function(_LocationListItem)? onTap; }