mnemo_cards/lib/widgets/card_pack/available_pack.dart

511 lines
15 KiB
Dart
Raw Normal View History

2024-05-22 20:48:44 +00:00
import 'dart:math';
import 'package:auto_route/auto_route.dart';
import 'package:flutter/animation.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/main.dart';
import 'package:mnemo_cards/utils/iterable_helper.dart';
import 'package:mnemo_cards_common/mnemo_cards_common.dart';
import 'package:yandex_mobileads/mobile_ads.dart';
import '../../domain/router/app_router.gr.dart';
import '../../features/yandex_ads/yandex_ads.dart';
import '../game_card_widget.dart';
import '../../di/locator.dart';
import '../../theme/themes.dart';
import 'card_pack_header.dart';
import 'package:mnemo_cards/utils/string_helper.dart';
class AvailablePack extends StatelessWidget {
final CardPackDto cardPack;
final _CardsViewController _cardsViewController = _CardsViewController();
AvailablePack(this.cardPack);
@override
Widget build(BuildContext context) {
return Column(
children: [
CardPackHeader.fromDto(cardPack),
Expanded(
child: ListView(
children: [
StatefulBuilder(builder: (context, setState) {
return Column(
children: [
Container(
padding: EdgeInsets.symmetric(vertical: 0.h),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: cardPack.color?.asColor ??
Colors.grey.withOpacity(0.5)),
),
),
clipBehavior: Clip.antiAlias,
child: _CardsView(_cardsViewController, cardPack),
),
SizedBox(
height: 10.h,
),
Padding(
padding: EdgeInsets.symmetric(
vertical: 8.0.h,
horizontal: 10.w,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_Button('view.png', () async {
_cardsViewController.toggleExpanded();
}),
_Button('shuffle.png', () async {
final index =
Random().nextInt(cardPack.cards.length);
await _cardsViewController.animateTo(index);
AutoRouter.of(context).push(
PageRouteInfo(
CardGamePage.name,
args: CardGamePageArgs(
cardPack: cardPack,
startCardId: cardPack.cards[index].id,
infinite: true,
// id: cardPack.dto.id.toString(),
),
),
);
}),
// _Button('chat.png', () {}),
_Button('heart.png', () {}),
],
),
),
],
);
}),
_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) {
return FutureBuilder(
future: locator.testManager.loadTests(packId: packId),
builder: (context, snapshot) {
return Column(
children: [
if (snapshot.hasData)
...snapshot.data!.map(
(dto) => _TestButton(
title: dto.name,
subtitle: null,
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) {
final theme = Theme.of(context).textTheme;
return 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: LayoutBuilder(builder: (context, constraints) {
return AdWidget(
bannerAd: YandexAds.createBanner(constraints)..loadAd());
}),
),
);
}
}
class _Button extends StatelessWidget {
final String asset;
final VoidCallback onTap;
_Button(
this.asset,
this.onTap,
);
@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 _TestButton extends StatelessWidget {
final VoidCallback onTap;
final String title;
final String? subtitle;
final String? time;
final String? timeSubtitle;
_TestButton({
required this.title,
required this.onTap,
this.subtitle,
this.time,
this.timeSubtitle,
});
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.symmetric(
vertical: 5.0.h,
horizontal: 10.w,
),
child: InkWell(
onTap: onTap,
child: Container(
height: 120.h,
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)
Text(
subtitle!,
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w300,
height: 0.85,
),
),
],
),
),
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),
),
],
),
),
],
),
),
),
);
}
}
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(
tag: card.id,
child: GameCardDecoration(
alpha: 0.0,
child: FrontCard(
card,
image,
alpha: 0,
),
),
createRectTween: (a, b) => RectTween(begin: a, end: b),
flightShuttleBuilder: (flightC, a, dir, fromC, toC) => AnimatedBuilder(
animation: a,
builder: (context, child) => GameCardDecoration(
alpha: a.value,
child: FrontCard(
card,
card.memoryImage!,
alpha: a.value,
),
),
),
),
);
}
}
class _CardsView extends StatefulWidget {
final CardPackDto cardPack;
final _CardsViewController controller;
_CardsView(this.controller, this.cardPack);
@override
State<StatefulWidget> createState() => _CardsViewState();
}
class _CardsViewState extends State<_CardsView>
with SingleTickerProviderStateMixin {
CardPackDto get cardPack => widget.cardPack;
_CardsViewController get controller => widget.controller;
late AnimationController _animationController;
bool get expanded => controller._expanded;
void _onStateChanged() {
if (expanded) {
_animationController.animateTo(1.0);
} else {
_animationController.animateBack(0.0);
}
// setState(() {});
}
@override
void didUpdateWidget(_CardsView oldWidget) {
super.didUpdateWidget(oldWidget);
controller.addListener(_onStateChanged);
}
@override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this,
duration: Duration(milliseconds: 500),
reverseDuration: Duration(milliseconds: 500));
controller.addListener(_onStateChanged);
}
@override
void dispose() {
controller.removeListener(_onStateChanged);
_animationController.dispose();
super.dispose();
}
void onCardTap(GameCardDto card) async {
final playing = appRouter.push(
PageRouteInfo(
CardGamePage.name,
args: CardGamePageArgs(
cardPack: cardPack,
startCardId: card.id,
infinite: true,
// 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;
final packCards = cardPack.cards;
controller.imageWidth = imageWidth;
final animation = _animationController.view;
return AnimatedBuilder(
animation: animation,
builder: (context, child) {
return 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 * 4 / 9 * cardWidth - cardWidth * 4 / 3) *
animation.value,
child: Wrap(
children: List.generate(packCards.length, (index) {
final card = packCards[index];
final memoryImage = card.id.memoryImage;
if (memoryImage == null) return SizedBox();
return Container(
height: cardWidth * 4 / 3,
width: cardWidth,
padding: EdgeInsets.symmetric(
horizontal: cardWidth - imageWidth - 1,
vertical: cardWidth - imageWidth - 1,
),
alignment: Alignment.topCenter,
child: Container(
height: imageWidth * 4 / 3,
child: _CardBuilder(
cardPack, card, memoryImage, onCardTap)),
);
}),
),
),
);
});
}
}
class _CardsViewController extends ChangeNotifier {
bool _expanded = false;
final ScrollController _cardsScrollController =
ScrollController(keepScrollOffset: true);
double imageWidth = 0.0;
void setExpanded(bool expanded) {
if (_expanded != expanded) {
_expanded = expanded;
notifyListeners();
}
}
void toggleExpanded() => setExpanded(!_expanded);
_CardsViewController();
Future<void> animateTo(int index) {
final millis =
((_cardsScrollController.offset / imageWidth - index).abs() * 30.0)
.ceil();
return _cardsScrollController.animateTo(
imageWidth * index,
duration: Duration(milliseconds: millis),
curve: Curves.easeInOut,
);
}
void jumpTo(int index) {
final millis =
((_cardsScrollController.offset / imageWidth - index).abs() * 30.0)
.ceil();
return _cardsScrollController.jumpTo(
imageWidth * index,
);
}
}