first
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
/// Search tab: query field plus a [ListView.builder] of matching items.
|
||||
library;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../models/item.dart';
|
||||
import '../providers/item_provider.dart';
|
||||
import '../widgets/empty_state.dart';
|
||||
import '../widgets/item_card.dart';
|
||||
import '../widgets/responsive_body.dart';
|
||||
|
||||
class SearchScreen extends StatefulWidget {
|
||||
const SearchScreen({super.key});
|
||||
|
||||
@override
|
||||
State<SearchScreen> createState() => _SearchScreenState();
|
||||
}
|
||||
|
||||
class _SearchScreenState extends State<SearchScreen> {
|
||||
final _query = TextEditingController();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_query.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final results = context.watch<ItemController>().search(_query.text);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Search')),
|
||||
body: ResponsiveBody(
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(height: 8),
|
||||
TextField(
|
||||
controller: _query,
|
||||
textInputAction: TextInputAction.search,
|
||||
onChanged: (_) => setState(() {}),
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Title, summary, or category',
|
||||
prefixIcon: const Icon(Icons.search),
|
||||
suffixIcon: _query.text.isEmpty
|
||||
? null
|
||||
: IconButton(
|
||||
tooltip: 'Clear',
|
||||
onPressed: () {
|
||||
_query.clear();
|
||||
setState(() {});
|
||||
},
|
||||
icon: const Icon(Icons.close),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
'${results.length} result${results.length == 1 ? '' : 's'}',
|
||||
style: Theme.of(context).textTheme.labelMedium,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Expanded(child: _ResultsList(results: results, query: _query.text)),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ResultsList extends StatelessWidget {
|
||||
const _ResultsList({required this.results, required this.query});
|
||||
|
||||
final List<Item> results;
|
||||
final String query;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (results.isEmpty) {
|
||||
return EmptyState(
|
||||
icon: Icons.search_off,
|
||||
title: query.trim().isEmpty ? 'Start typing' : 'No matches',
|
||||
message: query.trim().isEmpty
|
||||
? 'Search the catalog by title, summary, or category.'
|
||||
: 'Try a different keyword.',
|
||||
);
|
||||
}
|
||||
|
||||
return ListView.builder(
|
||||
padding: const EdgeInsets.only(bottom: 24),
|
||||
itemCount: results.length,
|
||||
itemBuilder: (context, index) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: ItemCard(item: results[index], heroScope: 'search'),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user