mnemo_cards/lib/widgets/game_card_widget.dart
2024-06-05 00:44:16 +03:00

435 lines
16 KiB
Dart

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:flutter_tts/flutter_tts.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/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<StatefulWidget> 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<GameCardWidget> {
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;
final imageScale = 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 {
final sp = await SharedPreferences.getInstance();
if (sp.getBool('sound_on') != false &&
card.original != null) {
FlutterTts flutterTts = FlutterTts();
await flutterTts.setLanguage('es-ES');
await flutterTts.setSpeechRate(0.4);
flutterTts.speak(card.original!);
}
},
),
GestureDetector(
onTap: () async {
final sp = await SharedPreferences.getInstance();
if (sp.getBool('sound_on') != false &&
card.translation != null) {
FlutterTts flutterTts = FlutterTts();
await flutterTts.setLanguage('ru');
await flutterTts.setSpeechRate(0.5);
flutterTts.speak(
card.translation!,
);
}
},
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 {
final sp =
await SharedPreferences.getInstance();
if (sp.getBool('sound_on') != false) {
FlutterTts flutterTts = FlutterTts();
await flutterTts.setLanguage('ru');
await flutterTts.setSpeechRate(0.5);
flutterTts.speak(
"${card.transcription} - ${card.transcriptionMnemo}",
);
}
},
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 {
final sp = await SharedPreferences.getInstance();
if (sp.getBool('sound_on') != false) {
FlutterTts flutterTts = FlutterTts();
await flutterTts.setLanguage('ru');
await flutterTts.setSpeechRate(0.5);
flutterTts.speak(
card.mnemo!.replaceAll(RegExp(r'[\]\[]'), ''));
}
},
child: MnemoText(
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)
Opacity(
opacity: alpha,
child: Container(
padding: EdgeInsets.all(4.0.h * alpha),
alignment: Alignment.topRight,
child: IconButton(
icon: Icon(
Icons.volume_up,
size: (26 * textScale).toDouble(),
),
onPressed: () async {
final sp = await SharedPreferences.getInstance();
if (sp.getBool('sound_on') != false) {
FlutterTts flutterTts = FlutterTts();
await flutterTts.setLanguage('es-ES');
await flutterTts.setSpeechRate(0.5);
flutterTts.speak(card.original!);
}
},
),
),
),
],
),
);
}),
);
});
}
}
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(),
);
});
}
}