import 'dart:io'; import 'dart:math'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:pmu_labworks/components/extensions/context_x.dart'; import 'package:pmu_labworks/components/utils/debounce.dart'; import 'package:pmu_labworks/domain/models/comment.dart'; import 'package:pmu_labworks/domain/models/user.dart'; import 'package:pmu_labworks/view/common/svg_objects.dart'; import 'package:pmu_labworks/view/details_page/details_page.dart'; import 'package:pmu_labworks/view/home_page/bloc/bloc.dart'; import 'package:pmu_labworks/view/home_page/bloc/events.dart'; import 'package:pmu_labworks/view/home_page/bloc/state.dart'; import 'package:pmu_labworks/view/reaction_bloc/reaction_bloc.dart'; import 'package:pmu_labworks/view/reaction_bloc/reaction_event.dart'; import 'package:pmu_labworks/view/reaction_bloc/reaction_state.dart'; import 'package:pmu_labworks/view/locale_bloc/locale_bloc.dart'; import 'package:pmu_labworks/view/locale_bloc/locale_events.dart'; import 'package:pmu_labworks/view/locale_bloc/locale_state.dart'; part 'comment.dart'; class HomePage extends StatefulWidget { const HomePage({super.key, required this.title}); final String title; @override State createState() => _HomePageState(); } class _HomePageState extends State { Color _color = Colors.transparent; @override void initState() { super.initState(); _color = _generateColor(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( backgroundColor: _color, title: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(widget.title, style: const TextStyle( fontSize: 20, fontWeight: FontWeight.bold, )), Text( context.locale.credits + ' Factorino', style: TextStyle( fontSize: 12, fontStyle: FontStyle.italic, ), ), ], ), ), body: const _Body(), ); } Color _generateColor() { final Random random = Random(); final int red = (random.nextInt(106) + 150); final int green = (random.nextInt(106) + 150); final int blue = (random.nextInt(106) + 150); return Color.fromARGB(255, red, green, blue); } } class _Body extends StatefulWidget { const _Body(); @override State<_Body> createState() => _BodyState(); } class _BodyState extends State<_Body> { final searchController = TextEditingController(); final scrollController = ScrollController(); @override void initState() { SvgObjects.init(); WidgetsBinding.instance.addPostFrameCallback((_) { context.read().add(const HomeLoadDataEvent()); context.read().add(const LoadReactionsEvent()); }); scrollController.addListener(_onNextPageListener); super.initState(); } @override void dispose() { searchController.dispose(); scrollController.dispose(); super.dispose(); } @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: CupertinoSearchTextField( controller: searchController, placeholder: context.locale.search, onChanged: (search) { Debounce.run( () => context.read().add(HomeLoadDataEvent(search: search))); }, ), ), ), GestureDetector( onTap: () => context.read().add(const ChangeLocaleEvent()), child: SizedBox.square( dimension: 50, child: Padding( padding: const EdgeInsets.only(right: 12), child: BlocBuilder( builder: (context, state) { return state.currentLocale.languageCode == 'ru' ? const SvgRu() : const SvgUk(); }, ), ), ), ), ], ), 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, reactionState) { return 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 ? _Comment.fromData( data, onLike: _onLike, onDislike: _onDislike, isLiked: reactionState.likedIds?.contains(data.id) == true, isDisliked: reactionState.dislikedIds?.contains(data.id) == true, onTap: () => _navToDetails(context, data), ) : const SizedBox.shrink(); }, ), ), ); }, ), ), 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 _onLike(String? id, String nickname, bool isLiked, bool isDisliked) { if (id != null) { if (isDisliked) { isDisliked = false; } context.read().add(ChangeLikeEvent(id)); _showSnackBar(context, nickname, !isLiked, isDisliked); } } void _onDislike(String? id, String nickname, bool isLiked, bool isDisliked) { if (id != null) { if (isLiked) { isLiked = false; } context.read().add(ChangeDislikeEvent(id)); _showSnackBar(context, nickname, isLiked, !isDisliked); } } void _navToDetails(BuildContext context, CommentData data) { Navigator.push( context, CupertinoPageRoute(builder: (context) => DetailsPage(data)), ); } void _showSnackBar(BuildContext context, String nickname, bool isLiked, bool isDisliked) { WidgetsBinding.instance.addPostFrameCallback((_) { final String action; if (isLiked && !isDisliked) { action = context.locale.liked; } else if (!isLiked && isDisliked) { action = context.locale.disliked; } else { action = context.locale.neutralized; // Состояние без лайков/дизлайков } print('$isLiked $isDisliked = $action'); ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text( context.locale.comment(nickname) + ' ' + action, style: Theme.of(context).textTheme.bodyLarge, ), backgroundColor: Colors.white70, duration: const Duration(seconds: 1), )); }); } 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, )); } } } }