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

276 lines
8.5 KiB
Dart

import 'dart:async';
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:collection/collection.dart';
import 'package:reorderables/reorderables.dart';
import 'package:rxdart/rxdart.dart';
import '../../di/locator.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<int> cardsOrder = [];
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;
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<GameCardDto> 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.w500,
),
textAlign: TextAlign.center,
),
);
}
}