import 'package:flutter/material.dart'; import 'package:test_app/data/repository/potter_repository.dart'; import 'package:test_app/domain/card.dart'; import 'package:test_app/domain/home.dart'; import 'package:test_app/utils/debounce.dart'; import 'package:test_app/view/details_page/details_page.dart'; import 'package:test_app/view/home_page/home_card.dart'; class MyHomePage extends StatefulWidget { const MyHomePage({super.key, required this.title}); final String title; @override State createState() => _MyHomePageState(); } class _MyHomePageState extends State { final Color _color = Colors.orangeAccent; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( backgroundColor: _color, title: Text(widget.title), ), body: const Body(), ); } } class Body extends StatefulWidget { const Body({super.key}); @override State createState() => _BodyState(); } class _BodyState extends State { final searchController = TextEditingController(); final scrollController = ScrollController(); final repository = PotterRepository(); int nextPage = 1; int currentPage = 0; List data = []; bool isLoading = false; @override void initState() { _loadData(); scrollController.addListener(_onNextPageListener); super.initState(); } void _setLoading(bool state) { setState(() { isLoading = state; }); } void _loadData() async { _setLoading(true); final result = await repository.loadData(q: searchController.text, page: nextPage); _setData(result); _setLoading(false); } void _setData(HomeData? result) { nextPage = result?.nextPage ?? 1; if (currentPage >= (result?.currentPage ?? 1)) { return; } currentPage = result?.currentPage ?? 1; setState(() { data.addAll(result?.data ?? []); }); } @override void dispose() { searchController.dispose(); scrollController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Column( children: [ Padding( padding: const EdgeInsets.all(16), child: SearchBar( leading: const Icon(Icons.search), trailing: [ IconButton( onPressed: () { searchController.clear(); data.clear(); currentPage = 0; nextPage = 1; _loadData(); }, icon: const Icon(Icons.clear)), ], controller: searchController, onChanged: (search) { Debounce.run(() async { _setLoading(true); data.clear(); currentPage = 0; nextPage = 1; final result = await repository.loadData(q: search); if (scrollController.hasClients) { scrollController.jumpTo(0); } _setData(result); _setLoading(false); }); }, ), ), Expanded( child: Stack( children: [ Positioned.fill( child: ListView.builder( controller: scrollController, padding: const EdgeInsets.only(right: 16, left: 16), itemCount: data.length, itemBuilder: (context, index) { final item = data[index]; return HomeCard.fromData( item, onLike: (String title, bool isLiked) => _showSnackBar(context, title, isLiked), onTap: () => _navigateToDetails(context, item), ); }), ), Align( alignment: data.isEmpty ? Alignment.topCenter : Alignment.bottomCenter, child: Visibility( visible: isLoading, child: const Padding( padding: EdgeInsets.only(bottom: 32, top: 16), child: CircularProgressIndicator(), ), ), ), ], ), ), ], ); } void _showSnackBar(BuildContext context, String title, bool isLiked) { WidgetsBinding.instance.addPostFrameCallback((_) { ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text( '$title is ${isLiked ? 'liked' : 'disliked'}!', style: Theme.of(context).textTheme.bodyLarge, ), backgroundColor: Colors.orangeAccent, duration: const Duration(seconds: 1), )); }); } void _navigateToDetails(BuildContext context, CardData data) { Navigator.push( context, MaterialPageRoute(builder: (context) => DetailsPage(data)), ); } void _onNextPageListener() { Debounce.run(() { if (scrollController.offset > scrollController.position.maxScrollExtent - 10) { _loadData(); } }); } }