import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../../../core/network/api_client.dart'; import '../../../core/theme/app_theme.dart'; import '../../../core/widgets/asset_logo_widget.dart'; import '../../../core/widgets/shimmer_loading.dart'; import '../../../shared/widgets/favorite_star_button.dart'; import '../../asset_detail/views/asset_detail_screen.dart'; import '../bloc/search_bloc.dart'; import '../repositories/search_repository.dart'; /// Focused Spotlight Asset Search Dialog with reactive Favorite Star Button, Hero transitions, and Shimmer loading states. /// /// By default, tapping a result navigates straight to [AssetDetailScreen] (the main search-feature use case). /// Pass [onAssetSelected] to repurpose this as a reusable asset PICKER instead - e.g. the simulation screen's /// ISIN input - in which case a tap pops the dialog and invokes the callback with the picked ISIN/name instead /// of navigating anywhere. class AssetSearchDialog extends StatelessWidget { final ApiClient apiClient; final void Function(String isin, String name)? onAssetSelected; const AssetSearchDialog({ super.key, required this.apiClient, this.onAssetSelected, }); @override Widget build(BuildContext context) { return BlocProvider( create: (context) => SearchBloc( repository: SearchRepository(apiClient: apiClient), )..add(const SearchQueryChanged('')), child: _AssetSearchDialogContent(apiClient: apiClient, onAssetSelected: onAssetSelected), ); } } class _AssetSearchDialogContent extends StatefulWidget { final ApiClient apiClient; final void Function(String isin, String name)? onAssetSelected; const _AssetSearchDialogContent({required this.apiClient, this.onAssetSelected}); @override State<_AssetSearchDialogContent> createState() => _AssetSearchDialogContentState(); } class _AssetSearchDialogContentState extends State<_AssetSearchDialogContent> { final _searchController = TextEditingController(); @override Widget build(BuildContext context) { final activeTheme = AppTheme.activePreset; return Dialog( insetPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 40), backgroundColor: activeTheme.cardSurface, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(activeTheme.borderRadius + 2), side: BorderSide(color: activeTheme.glassBorder, width: 1), ), child: Container( width: 600, height: 520, padding: const EdgeInsets.all(20), child: Column( children: [ BlocBuilder( builder: (context, state) { final isLoading = state is SearchLoading; return TextField( controller: _searchController, autofocus: true, decoration: InputDecoration( hintText: 'Aktienname oder ISIN suchen...', prefixIcon: Icon(Icons.search, color: activeTheme.primaryColor), suffixIcon: Row( mainAxisSize: MainAxisSize.min, children: [ if (isLoading) Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0), child: SizedBox( width: 18, height: 18, child: CircularProgressIndicator(strokeWidth: 2, color: activeTheme.primaryColor), ), ), IconButton( icon: const Icon(Icons.close), onPressed: () => Navigator.pop(context), ), ], ), ), onChanged: (query) { context.read().add(SearchQueryChanged(query)); }, ); }, ), const SizedBox(height: 16), Expanded( child: BlocBuilder( builder: (context, state) { if (state is SearchLoading) { return ListView.builder( itemCount: 5, itemBuilder: (context, index) => Padding( padding: const EdgeInsets.symmetric(vertical: 8), child: Row( children: [ ShimmerLoading(width: 36, height: 36, borderRadius: 10), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ ShimmerLoading(width: 140, height: 14), const SizedBox(height: 6), ShimmerLoading(width: 80, height: 10), ], ), ), ], ), ), ); } if (state is SearchError) { return Center( child: Text(state.message, style: TextStyle(color: activeTheme.textMuted, fontSize: 13)), ); } if (state is SearchLoaded) { final results = state.results; if (results.isEmpty) { return Center( child: Text('Keine Wertpapiere gefunden', style: TextStyle(color: activeTheme.textMuted, fontSize: 13)), ); } return ListView.builder( itemCount: results.length, itemBuilder: (context, index) { final item = results[index]; final assetName = item.displayName; final isin = item.isin; final targetId = isin.isNotEmpty ? isin : assetName; return ListTile( leading: AssetLogoWidget( symbolOrName: targetId, imageUrl: item.image, size: 36, ), title: Text(assetName, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), subtitle: isin.isNotEmpty ? Text('ISIN: $isin', style: TextStyle(color: activeTheme.textMuted, fontSize: 11)) : null, trailing: SizedBox( width: 40, height: 40, child: FavoriteStarButton( identifier: targetId, symbol: assetName, name: assetName, ), ), onTap: () { Navigator.pop(context); if (widget.onAssetSelected != null) { widget.onAssetSelected!(isin, assetName); return; } Navigator.push( context, MaterialPageRoute( builder: (_) => AssetDetailScreen( isin: isin, name: assetName, apiClient: widget.apiClient, ), ), ); }, ); }, ); } return const SizedBox.shrink(); }, ), ), ], ), ), ); } }