import 'package:flutter/material.dart'; import 'package:flutter_android_app/components/extensions/context_x.dart'; import 'package:flutter_android_app/components/utils/debounce.dart'; import 'package:flutter_android_app/domain/models/card.dart'; import 'package:flutter_android_app/presentation/details_page/details_page.dart'; import 'package:flutter_android_app/presentation/home_page/bloc/bloc.dart'; import 'package:flutter_android_app/presentation/home_page/bloc/events.dart'; import 'package:flutter_android_app/presentation/home_page/bloc/state.dart'; import 'package:flutter_android_app/presentation/like_bloc/like_bloc.dart'; import 'package:flutter_android_app/presentation/like_bloc/like_state.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../common/svg_objects.dart'; import '../like_bloc/like_events.dart'; import '../settings_page/settings_page.dart'; part 'card.dart'; class MyHomePage extends StatelessWidget { const MyHomePage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( backgroundColor: Theme.of(context).colorScheme.inversePrimary, title: Text(context.locale.mainAppBarTitle), actions: [ Padding( padding: const EdgeInsets.only(right: 12.0), child: IconButton( icon: const Icon(Icons.settings), onPressed: () => Navigator.push(context, MaterialPageRoute( builder: (BuildContext context) => const SettingsPage(), )), ), ), ], ), body: const Body(), ); } } class Body extends StatefulWidget { const Body({super.key}); @override State createState() => _BodyState(); } class _BodyState extends State { final TextEditingController searchController = TextEditingController(); final ScrollController scrollController = ScrollController(); @override void initState() { SvgObjects.init(); WidgetsBinding.instance.addPostFrameCallback((_) { context.read().add(HomeLoadDataEvent(locale: context.locale)); context.read().add(const LoadLikesEvent()); }); scrollController.addListener(_onNextPageListener); super.initState(); } @override Widget build(BuildContext context) { return Padding( padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top), child: Column( children: [ Row( children: [ Expanded( flex: 4, child: Padding( padding: const EdgeInsets.all(12), child: SearchBar( controller: searchController, onChanged: (search) { Debounce.run(() => context.read().add(HomeLoadDataEvent(search: search, locale: context.locale))); }, leading: const Icon(Icons.search), trailing: [ IconButton( icon: const Icon(Icons.close), onPressed: () { searchController.clear(); context.read().add(HomeLoadDataEvent(locale: context.locale)); }, ), ], hintText: context.locale.searchHint, elevation: const WidgetStatePropertyAll(0.0), padding: const WidgetStatePropertyAll(EdgeInsets.only(left: 18, right: 10)), backgroundColor: WidgetStatePropertyAll(Theme.of(context).colorScheme.secondaryContainer), ), ), ), ], ), BlocBuilder( builder: (context, state) => state.error != null ? Text( state.error ?? '', style: Theme.of(context).textTheme.headlineSmall?.copyWith(color: Colors.red), ) : state.isLoading ? const CircularProgressIndicator() : BlocBuilder( builder: (context, likeState) => Expanded( child: RefreshIndicator( onRefresh: _onRefresh, child: ListView.builder( controller: scrollController, padding: EdgeInsets.zero, itemCount: state.data?.data?.length ?? 0, itemBuilder: (context, index) { final data = state.data?.data?[index]; return data != null ? _Card.fromData( data, isLiked: likeState.likedIds?.contains(data.id) == true, onLike: _onLike, onTap: () => _navToDetails(context, data), ) : const SizedBox.shrink(); }, ), ), ), ) ), BlocBuilder( builder: (context, state) => state.isPaginationLoading ? const CircularProgressIndicator() : const SizedBox.shrink(), ), ], ) ); } @override void dispose() { searchController.dispose(); scrollController.dispose(); super.dispose(); } void _showSnackBar(BuildContext context, String title, bool isLiked) { WidgetsBinding.instance.addPostFrameCallback((_) { ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text( '$title ${isLiked ? context.locale.addedToFavourite : context.locale.removedFromFavourite}', style: Theme.of(context).textTheme.bodyLarge ), backgroundColor: Colors.deepPurple.shade200, duration: const Duration(seconds: 2), )); }); } void _navToDetails(BuildContext context, CardData data) { Navigator.push(context, MaterialPageRoute( builder: (context) => DetailsPage(data)), ); } Future _onRefresh() { context.read().add(HomeLoadDataEvent(search: searchController.text, locale: context.locale)); return Future.value(null); } void _onNextPageListener() { if (scrollController.offset >= scrollController.position.maxScrollExtent) { final bloc = context.read(); if (!bloc.state.isPaginationLoading) { bloc.add(HomeLoadDataEvent( search: searchController.text, nextPage: bloc.state.data?.nextPage, locale: context.locale, )); } } } void _onLike(String? id, String title, bool isLiked) { if (id != null) { context.read().add(ChangeLikeEvent(id)); _showSnackBar(context, title, !isLiked); } } }