mnemo_cards/lib/widgets/card_pack/available_pack.dart
2024-06-13 08:06:50 +03:00

476 lines
15 KiB
Dart

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<bool>(
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<String>() ??
<String>[])
.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),
),
],
),
),
],
),
),
);
}
}