import 'dart:math'; import 'package:collection/collection.dart'; import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:mnemo_cards/main.dart'; import 'package:mnemo_cards/utils/iterable_helper.dart'; import 'package:mnemo_cards/widgets/fade_in_widget.dart'; import 'package:mnemo_cards/widgets/feedback_builder.dart'; import 'package:mnemo_cards_common/mnemo_cards_common.dart'; import 'package:reorderables/reorderables.dart'; import 'package:yandex_mobileads/mobile_ads.dart'; import '../../domain/router/app_router.gr.dart'; import '../../features/analytics/analytics.dart'; import '../../features/yandex_ads/yandex_ads.dart'; import '../../di/locator.dart'; import '../../theme/themes.dart'; import '../horizontal_progress.dart'; import 'card_pack_header.dart'; import 'package:mnemo_cards/utils/string_helper.dart'; import 'cards_view.dart'; import 'cards_view_controller.dart'; final _mainScrollController = ScrollController(); final _reorderableTests = GlobalKey(); class AvailablePack extends StatelessWidget { final CardPackDto cardPack; late final CardsViewController _cardsViewController = CardsViewController(cardPack); AvailablePack(this.cardPack); @override Widget build(BuildContext context) { Analytics.availablePackOpened(cardPack); return Column( children: [ CardPackHeader.fromDto(cardPack), Expanded( child: ListView( controller: _mainScrollController, children: [ StatefulBuilder(builder: (context, setState) { return Column( children: [ Container( decoration: BoxDecoration( color: cardPack.color?.asColor?.withOpacity(0.1), ), alignment: Alignment.center, clipBehavior: Clip.antiAlias, child: CardsView(_cardsViewController, cardPack), ), Container( height: 1, color: cardPack.color?.asColor ?? Colors.grey.withOpacity(0.5), ), SizedBox( height: 10.h, ), Padding( padding: EdgeInsets.symmetric( horizontal: 10.w, ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ StreamBuilder( stream: _cardsViewController.asStream .map((s) => s.expanded), builder: (context, snapshot) { return _DownButton( !(snapshot.data ?? false), () async { if (snapshot.hasData) { _cardsViewController.setExpanded( !snapshot.requireData, ); } }, key: ValueKey('DownButton'), ); }), _Button('shuffle.png', () async { final cards = _cardsViewController.state.packCards; if (cards.isEmpty) return; final index = (_cardsViewController.offsetCards + Random().nextInt( (cards.length / 2).ceil(), ) + 3) % cards.length; await _cardsViewController.animateTo(index); AutoRouter.of(context).push( PageRouteInfo( CardGamePage.name, args: CardGamePageArgs( cards: cards, color: cardPack.color?.asColor, startCardId: cards[index].id, infinite: true, shuffle: true, // id: cardPack.dto.id.toString(), ), ), ); }), // _Button('chat.png', () {}), StreamBuilder( stream: _cardsViewController.asStream, builder: (context, snapshot) { return _Button( (snapshot.data?.favorite ?? false) ? 'heart_fill.png' : 'heart.png', () { if (snapshot.hasData) { _cardsViewController.setFavorite( !snapshot.requireData.favorite, ); } }); }), ], ), ), ], ); }), _AdTile(), _TestsSection(cardPack.id.toString()), // _TestButton( // title: 'Быстрый тест', // subtitle: 'Лучший результат: 99 / 100', // time: '3', // timeSubtitle: 'мин', // onTap: () {}, // ), // _TestButton( // title: 'Полный тест', // subtitle: 'Лучший результат: 99 / 100', // time: '10', // timeSubtitle: 'мин', // onTap: () {}, // ), ], ), ), Hero( tag: 'bottom', child: SizedBox( height: 0, width: 300.w, ), ), ], ); } } class _TestsSection extends StatelessWidget { final String packId; const _TestsSection(this.packId); @override Widget build(BuildContext context) { final order = (globalSharedPreferences .getString('tests_order_${packId}') ?.decodeList() ?? []) .toList(); void saveOrder() { globalSharedPreferences.setString( 'tests_order_${packId}', order.encode(), ); } return StreamBuilder( stream: locator.testManager.loadTestsStream(packId: packId), builder: (context, snapshot) { final tests = snapshot.data ?? []; if (tests.isEmpty) { return SizedBox.shrink(); } if (order.isEmpty) { order.addAll(tests.map((t) => t.name)); saveOrder(); } return FadeInWidget( animationDuration: Duration(milliseconds: 400), child: StatefulBuilder(builder: (context, setState) { tests..sortInOrder(order, (t) => t.name); return ReorderableListView.builder( key: _reorderableTests, itemExtent: 130.h + 2 * 5.h, padding: EdgeInsets.symmetric( horizontal: 10.w, vertical: 5.h, ), shrinkWrap: true, scrollController: _mainScrollController, // physics: NeverScrollableScrollPhysics(), proxyDecorator: (_, index, animation) { final dto = tests[index]; return Material( elevation: 5, borderRadius: BorderRadius.circular(16), color: Colors.transparent, child: _TestButton( title: dto.name, subtitle: null, stats: dto.statistics, color: dto.color?.asColor ?? borderGray, time: dto.time, timeSubtitle: dto.timeSubtitle, onTap: () {}, ), ); }, onReorder: (int from, int to) { if (to - from > 1) { to--; } order.move(from, to); saveOrder(); setState(() {}); }, itemCount: tests.length, itemBuilder: (context, index) { final dto = tests[index]; return Padding( key: ValueKey(dto.id), padding: EdgeInsets.symmetric( vertical: 5.0.h, ), child: _TestButton( title: dto.name, subtitle: null, stats: dto.statistics, color: dto.color?.asColor ?? borderGray, time: dto.time, timeSubtitle: dto.timeSubtitle, onTap: () { AutoRouter.of(context).push( PageRouteInfo(TestPage.name, args: TestPageArgs(testId: dto.id!)), ); }, ), ); }, ); }), ); }, ); } } class _AdTile extends StatelessWidget { const _AdTile(); @override Widget build(BuildContext context) { return LayoutBuilder(builder: (context, constraints) { final banner = YandexAds.createBanner( BoxConstraints.tight(Size(constraints.maxWidth - 16, 80 - 8))); return FadeInWidget( future: banner.loadAd(), animationDuration: Duration(milliseconds: 1000), child: Container( height: 80, margin: const EdgeInsets.symmetric(vertical: 4.0, horizontal: 8.0), decoration: BoxDecoration( border: Border.all( color: Colors.white, width: 4.0, ), borderRadius: BorderRadius.circular(12.0), ), child: Center( child: AdWidget(bannerAd: banner), ), ), ); }); } } class _Button extends StatelessWidget { final String asset; final VoidCallback onTap; _Button(this.asset, this.onTap, {super.key}); @override Widget build(BuildContext context) { return InkWell( onTap: onTap, child: Container( width: 110.w, height: 60.h, decoration: BoxDecoration( color: Colors.white, border: Border.all(color: borderGray), borderRadius: BorderRadius.circular(16.0), ), alignment: Alignment.center, child: Image.asset( 'icons/$asset', width: 29.w, // height: 29.h, fit: BoxFit.scaleDown, ), ), ); } } class _DownButton extends StatelessWidget { final VoidCallback onTap; final bool isDown; _DownButton(bool this.isDown, this.onTap, {super.key}); @override Widget build(BuildContext context) { return InkWell( onTap: onTap, child: Container( width: 110.w, height: 60.h, decoration: BoxDecoration( color: Colors.white, border: Border.all(color: borderGray), borderRadius: BorderRadius.circular(16.0), ), alignment: Alignment.center, child: AnimatedRotation( duration: Duration(milliseconds: 300), turns: isDown ? 0.0 : 0.5, child: Image.asset( 'icons/down.png', width: 29.w, // height: 29.h, fit: BoxFit.scaleDown, ), ), ), ); } } class _TestButton extends StatelessWidget { final VoidCallback onTap; final String title; final String? subtitle; final String? time; final String? timeSubtitle; final Color color; final TestStatisticsDto? stats; _TestButton({ required this.title, required this.onTap, required this.color, this.subtitle, this.time, this.timeSubtitle, this.stats, super.key, }); @override Widget build(BuildContext context) { return GestureDetector( onTap: onTap, child: Container( height: 130.h, alignment: Alignment.center, decoration: BoxDecoration( color: Colors.white, border: Border.all(color: borderGray), borderRadius: BorderRadius.circular(16.0), ), padding: EdgeInsets.only( top: 12.0.h, left: 10.0.w, right: 10.0.w, bottom: 20.h, ), child: Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( title, style: TextStyle( fontSize: 25.sp, fontWeight: FontWeight.w500, height: 0.85, ), ), if (subtitle != null) Padding( padding: const EdgeInsets.only(top: 8.0), child: Text( subtitle!, style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.w300, height: 0.85, ), ), ), Spacer(), if (stats != null) Padding( padding: const EdgeInsets.only(right: 50.0), child: Row( children: [ Expanded( child: HorizontalProgressWidget( stats!.words.incorrect.round(), stats!.words.total.round(), color, hasBorder: true, ), ), ], ), ), ], ), ), Container( width: 75.w, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ if (time != null) Text( time!, style: TextStyle( fontSize: 40.sp, fontWeight: FontWeight.w400, height: 0.85), ), if (timeSubtitle != null) Text( timeSubtitle!, style: TextStyle( fontSize: 24.sp, fontWeight: FontWeight.w400, height: 0.85), ), ], ), ), ], ), ), ); } }