import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:mnemo_cards/features/packs/images_holder.dart'; import 'package:mnemo_cards_common/mnemo_cards_common.dart'; import 'package:reorderables/reorderables.dart'; import 'package:rxdart/rxdart.dart'; import '../../utils/string_helper.dart'; import '../../domain/router/app_router.gr.dart'; import '../../main.dart'; import '../feedback_builder.dart'; import '../game_card_widget.dart'; import 'cards_view_controller.dart'; class CardsView extends StatefulWidget { final CardPackDto cardPack; final CardsViewController controller; final List cardsOrder = []; CardsView(this.controller, this.cardPack); @override State createState() => _CardsViewState(); } class _CardsViewState extends State with SingleTickerProviderStateMixin { CardPackDto get cardPack => widget.cardPack; CardsViewController get controller => widget.controller; var _subscription = CompositeSubscription(); late AnimationController _animationController; bool expanded = false; bool favorite = false; final wrapKey = GlobalKey(); void _onStateChanged(CardsViewState state) async { favorite = state.favorite; if (state.expanded != expanded) { expanded = state.expanded; if (expanded) { await controller.animateTo(0); _animationController.animateTo( 1.0, duration: Duration(milliseconds: (100 * state.packCards.length / 3).ceil()), ); } else { _animationController.animateBack( 0.0, duration: Duration(milliseconds: (100 * state.packCards.length / 3).ceil()), ); } } } @override void didUpdateWidget(CardsView oldWidget) { super.didUpdateWidget(oldWidget); _subscription.cancel(); _subscription = CompositeSubscription(); controller.asStream.listen(_onStateChanged).addTo(_subscription); } @override void initState() { super.initState(); expanded = controller.state.expanded; favorite = controller.state.favorite; _animationController = AnimationController(vsync: this); _subscription.cancel(); _subscription = CompositeSubscription(); controller.asStream.listen(_onStateChanged).addTo(_subscription); } @override void dispose() { _subscription.cancel(); _animationController.dispose(); super.dispose(); } void onCardTap(List cards, GameCardDto card) async { final playing = appRouter.push( PageRouteInfo( CardGamePage.name, args: CardGamePageArgs( cards: cards, color: cardPack.color?.asColor, startCardId: card.id, infinite: true, shuffle: false, // id: cardPack.dto.id.toString(), ), ), ); final id = await playing; // if (id is int) { // final index = cardPack.cards.indexWhere((c) => c.id == id); // if (index >= 0) { // print('jump to $index'); // controller.jumpTo(index); // } // } } @override Widget build(BuildContext context) { final cardWidth = MediaQuery.of(context).size.width / 3; final imageWidth = cardWidth * 0.93; return StreamBuilder( stream: controller.cardsStream, builder: (context, snapshot) { if (!snapshot.hasData) { return SizedBox(); } final packCards = snapshot.requireData; if (packCards.isEmpty) { return _NoCards( favorite: favorite, height: cardWidth * 4 / 3, ); } controller.imageWidth = imageWidth; final animation = _animationController.view; return AnimatedBuilder( animation: animation, builder: (context, child) { return Column( children: [ SingleChildScrollView( controller: controller.cardsScrollController, scrollDirection: animation.value > 0 ? Axis.vertical : Axis.horizontal, physics: animation.value > 0 ? NeverScrollableScrollPhysics() : null, child: Container( alignment: Alignment.topCenter, height: cardWidth * 4 / 3 + ((packCards.length / 3).ceil() * 4 / 3 * cardWidth - cardWidth * 4 / 3) * animation.value, child: Builder(builder: (context) { return ReorderableWrap( key: wrapKey, onReorder: (int from, int to) { controller.onOrderChanged(from, to); }, buildDraggableFeedback: FeedbackBuilder.simple, padding: EdgeInsets.symmetric( horizontal: (cardWidth - imageWidth - 1), vertical: (cardWidth - imageWidth - 1), ), runSpacing: (cardWidth - imageWidth - 1) * 2, spacing: (cardWidth - imageWidth - 1) * 2, children: List.generate(packCards.length, (index) { final card = packCards[index]; final memoryImage = card.memoryImage; if (memoryImage == null) { return SizedBox(); } return Container( height: 2 * imageWidth - cardWidth * 2 / 3 + 2, width: 2 * imageWidth - cardWidth + 2, alignment: Alignment.topCenter, child: Container( height: imageWidth * 4 / 3, child: _CardBuilder( cardPack, card, memoryImage, (card) => onCardTap(packCards, card), ), ), ); }), ); }), ), ), ], ); }); }); } } class _CardBuilder extends StatelessWidget { final GameCardDto card; final CardPackDto cardPack; final MemoryImage image; final Function(GameCardDto) onTap; _CardBuilder( this.cardPack, this.card, this.image, this.onTap, ); @override Widget build(BuildContext context) { return GestureDetector( key: ObjectKey(card), onTap: () => onTap(card), child: Hero( key: ValueKey('${card.id}'), tag: card.id, child: GameCardDecoration( alpha: 0.0, borderColor: cardPack.color?.asColor, child: FrontCard( card, image, alpha: 0, ), ), createRectTween: (a, b) => RectTween(begin: a, end: b), flightShuttleBuilder: (flightC, a, dir, fromC, toC) { return AnimatedBuilder( animation: a, builder: (context, child) => GameCardDecoration( alpha: a.value, borderColor: cardPack.color?.asColor, child: FrontCard( card, card.memoryImage!, alpha: a.value, ), ), ); }, ), ); } } class _NoCards extends StatelessWidget { final bool favorite; final double height; _NoCards({ required this.favorite, required this.height, }); @override Widget build(BuildContext context) { return Container( height: height, alignment: Alignment.center, child: Text( favorite ? 'Нет любимых карточек' : 'Пока нет карточек', style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.w700, ), textAlign: TextAlign.center, ), ); } }