feat: integrate persona D (recycling guide) and routes modules
This commit is contained in:
@@ -0,0 +1,164 @@
|
||||
// lib/features/recycling_guide/presentation/screens/recycling_guide_screen.dart
|
||||
// Pantalla principal — Persona C la agrega al router así:
|
||||
// GoRoute(path: '/guia', builder: (_, __) => const RecyclingGuideScreen())
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../providers/recycling_provider.dart';
|
||||
import '../widgets/category_card.dart';
|
||||
import '../widgets/search_result_tile.dart';
|
||||
import 'category_detail_screen.dart';
|
||||
|
||||
class RecyclingGuideScreen extends ConsumerStatefulWidget {
|
||||
const RecyclingGuideScreen({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<RecyclingGuideScreen> createState() =>
|
||||
_RecyclingGuideScreenState();
|
||||
}
|
||||
|
||||
class _RecyclingGuideScreenState extends ConsumerState<RecyclingGuideScreen> {
|
||||
final _searchCtrl = TextEditingController();
|
||||
bool _buscando = false;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_searchCtrl.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _onSearchChanged(String query) {
|
||||
setState(() => _buscando = query.trim().isNotEmpty);
|
||||
ref.read(recyclingSearchProvider.notifier).buscar(query);
|
||||
}
|
||||
|
||||
void _limpiarBusqueda() {
|
||||
_searchCtrl.clear();
|
||||
setState(() => _buscando = false);
|
||||
ref.read(recyclingSearchProvider.notifier).limpiar();
|
||||
FocusScope.of(context).unfocus();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Guía de separación'),
|
||||
actions: [
|
||||
// Badge offline — refuerza que funciona sin internet
|
||||
Container(
|
||||
margin: const EdgeInsets.only(right: 12),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.green.withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: const Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Icons.offline_bolt, size: 14, color: Colors.white),
|
||||
SizedBox(width: 4),
|
||||
Text(
|
||||
'sin internet',
|
||||
style: TextStyle(fontSize: 11, color: Colors.white),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
// ── Buscador ────────────────────────────────────────────
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 16, 16, 8),
|
||||
child: TextField(
|
||||
controller: _searchCtrl,
|
||||
onChanged: _onSearchChanged,
|
||||
decoration: InputDecoration(
|
||||
hintText: '¿Dónde va el aceite? ¿y la pila?',
|
||||
prefixIcon: const Icon(Icons.search),
|
||||
suffixIcon: _buscando
|
||||
? IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: _limpiarBusqueda,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// ── Contenido dinámico ──────────────────────────────────
|
||||
Expanded(
|
||||
child: _buscando
|
||||
? _SearchResults()
|
||||
: _CategoryList(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Lista de categorías ──────────────────────────────────────────────
|
||||
|
||||
class _CategoryList extends ConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final async = ref.watch(recyclingCategoriesProvider);
|
||||
|
||||
return async.when(
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
error: (e, _) => Center(child: Text('Error: $e')),
|
||||
data: (categorias) => ListView.separated(
|
||||
padding: const EdgeInsets.fromLTRB(16, 8, 16, 24),
|
||||
itemCount: categorias.length,
|
||||
separatorBuilder: (_, __) => const SizedBox(height: 12),
|
||||
itemBuilder: (context, i) => CategoryCard(
|
||||
categoria: categorias[i],
|
||||
onTap: () => Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) =>
|
||||
CategoryDetailScreen(categoria: categorias[i]),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Resultados de búsqueda ───────────────────────────────────────────
|
||||
|
||||
class _SearchResults extends ConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final estado = ref.watch(recyclingSearchProvider);
|
||||
|
||||
return switch (estado) {
|
||||
_Idle() => const SizedBox.shrink(),
|
||||
_Loading() => const Center(child: CircularProgressIndicator()),
|
||||
_Done(results: final r) when r.isEmpty => Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Icons.search_off, size: 48, color: Colors.grey[400]),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
'No encontramos ese residuo.',
|
||||
style: TextStyle(color: Colors.grey[600]),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
_Done(results: final r) => ListView.builder(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
itemCount: r.length,
|
||||
itemBuilder: (_, i) => SearchResultTile(resultado: r[i]),
|
||||
),
|
||||
_ => const SizedBox.shrink(),
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user