import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_project/components/extensions/context_x.dart'; import 'package:flutter_project/components/locale/l10n/app_localizations.dart'; import 'package:flutter_project/components/utils/debounce.dart'; import 'package:flutter_project/domain/models/card.dart'; import 'package:flutter_project/views/common/svg_objects.dart'; import 'package:flutter_project/views/details_page/details_page.dart'; import 'package:flutter_project/views/home_page/home_bloc/events.dart'; import 'package:flutter_project/views/home_page/home_bloc/state.dart'; import 'package:flutter_project/views/like_bloc/like_events.dart'; import 'package:flutter_project/views/like_bloc/like_state.dart'; import 'package:flutter_project/views/locale_bloc/locale_bloc.dart'; import 'package:flutter_project/views/locale_bloc/locale_events.dart'; import 'package:flutter_project/views/locale_bloc/locale_state.dart'; import '../like_bloc/like_bloc.dart'; import 'home_bloc/bloc.dart'; part 'card.dart'; class HomePage extends StatefulWidget { const HomePage({super.key}); @override State createState() => _HomePageState(); } class _HomePageState extends State { final body = Body(); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( backgroundColor: Theme.of(context).colorScheme.inversePrimary, title: Center( child: Text( context.locale.appBarTitle, ), ), ), body: body); } } class Body extends StatefulWidget { const Body({super.key}); @override State createState() => _BodyState(); } class _BodyState extends State { final scrollController = ScrollController(); final searchController = TextEditingController(); bool isButtonToTopShown = false; @override void initState() { SvgObjects.init(); WidgetsBinding.instance.addPostFrameCallback((_) { context.read().add(const HomeLoadDataEvent()); context.read().add(const LoadLikesEvent()); }); scrollController.addListener(_viewListScrollListener); super.initState(); } @override void dispose() { scrollController.dispose(); searchController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Stack(children: [ Column( children: [ Row( children: [ Expanded( child: Padding( padding: EdgeInsets.only(left: 16, top: 10, bottom: 10), child: Center( child: CupertinoSearchTextField( placeholder: context.locale.search, controller: searchController, onChanged: (search) { context .read() .add(HomeShowButtonToTopEvent(isShown: false)); Debounce.run(() => context .read() .add(HomeLoadDataEvent(search: search))); }, ), ), ), ), GestureDetector( onTap: () => context.read().add(const ChangeLocaleEvent()), child: Padding( padding: const EdgeInsets.all(16.0), child: BlocBuilder( builder: (context, state) { return AnimatedSwitcher( duration: const Duration(milliseconds: 250), child: state.currentLocale.languageCode == 'ru' ? const SvgRu(key: ValueKey(1)) : const SvgUs(key: ValueKey(0))); }), ), ) ], ), BlocConsumer( listener: (context, state) { if (state.error != null) { _onErrorShowDialog(context, state); } }, builder: (context, state) => state.isLoading ? const CircularProgressIndicator() : BlocBuilder( builder: (context, likeState) => Expanded( child: RefreshIndicator( onRefresh: _onRefresh, child: ListView.builder( controller: scrollController, padding: EdgeInsets.only(bottom: 10), itemCount: state.data?.data?.length ?? 0, itemBuilder: (context, index) { final data = state.data?.data?[index]; return data != null ? _Card.withData(context.locale, 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()) ], ), BlocBuilder( builder: (context, state) => AnimatedSwitcher( duration: Duration(milliseconds: 250), child: state.isButtonToTopShown ? Container( key: ValueKey(1), alignment: Alignment.topRight, padding: EdgeInsets.only(right: 16, top: 72), child: FloatingActionButton( onPressed: _goToTop, child: Icon(Icons.arrow_upward_rounded), )) : SizedBox.shrink(key: ValueKey(0))), ) ]); } void _onLike(String? id, String title, bool isLiked) { if (id != null) { context.read().add(ChangeLikeEvent(id)); _showSnackBar(context, !isLiked, title); } } void _onErrorShowDialog(BuildContext context, HomeState state) { showCupertinoDialog( context: context, builder: (_) => CupertinoAlertDialog( title: Text(context.locale.errorOccured), content: Text(state.error ?? context.locale.noErrorMsg), actions: [ CupertinoDialogAction( onPressed: () { Navigator.of(context).pop(); context .read() .add(HomeLoadDataEvent(hasError: true)); }, child: Text(context.locale.retry)) ], )); } void _goToTop() { scrollController.animateTo( 0, duration: Duration(milliseconds: 300), curve: Curves.easeInOut, ); } void _viewListScrollListener() { if (!context.read().state.isButtonToTopShown) { if (scrollController.offset >= 400) { context.read().add(HomeShowButtonToTopEvent(isShown: true)); } } else { if (scrollController.offset < 400) { context.read().add(HomeShowButtonToTopEvent(isShown: false)); } } if (scrollController.offset >= scrollController.position.maxScrollExtent) { final bloc = context.read(); if (!bloc.state.isPaginationLoading && bloc.state.data?.nextPage != null) { bloc.add(HomeLoadDataEvent( search: searchController.text, nextPage: bloc.state.data?.nextPage)); } } } Future _onRefresh() { context .read() .add(HomeLoadDataEvent(search: searchController.text)); return Future.value(null); } void _showSnackBar(BuildContext context, bool isLiked, String title) { WidgetsBinding.instance.addPostFrameCallback((_) { ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text( "${isLiked ? context.locale.liked : context.locale.unliked} $title", style: Theme.of(context).textTheme.bodyLarge, ), backgroundColor: Theme.of(context).colorScheme.inversePrimary, duration: const Duration(milliseconds: 1200), )); }); } void _navToDetails(BuildContext context, CardData d) { Navigator.push( context, CupertinoPageRoute( builder: (context) => DetailsPage(context.locale, d))); } }