build method
- BuildContext context
override
Builds the main UI for the mosquito gallery screen.
Creates a responsive layout featuring:
- App bar with gallery title
- Search text field for species filtering
- Grid view of mosquito species cards
- Loading, error, and empty state handling
The layout uses Consumer widgets to react to state changes in the MosquitoGalleryViewModel and updates the UI accordingly. The grid layout adapts to different screen sizes using a 2-column configuration.
context The build context for accessing theme and localization data.
Returns a Widget representing the complete mosquito gallery screen UI.
Implementation
@override
Widget build(BuildContext context) {
final localizations = AppLocalizations.of(context)!;
final searchController = TextEditingController();
return ChangeNotifierProvider<MosquitoGalleryViewModel>.value(
value: locator<MosquitoGalleryViewModel>()
..loadMosquitoSpecies(localizations),
child: Scaffold(
appBar: AppBar(
title: Text(localizations.mosquitoGalleryScreenTitle),
elevation: 0,
centerTitle: true,
),
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(16.0),
child: Consumer<MosquitoGalleryViewModel>(
builder: (context, viewModel, child) {
return TextField(
controller: searchController,
decoration: InputDecoration(
hintText: localizations.searchMosquitoSpeciesHint,
prefixIcon: const Icon(Icons.search),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: Colors.grey.shade300),
),
filled: true,
fillColor: Colors.grey.shade100,
contentPadding: const EdgeInsets.symmetric(vertical: 0),
suffixIcon: searchController.text.isNotEmpty
? IconButton(
icon: const Icon(Icons.clear),
onPressed: () {
searchController.clear();
viewModel.updateSearchQuery('');
},
)
: null,
),
onChanged: (value) {
viewModel.updateSearchQuery(value);
},
);
},
),
),
Expanded(
child: Consumer<MosquitoGalleryViewModel>(
builder: (context, viewModel, child) {
if (viewModel.isLoading) {
return const Center(child: CircularProgressIndicator());
}
if (viewModel.state == GalleryState.error) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icons.error_outline,
color: Colors.red,
size: 48,
),
const SizedBox(height: 16),
Text(
viewModel.errorMessage ??
localizations.anErrorOccurred,
textAlign: TextAlign.center,
style: const TextStyle(color: Colors.red),
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () {
viewModel.loadMosquitoSpecies(localizations);
},
child: Text(localizations.retryButton),
),
],
),
);
}
final filteredSpecies = viewModel.filteredSpecies;
if (filteredSpecies.isEmpty) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.search_off,
size: 64,
color: Colors.grey.shade400,
),
const SizedBox(height: 16),
Text(
localizations.noMosquitoSpeciesFound,
style: TextStyle(
fontSize: 18,
color: Colors.grey.shade600,
),
),
if (searchController.text.isNotEmpty)
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(
localizations.tryDifferentSearchTerm,
style: TextStyle(color: Colors.grey.shade500),
),
),
],
),
);
}
return GridView.builder(
padding: const EdgeInsets.all(16),
gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 0.75,
crossAxisSpacing: 16,
mainAxisSpacing: 16,
),
itemCount: filteredSpecies.length,
itemBuilder: (context, index) {
final species = filteredSpecies[index];
return _buildMosquitoCard(
context, species, localizations);
},
);
},
),
),
],
),
),
);
}