import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:laba1/data/repositories/potter_repository.dart'; import 'package:laba1/presentation/details_page/details_page.dart'; import '../../components/utils/debounce.dart'; import '../../data/repositories/mock_repository.dart'; import '../../domain/models/card.dart'; import '../dialogs/show_dialog.dart'; import 'bloc/bloc.dart'; import 'bloc/events.dart'; import 'bloc/state.dart'; part 'card.dart'; class MyHomePage extends StatefulWidget { const MyHomePage({super.key, required this.title}); final String title; @override State createState() => _MyHomePageState(); } class _MyHomePageState extends State { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), backgroundColor: Colors.amber, ), backgroundColor: Colors.yellow, body: const _Body()); } } class _Body extends StatefulWidget { const _Body({Key? key}) : super(key: key); @override State<_Body> createState() => _BodyState(); } class _BodyState extends State<_Body> { final searchController = TextEditingController(); final scrollController = ScrollController(); @override void initState() { WidgetsBinding.instance.addPostFrameCallback((_) { context.read().add(const HomeLoadDataEvent()); }); scrollController.addListener(_onNextPageListener); super.initState(); } void _onNextPageListener() { if (scrollController.offset >= scrollController.position.maxScrollExtent) { // preventing multiple pagination request on multiple swipes final bloc = context.read(); if (!bloc.state.isPaginationLoading) { bloc.add(HomeLoadDataEvent( search: searchController.text, nextPage: bloc.state.data?.nextPage, )); } } } @override void dispose() { searchController.dispose(); scrollController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Center( child: Stack( children: [ BlocBuilder( builder: (context, state) => state.error != null ? Text( state.error ?? '', style: Theme.of(context) .textTheme .headlineSmall ?.copyWith(color: Colors.red), ) : state.isLoading ? Center(child: CircularProgressIndicator()) : Expanded( child: RefreshIndicator( onRefresh: _onRefresh, child: ListView.builder( controller: scrollController, padding: EdgeInsets.zero, itemCount: (state.data?.data?.length ?? 0) + 1, itemBuilder: (context, index) { if (index == 0) { return Padding( padding: const EdgeInsets.only( left: 8.0, right: 8), child: CupertinoSearchTextField( borderRadius: BorderRadius.only( bottomLeft: Radius.circular(20), bottomRight: Radius.circular(20), ), backgroundColor: Colors.amberAccent, )); } final data = state.data?.data?[index - 1]; return data != null ? _Card.fromData( data, onLike: (title, isLiked) => _showSnackBar( context, title, isLiked), onTap: () => _navToDetails(context, data), ) : const SizedBox.expand(); }, ), ), ), ), Padding( padding: const EdgeInsets.only(left: 8.0, right: 8), child: CupertinoSearchTextField( borderRadius: BorderRadius.only( bottomLeft: Radius.circular(20), bottomRight: Radius.circular(20), ), backgroundColor: Colors.amberAccent, controller: searchController, onChanged: (search) { Debounce.run(() => context .read() .add(HomeLoadDataEvent(search: search))); }, ), ), Align( alignment: Alignment.bottomCenter, child: Padding( padding: const EdgeInsets.all(8.0), child: SizedBox( height: 50, width: 50, child: BlocBuilder( builder: (context, state) => state.isPaginationLoading ? const CircularProgressIndicator() : const SizedBox.shrink(), ), ), ), ), ], ), ); } Future _onRefresh() { context .read() .add(HomeLoadDataEvent(search: searchController.text)); return Future.value(null); } void _navToDetails(BuildContext context, CardData data) { Navigator.push( context, CupertinoPageRoute(builder: (context) => DetailsPage(data)), ); } void _showSnackBar(BuildContext context, String title, bool isLiked) { WidgetsBinding.instance.addPostFrameCallback((_) { ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text( '$title ${isLiked ? 'liked' : 'unliked'}', style: Theme.of(context).textTheme.bodyLarge, ), backgroundColor: Colors.orangeAccent, duration: const Duration(seconds: 1), )); }); } }