import 'dart:math'; import 'dart:ui'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:mnemo_cards/di/locator.dart'; import 'package:mnemo_cards/features/yandex_ads/yandex_ads.dart'; import 'package:flutter/painting.dart'; import 'package:mnemo_cards/managers/audio_player.dart'; import 'package:mnemo_cards/utils/color_helper.dart'; import 'package:mnemo_cards_common/mnemo_cards_common.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:yandex_mobileads/mobile_ads.dart'; import '../features/card_flipper/flip_card.dart'; import '../features/card_flipper/flip_card_controllers.dart'; import '../features/card_flipper/flip_side.dart'; import '../theme/themes.dart'; import 'image_fade.dart'; import 'mnemo_text.dart'; class GameCardWidget extends StatefulWidget { static const borderRadius = 32.0; final GameCardDto card; final MemoryImage? image; final bool showFront; final bool isFirstCard; final FlipCardController? flipCardController; final Color? backColor; const GameCardWidget( this.card, this.image, this.flipCardController, this.showFront, this.isFirstCard, this.backColor, { super.key, }); @override State createState() => _GameCardWidgetState(); } class GameCardDecoration extends StatelessWidget { final Widget child; final double alpha; final Color? borderColor; GameCardDecoration({ required this.child, this.alpha = 1.0, this.borderColor, }); @override Widget build(BuildContext context) => Container( padding: EdgeInsets.all(8.0 * alpha), margin: EdgeInsets.only( left: 10.0 * alpha, right: 10.0 * alpha, bottom: 2.0 * alpha, top: 0.0 * alpha, ), decoration: BoxDecoration( color: Colors.white, boxShadow: [ if (alpha > 0) BoxShadow( color: Colors.black.withOpacity(0.25 * alpha), blurRadius: 4.0 * alpha, ), ], borderRadius: BorderRadius.circular( 12 + (GameCardWidget.borderRadius - 12) * alpha), border: Border.all( color: (borderColor ?? borderGray).withOpacity(1.0 - alpha * alpha), ), ), child: ClipRRect( borderRadius: BorderRadius.circular( 8 + (GameCardWidget.borderRadius - 8) * alpha), child: child, ), ); } class _GameCardWidgetState extends State { FlipCardController? get _flipCardController => widget.flipCardController; @override Widget build(BuildContext context) { final theme = Theme.of(context).textTheme; print('${widget.card.original}'); final frontWidget = FrontCard(widget.card, widget.image); final backWidget = _BackWidget( widget.card, widget.backColor, ); Duration? dragStart; Duration? dragUpdate; return GameCardDecoration( child: Material( color: Colors.transparent, child: GestureDetector( onVerticalDragStart: (start) { dragStart = start.sourceTimeStamp; }, onVerticalDragUpdate: (update) { dragUpdate = update.sourceTimeStamp; }, onVerticalDragEnd: (update) { final pps = update.velocity.pixelsPerSecond; if (widget.isFirstCard && pps.dy.abs() > 2 * pps.dx.abs() && pps.dy.abs() > 70 && (dragStart != null && dragUpdate != null && (dragUpdate!.inMilliseconds - dragStart!.inMilliseconds) > 50)) { _flipCardController?.flipcard(); } }, behavior: HitTestBehavior.deferToChild, onDoubleTap: () async { await locator.favoriteCardsController .switchFavoriteCard(widget.card); }, child: (widget.isFirstCard) ? FlipCard( rotateSide: RotateSide.left, onTapFlipping: false, axis: FlipAxis.horizontal, controller: _flipCardController!, frontWidget: frontWidget, backWidget: backWidget, ) : widget.showFront ? frontWidget : backWidget, ), ), ); } } class _BackWidget extends StatelessWidget { final GameCardDto card; final Color? _backColor; const _BackWidget( this.card, this._backColor, ); @override Widget build(BuildContext context) { final theme = Theme.of(context).textTheme; final backColor = _backColor ?? Colors.lightBlueAccent[200]; return Container( padding: const EdgeInsets.all(8.0), decoration: BoxDecoration( color: backColor, boxShadow: const [BoxShadow()], borderRadius: BorderRadius.circular(32.0), ), child: Center( child: MnemoText( card.back?.isNotEmpty == true ? card.back : card.translation ?? '', textStyle: theme.displayMedium!.copyWith(color: Colors.white), maxLines: 2, softWrap: true, ), ), ); } } class FrontCard extends StatelessWidget { final GameCardDto card; final MemoryImage? image; final double alpha; FrontCard(this.card, this.image, {super.key, this.alpha = 1.0}); @override Widget build(BuildContext context) { final theme = Theme.of(context).textTheme; return StatefulBuilder(builder: (context, setState) { return AbsorbPointer( absorbing: alpha != 1, child: LayoutBuilder(builder: (context, constrains) { const targetTextAr = 10 / 9; const targetImageAr = 16 / 9; double ar = constrains.maxHeight / constrains.maxWidth; if (constrains.maxHeight.isInfinite) { ar = 16 / 9; } final textScale = min(1.0, ar / targetTextAr) * alpha * alpha; final imageScale = 0.9 * min(1.0, pow(ar / targetImageAr, 1.5 * alpha)); return Material( borderOnForeground: false, type: MaterialType.transparency, child: Stack( alignment: Alignment.center, children: [ Padding( padding: EdgeInsets.all(8.0 * imageScale * (alpha / 2 + 0.5)), child: Column( mainAxisSize: MainAxisSize.max, mainAxisAlignment: ar > 1 ? MainAxisAlignment.spaceEvenly : MainAxisAlignment.spaceAround, children: [ Column( children: [ GestureDetector( child: MnemoText( card.original ?? '', textStyle: TextStyle( fontSize: 15 * textScale + 12, fontWeight: FontWeight.w600, ), maxLines: 1, ), onTap: () async { if (card.original != null) { AudioPlayer.playAudio( card.original!, lang: 'es-ES', ); } }, ), GestureDetector( onTap: () async { if (card.translation != null) { if (card.original != null) { AudioPlayer.playAudio( card.translation!, lang: 'ru', ); } } }, child: MnemoText( card.translation?.toLowerCase(), textStyle: TextStyle( fontSize: 12 * textScale * alpha + 8, fontWeight: FontWeight.w400, color: theme.headlineSmall!.color! .withOpacity(0.5), ), maxLines: 1, ), ), if (alpha > 0 && ar > 1 && card.transcription != null && card.transcriptionMnemo != null) Padding( padding: EdgeInsets.only(top: 4.0.h * alpha), child: GestureDetector( onTap: () async { if (card.transcription != null && card.transcriptionMnemo != null) { AudioPlayer.playAudio( '${card.transcription} - ${card.transcriptionMnemo}', lang: 'ru', ); } }, child: MnemoText( "[${card.transcription}] - ${card.transcriptionMnemo}", textStyle: TextStyle( fontSize: 20 * textScale * alpha, fontWeight: FontWeight.w400, ), textAlign: TextAlign.center, maxLines: 2, // group: smallSize, ), ), ), ], ), Container( width: constrains.maxHeight / ar * imageScale, height: constrains.maxHeight / ar * imageScale, clipBehavior: Clip.antiAlias, decoration: BoxDecoration( borderRadius: BorderRadius.circular( GameCardWidget.borderRadius * imageScale, )), child: ImageFade( key: ValueKey(card.id), image: image, // fix for elapsedInSeconds < 0 in debug mode syncDuration: Duration.zero, ), ), GestureDetector( onTap: () async { if (card.mnemo?.trim().isNotEmpty != true) { return; } AudioPlayer.playAudio( card.mnemo!.replaceAll(RegExp(r'[\]\[]'), ''), lang: 'ru', ); }, child: MnemoText( card.mnemo?.isNotEmpty == true ? card.mnemo : ' ', textStyle: TextStyle( fontSize: 20 * textScale, fontWeight: FontWeight.w400, ), ), ), ], ), ), if (alpha > 0) Align( alignment: ar > 4 / 3 ? Alignment.bottomCenter : Alignment.topCenter, child: Text( "mnemo cards", style: TextStyle( fontSize: 10 * textScale, color: Colors.black26), maxLines: 1, // group: smallSize, ), ), if (false && alpha > 0 && locator.favoriteCardsController.isFavoriteCard(card.id)) Align( alignment: ar > 4 / 3 ? Alignment.bottomLeft : Alignment.topLeft, child: Padding( padding: EdgeInsets.all(8.0.w), child: Image.asset( "icons/heart.png", width: 8.w, height: 8.h, ), ), ), if (alpha > 0 && AudioPlayer.soundOn) Opacity( opacity: alpha, child: Container( padding: EdgeInsets.all(4.0.h * alpha), alignment: Alignment.topRight, child: IconButton( icon: Image.asset( 'icons/sound_on.png', width: 24 * textScale, ), onPressed: () async { AudioPlayer.playAudio(card.original, lang: 'es-ES'); }, ), ), ), ], ), ); }), ); }); } } class _BannerWidget extends StatelessWidget { final BoxConstraints constraints; final String adId; final String adKey; late final BannerAd bannerAd; bool bannerLoaded = false; _BannerWidget(this.constraints, this.adId, this.adKey) : super(key: ValueKey(adKey)); @override Widget build(BuildContext context) { BannerAd? bannerAd; bool bannerLoaded = false; return StatefulBuilder(builder: (context, setState) { bannerAd ??= YandexAds.createBanner( BoxConstraints( maxHeight: constraints.maxHeight, maxWidth: constraints.maxWidth, ), id: adId, key: adKey, onLoaded: () { bannerLoaded = true; setState(() {}); }, ); return (bannerLoaded || true) ? Container( alignment: Alignment.center, child: AdWidget( bannerAd: bannerAd!, ), ) : Center( child: CircularProgressIndicator(), ); }); } }