import 'dart:math'; import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/theme_data.dart'; import 'package:didvan/constants/assets.dart'; import 'package:didvan/models/category.dart'; import 'package:didvan/models/enums.dart'; import 'package:didvan/models/requests/radar.dart'; import 'package:didvan/views/home/statistics/statistics_state.dart'; import 'package:didvan/views/home/widgets/categories_gird.dart'; import 'package:didvan/views/home/widgets/categories_list.dart'; import 'package:didvan/views/home/widgets/logo_app_bar.dart'; import 'package:didvan/views/home/widgets/overview/radar.dart'; import 'package:didvan/views/widgets/animated_visibility.dart'; import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:didvan/views/widgets/state_handlers/empty_state.dart'; import 'package:didvan/views/widgets/state_handlers/sliver_state_handler.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; class Statistics extends StatefulWidget { const Statistics({Key? key}) : super(key: key); @override State createState() => _RadarState(); } class _RadarState extends State { final ScrollController _scrollController = ScrollController(); bool _isAnimating = false; @override void initState() { _scrollController.addListener(() { _handleAnimations(); }); final state = context.read(); state.addListener(() { if (state.shouldColapse && mounted) { _handleAnimations(true); state.shouldColapse = false; } }); state.init(); super.initState(); } @override Widget build(BuildContext context) { return Consumer( builder: (context, state, child) => Stack( children: [ CustomScrollView( physics: _isAnimating ? const NeverScrollableScrollPhysics() : const ClampingScrollPhysics(), controller: _scrollController, slivers: [ const SliverToBoxAdapter(child: LogoAppBar()), if (state.appState != AppState.failed) const SliverToBoxAdapter( child: SizedBox(height: 156), ), if (state.appState != AppState.failed) SliverPadding( padding: const EdgeInsets.only(right: 16, bottom: 20), sliver: SliverToBoxAdapter( child: Align( alignment: Alignment.centerRight, child: AnimatedVisibility( isVisible: !state.isColapsed, duration: DesignConfig.lowAnimationDuration, child: DidvanText( 'شاخص‌های منتخب', style: Theme.of(context).textTheme.subtitle1, color: Theme.of(context).colorScheme.title, ), ), ), ), ), SliverStateHandler( onRetry: () => state.getStatistics(page: state.page), state: state, itemPadding: const EdgeInsets.only( bottom: 20, left: 16, right: 16, ), enableEmptyState: state.statistics.isEmpty, emptyState: Padding( padding: const EdgeInsets.only(bottom: 120), child: EmptyState( asset: Assets.emptyResult, title: 'موردی برای نمایش وجود ندارد.', ), ), placeholder: RadarOverview.placeholder, builder: (context, state, index) { index += 2; if (index % 15 == 0 && state.lastPage != state.page) { state.getStatistics(page: state.page + 1); } index -= 2; if (index >= state.statistics.length) { return RadarOverview.placeholder; } final radar = state.statistics[index]; return RadarOverview( radar: radar, onMarkChanged: state.changeMark, onCommentsChanged: (id, count) => {}, radarRequestArgs: RadarRequestArgs( page: state.page, categories: List.from(state.selectedCats.map((cat) => cat.id)), isSingleItem: false, ), ); }, childCount: state.statistics.length + (state.lastPage == state.page ? 0 : 3), ), if (state.statistics.length == 1) const SliverToBoxAdapter( child: SizedBox(height: 320), ), ], ), if (state.appState != AppState.failed) CategoriesRow1( onSelected: _onCategorySelected, categories: state.categories, isColapsed: state.isColapsed, topPadding: 120, ), if (state.appState != AppState.failed) CategoriesList( isRadar: false, categories: state.categories, isColapsed: state.isColapsed, onSelected: () => state.getStatistics(page: 1), selectedCats: state.selectedCats, ), ], ), ); } void _onCategorySelected(CategoryData category) { final state = context.read(); state.selectedCats.clear(); if (category.id != 0) { state.selectedCats.add(category); } state.getStatistics(page: 1); } void _handleAnimations([bool forceAnimate = false]) async { final state = context.read(); if (_isAnimating) return; final double position = _scrollController.offset; if (position > 5 && !state.isColapsed || forceAnimate) { state.isScrolled = true; _isAnimating = true; setState(() {}); await _scrollController.animateTo( 200, duration: DesignConfig.mediumAnimationDuration, curve: Curves.easeIn, ); _isAnimating = false; setState(() {}); } else if (position < min(_scrollController.position.maxScrollExtent, 200) && state.isColapsed) { state.isScrolled = false; _isAnimating = true; setState(() {}); await _scrollController.animateTo( 0, duration: DesignConfig.mediumAnimationDuration, curve: Curves.easeIn, ); _isAnimating = false; setState(() {}); } } @override void dispose() { _scrollController.dispose(); super.dispose(); } }