import 'package:flutter/material.dart'; import 'package:collection/collection.dart'; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/pangea/common/widgets/choice_animation.dart'; import 'package:fluffychat/pangea/text_to_speech/tts_controller.dart'; import 'package:fluffychat/widgets/matrix.dart'; import '../../bot/utils/bot_style.dart'; import '../../choreographer/it/it_shimmer.dart'; typedef ChoiceCallback = void Function(String value, int index); class ChoicesArray extends StatelessWidget { final bool isLoading; final List? choices; final ChoiceCallback onPressed; final ChoiceCallback? onLongPress; final int? selectedChoiceIndex; final bool enableAudio; /// language code for the TTS final String? langCode; /// Used to unqiuely identify the keys for choices, in cases where multiple /// choices could have identical text, like in back-to-back practice activities final String? id; final String Function(String)? getDisplayCopy; const ChoicesArray({ super.key, required this.isLoading, required this.choices, required this.onPressed, required this.selectedChoiceIndex, this.enableAudio = true, this.langCode, this.onLongPress, this.getDisplayCopy, this.id, }); @override Widget build(BuildContext context) { return isLoading && (choices == null || choices!.length <= 1) ? const ItShimmer() : Wrap( alignment: WrapAlignment.center, spacing: 4.0, children: [ ...choices!.mapIndexed( (index, entry) => ChoiceItem( onLongPress: onLongPress, onPressed: (String value, int index) { onPressed(value, index); if (enableAudio && langCode != null) { TtsController.tryToSpeak( value, targetID: null, langCode: langCode!, ); } }, entry: MapEntry(index, entry), isSelected: selectedChoiceIndex == index, id: id, getDisplayCopy: getDisplayCopy, ), ), ], ); } } class Choice { Choice({ this.color, required this.text, this.isGold = false, }); final Color? color; final String text; final bool isGold; } class ChoiceItem extends StatelessWidget { final MapEntry entry; final ChoiceCallback? onLongPress; final ChoiceCallback onPressed; final bool isSelected; final String? id; final String Function(String)? getDisplayCopy; final double? fontSize; const ChoiceItem({ super.key, required this.onLongPress, required this.onPressed, required this.entry, required this.isSelected, required this.id, this.getDisplayCopy, this.fontSize, }); @override Widget build(BuildContext context) { final theme = Theme.of(context); return Tooltip( message: onLongPress != null ? L10n.of(context).holdForInfo : "", waitDuration: onLongPress != null ? const Duration(milliseconds: 500) : const Duration(days: 1), child: CompositedTransformTarget( link: MatrixState.pAnyState .layerLinkAndKey("${entry.value.text}$id") .link, child: ChoiceAnimationWidget( isSelected: isSelected, isCorrect: entry.value.isGold, key: MatrixState.pAnyState .layerLinkAndKey("${entry.value.text}$id") .key, child: Container( margin: const EdgeInsets.all(2), padding: EdgeInsets.zero, decoration: BoxDecoration( borderRadius: const BorderRadius.all( Radius.circular(AppConfig.borderRadius), ), border: Border.all( color: isSelected ? entry.value.color ?? theme.colorScheme.primary : Colors.transparent, style: BorderStyle.solid, width: 2.0, ), ), child: TextButton( style: TextButton.styleFrom( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 4, ), backgroundColor: entry.value.color?.withAlpha(50) ?? theme.colorScheme.primary.withAlpha(10), textStyle: BotStyle.text(context), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(AppConfig.borderRadius), ), ), onLongPress: onLongPress != null ? () => onLongPress!(entry.value.text, entry.key) : null, onPressed: () => onPressed(entry.value.text, entry.key), child: Text( getDisplayCopy != null ? getDisplayCopy!(entry.value.text) : entry.value.text, style: BotStyle.text(context).copyWith( fontSize: fontSize, ), textAlign: TextAlign.center, ), ), ), ), ), ); } }