Merge branch 'main' into 3148-when-bot-responds-in-an-audio-message-play-that-message-without-a-click

pull/2245/head
ggurdin 5 months ago
commit ddbb63f815
No known key found for this signature in database
GPG Key ID: A01CB41737CBB478

@ -54,7 +54,7 @@ abstract class AppConfig {
final bigEmotes = event != null &&
event.onlyEmotes &&
event.numberEmotes > 0 &&
event.numberEmotes <= 10;
event.numberEmotes <= 3;
return TextStyle(
color: textColor,
@ -78,6 +78,10 @@ abstract class AppConfig {
static const Color silver = Color.fromARGB(255, 192, 192, 192);
static const Color bronze = Color.fromARGB(255, 205, 127, 50);
static const Color goldLight = Color.fromARGB(255, 254, 223, 73);
static const Color yellowLight = Color.fromARGB(255, 247, 218, 120);
static const Color yellowDark = Color.fromARGB(255, 253, 191, 1);
static const Color error = Colors.red;
static const int overlayAnimationDuration = 250;
static const int roomCreationTimeoutSeconds = 15;

@ -308,19 +308,34 @@ class AudioPlayerState extends State<AudioPlayerWidget> {
final audioPlayer = matrix.audioPlayer;
if (audioPlayer == null) return;
switch (audioPlayer.speed) {
// #Pangea
// case 1.0:
// await audioPlayer.setSpeed(1.25);
// break;
// case 1.25:
// await audioPlayer.setSpeed(1.5);
// break;
// case 1.5:
// await audioPlayer.setSpeed(2.0);
// break;
// case 2.0:
// await audioPlayer.setSpeed(0.5);
// break;
// case 0.5:
case 1.0:
await audioPlayer.setSpeed(0.75);
break;
case 0.75:
await audioPlayer.setSpeed(0.5);
break;
case 0.5:
await audioPlayer.setSpeed(1.25);
break;
case 1.25:
await audioPlayer.setSpeed(1.5);
break;
case 1.5:
await audioPlayer.setSpeed(2.0);
break;
case 2.0:
await audioPlayer.setSpeed(0.5);
break;
case 0.5:
// Pangea#
default:
await audioPlayer.setSpeed(1.0);
break;

@ -40,14 +40,17 @@ class RecordingDialogState extends State<RecordingDialog> {
Future<void> startRecording() async {
final store = Matrix.of(context).store;
try {
final codec = kIsWeb
// Web seems to create webm instead of ogg when using opus encoder
// which does not play on iOS right now. So we use wav for now:
? AudioEncoder.wav
// Everywhere else we use opus if supported by the platform:
: await _audioRecorder.isEncoderSupported(AudioEncoder.opus)
? AudioEncoder.opus
: AudioEncoder.aacLc;
// #Pangea
// final codec = kIsWeb
// // Web seems to create webm instead of ogg when using opus encoder
// // which does not play on iOS right now. So we use wav for now:
// ? AudioEncoder.wav
// // Everywhere else we use opus if supported by the platform:
// : await _audioRecorder.isEncoderSupported(AudioEncoder.opus)
// ? AudioEncoder.opus
// : AudioEncoder.aacLc;
const codec = AudioEncoder.wav;
// Pangea#
fileName =
'recording${DateTime.now().microsecondsSinceEpoch}.${codec.fileExtension}';
String? path;

@ -132,8 +132,6 @@ class VocabDetailsView extends StatelessWidget {
: LemmaMeaningWidget(
constructUse: _construct,
langCode: _userL2!,
controller: null,
token: null,
style: Theme.of(context).textTheme.bodyLarge,
leading: TextSpan(
text: L10n.of(context).meaningSectionHeader,

@ -246,7 +246,7 @@ class TransparentBackdropState extends State<TransparentBackdrop>
curve: FluffyThemes.animationCurve,
),
);
_blurTween = Tween<double>(begin: 0.0, end: 2.5).animate(
_blurTween = Tween<double>(begin: 0.0, end: 3.0).animate(
CurvedAnimation(
parent: _controller,
curve: FluffyThemes.animationCurve,

@ -550,7 +550,7 @@ class MessageSelectionPositionerState extends State<MessageSelectionPositioner>
return 0.8;
case ReadingAssistanceMode.selectMode:
case null:
return 0.4;
return 0.6;
}
}

@ -13,6 +13,7 @@ class WordAudioButton extends StatefulWidget {
final String uniqueID;
final String langCode;
final EdgeInsets? padding;
final double? iconSize;
/// If defined, this callback will be called instead of the default one
final void Function()? callbackOverride;
@ -26,6 +27,7 @@ class WordAudioButton extends StatefulWidget {
this.baseOpacity = 1,
this.callbackOverride,
this.padding,
this.iconSize,
});
@override
@ -118,6 +120,7 @@ class WordAudioButtonState extends State<WordAudioButton> {
color: _isPlaying
? Theme.of(context).colorScheme.primary
: null,
size: widget.iconSize,
),
),
),

@ -0,0 +1,107 @@
import 'package:flutter/material.dart';
import 'package:fluffychat/pangea/constructs/construct_identifier.dart';
import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart';
import 'package:fluffychat/pangea/lemmas/lemma_info_repo.dart';
import 'package:fluffychat/pangea/lemmas/lemma_info_request.dart';
import 'package:fluffychat/pangea/lemmas/lemma_info_response.dart';
import 'package:fluffychat/widgets/matrix.dart';
class LemmaMeaningBuilder extends StatefulWidget {
final String langCode;
final ConstructIdentifier constructId;
final Widget Function(
BuildContext context,
LemmaMeaningBuilderState controller,
) builder;
const LemmaMeaningBuilder({
super.key,
required this.langCode,
required this.constructId,
required this.builder,
});
@override
LemmaMeaningBuilderState createState() => LemmaMeaningBuilderState();
}
class LemmaMeaningBuilderState extends State<LemmaMeaningBuilder> {
bool editMode = false;
LemmaInfoResponse? lemmaInfo;
bool isLoading = true;
String? error;
TextEditingController controller = TextEditingController();
@override
void initState() {
super.initState();
_fetchLemmaMeaning();
}
@override
void didUpdateWidget(covariant LemmaMeaningBuilder oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.constructId != widget.constructId ||
oldWidget.langCode != widget.langCode) {
_fetchLemmaMeaning();
}
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
LemmaInfoRequest get _request => LemmaInfoRequest(
lemma: widget.constructId.lemma,
partOfSpeech: widget.constructId.category,
lemmaLang: widget.langCode,
userL1:
MatrixState.pangeaController.languageController.userL1?.langCode ??
LanguageKeys.defaultLanguage,
);
Future<void> _fetchLemmaMeaning() async {
setState(() {
isLoading = true;
error = null;
});
try {
final resp = await LemmaInfoRepo.get(_request);
lemmaInfo = resp;
controller.text = resp.meaning;
} catch (e) {
error = e.toString();
} finally {
if (mounted) setState(() => isLoading = false);
}
}
void toggleEditMode(bool value) => setState(() => editMode = value);
Future<void> editLemmaMeaning(String userEdit) async {
final originalMeaning = lemmaInfo;
if (originalMeaning != null) {
LemmaInfoRepo.set(
_request,
LemmaInfoResponse(emoji: originalMeaning.emoji, meaning: userEdit),
);
toggleEditMode(false);
_fetchLemmaMeaning();
}
}
@override
Widget build(BuildContext context) {
return widget.builder(
context,
this,
);
}
}

@ -3,250 +3,138 @@ import 'dart:developer';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_use_model.dart';
import 'package:fluffychat/pangea/analytics_misc/text_loading_shimmer.dart';
import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart';
import 'package:fluffychat/pangea/lemmas/lemma_info_repo.dart';
import 'package:fluffychat/pangea/lemmas/lemma_info_request.dart';
import 'package:fluffychat/pangea/lemmas/lemma_info_response.dart';
import 'package:fluffychat/pangea/practice_activities/activity_type_enum.dart';
import 'package:fluffychat/pangea/toolbar/enums/message_mode_enum.dart';
import 'package:fluffychat/pangea/toolbar/enums/reading_assistance_mode_enum.dart';
import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart';
import 'package:fluffychat/pangea/toolbar/widgets/practice_activity/word_zoom_activity_button.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:fluffychat/pangea/toolbar/widgets/word_zoom/lemma_meaning_builder.dart';
class LemmaMeaningWidget extends StatefulWidget {
class LemmaMeaningWidget extends StatelessWidget {
final ConstructUses constructUse;
final String langCode;
final TextStyle? style;
final InlineSpan? leading;
/// These are not present if this widget is used outside the chat
/// (e.g. in the vocab details view)
/// TODO: let the user assign the meaning in the vocab details view
final MessageOverlayController? controller;
final PangeaToken? token;
const LemmaMeaningWidget({
super.key,
required this.constructUse,
required this.langCode,
required this.controller,
required this.token,
this.style,
this.leading,
});
@override
LemmaMeaningWidgetState createState() => LemmaMeaningWidgetState();
}
class LemmaMeaningWidgetState extends State<LemmaMeaningWidget> {
bool _editMode = false;
late TextEditingController _controller;
LemmaInfoResponse? _lemmaInfo;
bool _isLoading = true;
String? _error;
String get _lemma => widget.constructUse.lemma;
@override
void initState() {
super.initState();
_controller = TextEditingController();
_fetchLemmaMeaning();
}
@override
void didUpdateWidget(covariant LemmaMeaningWidget oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.constructUse != widget.constructUse ||
oldWidget.langCode != widget.langCode) {
_fetchLemmaMeaning();
}
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
LemmaInfoRequest get _request => LemmaInfoRequest(
lemma: _lemma,
partOfSpeech: widget.constructUse.category,
/// This assumes that the user's L2 is the language of the lemma
lemmaLang: widget.langCode,
userL1:
MatrixState.pangeaController.languageController.userL1?.langCode ??
LanguageKeys.defaultLanguage,
);
Future<void> _fetchLemmaMeaning() async {
setState(() {
_isLoading = true;
_error = null;
});
try {
_lemmaInfo = await LemmaInfoRepo.get(_request);
} catch (e) {
_error = e.toString();
} finally {
if (mounted) setState(() => _isLoading = false);
}
}
void _toggleEditMode(bool value) => setState(() => _editMode = value);
Future<void> editLemmaMeaning(String userEdit) async {
final originalMeaning = _lemmaInfo;
if (originalMeaning != null) {
LemmaInfoRepo.set(
_request,
LemmaInfoResponse(emoji: originalMeaning.emoji, meaning: userEdit),
);
_toggleEditMode(false);
_fetchLemmaMeaning();
}
}
@override
Widget build(BuildContext context) {
if (widget.token != null &&
widget.controller?.practiceSelection != null &&
widget.controller!.practiceSelection!.hasActiveActivityByToken(
ActivityTypeEnum.wordMeaning,
widget.token!,
) &&
widget.controller!.readingAssistanceMode ==
ReadingAssistanceMode.practiceMode) {
return WordZoomActivityButton(
icon: const Icon(Symbols.dictionary),
isSelected: widget.controller?.toolbarMode == MessageMode.wordMeaning,
onPressed: widget.controller != null
? () {
// TODO: it would be better to explicitly set to wordMeaningChoice here
widget.controller!.updateToolbarMode(MessageMode.wordMeaning);
}
: () => {},
opacity:
widget.controller?.toolbarMode == MessageMode.wordMeaning ? 1 : 0.4,
);
}
if (_isLoading) {
return const TextLoadingShimmer();
}
if (_error != null) {
debugger(when: kDebugMode);
return Text(
L10n.of(context).oopsSomethingWentWrong,
textAlign: TextAlign.center,
);
}
if (_editMode) {
_controller.text = _lemmaInfo?.meaning ?? "";
return Material(
type: MaterialType.transparency,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
"${L10n.of(context).pangeaBotIsFallible} ${L10n.of(context).whatIsMeaning(_lemma, widget.constructUse.category)}",
textAlign: TextAlign.center,
style: const TextStyle(fontStyle: FontStyle.italic),
),
const SizedBox(height: 10),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: TextField(
minLines: 1,
maxLines: 3,
controller: _controller,
decoration: InputDecoration(
hintText: _lemmaInfo?.meaning,
),
),
),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.center,
return LemmaMeaningBuilder(
langCode: langCode,
constructId: constructUse.id,
builder: (context, controller) {
if (controller.isLoading) {
return const TextLoadingShimmer();
}
if (controller.error != null) {
debugger(when: kDebugMode);
return Text(
L10n.of(context).oopsSomethingWentWrong,
textAlign: TextAlign.center,
);
}
if (controller.editMode) {
controller.controller.text = controller.lemmaInfo?.meaning ?? "";
return Material(
type: MaterialType.transparency,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ElevatedButton(
onPressed: () => _toggleEditMode(false),
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
Text(
"${L10n.of(context).pangeaBotIsFallible} ${L10n.of(context).whatIsMeaning(
constructUse.lemma,
constructUse.category,
)}",
textAlign: TextAlign.center,
style: const TextStyle(fontStyle: FontStyle.italic),
),
const SizedBox(height: 10),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: TextField(
minLines: 1,
maxLines: 3,
controller: controller.controller,
decoration: InputDecoration(
hintText: controller.lemmaInfo?.meaning,
),
padding: const EdgeInsets.symmetric(horizontal: 10),
),
child: Text(L10n.of(context).cancel),
),
const SizedBox(width: 10),
ElevatedButton(
onPressed: () => _controller.text != _lemmaInfo?.meaning &&
_controller.text.isNotEmpty
? editLemmaMeaning(_controller.text)
: null,
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () => controller.toggleEditMode(false),
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
),
padding: const EdgeInsets.symmetric(horizontal: 10),
),
child: Text(L10n.of(context).cancel),
),
padding: const EdgeInsets.symmetric(horizontal: 10),
),
child: Text(L10n.of(context).saveChanges),
const SizedBox(width: 10),
ElevatedButton(
onPressed: () => controller.controller.text !=
controller.lemmaInfo?.meaning &&
controller.controller.text.isNotEmpty
? controller
.editLemmaMeaning(controller.controller.text)
: null,
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
),
padding: const EdgeInsets.symmetric(horizontal: 10),
),
child: Text(L10n.of(context).saveChanges),
),
],
),
],
),
],
),
);
}
);
}
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Flexible(
child: Tooltip(
triggerMode: TooltipTriggerMode.tap,
message: L10n.of(context).doubleClickToEdit,
child: GestureDetector(
onLongPress: () => _toggleEditMode(true),
onDoubleTap: () => _toggleEditMode(true),
child: RichText(
textAlign:
widget.leading == null ? TextAlign.center : TextAlign.start,
text: TextSpan(
style: widget.style?.copyWith(
color: widget.controller?.toolbarMode ==
MessageMode.wordMeaning
? Theme.of(context).colorScheme.primary
: null,
),
children: [
if (widget.leading != null) widget.leading!,
if (widget.leading != null)
const WidgetSpan(child: SizedBox(width: 6.0)),
TextSpan(
text: _lemmaInfo?.meaning,
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Flexible(
child: Tooltip(
triggerMode: TooltipTriggerMode.tap,
message: L10n.of(context).doubleClickToEdit,
child: GestureDetector(
onLongPress: () => controller.toggleEditMode(true),
onDoubleTap: () => controller.toggleEditMode(true),
child: RichText(
textAlign:
leading == null ? TextAlign.center : TextAlign.start,
text: TextSpan(
style: style,
children: [
if (leading != null) leading!,
if (leading != null)
const WidgetSpan(child: SizedBox(width: 6.0)),
TextSpan(
text: controller.lemmaInfo?.meaning,
),
],
),
],
),
),
),
),
),
),
],
],
);
},
);
}
}

@ -1,191 +0,0 @@
import 'package:flutter/material.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/pangea/common/constants/model_keys.dart';
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart';
import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
import 'package:fluffychat/pangea/events/models/tokens_event_content_model.dart';
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart';
import 'package:fluffychat/pangea/toolbar/widgets/practice_activity/word_audio_button.dart';
import 'package:fluffychat/widgets/future_loading_dialog.dart';
class LemmaWidget extends StatefulWidget {
final PangeaToken token;
final PangeaMessageEvent pangeaMessageEvent;
final VoidCallback onEdit;
final VoidCallback onEditDone;
final MessageOverlayController? overlayController;
const LemmaWidget({
super.key,
required this.token,
required this.pangeaMessageEvent,
required this.onEdit,
required this.onEditDone,
required this.overlayController,
});
@override
LemmaWidgetState createState() => LemmaWidgetState();
}
class LemmaWidgetState extends State<LemmaWidget> {
bool _editMode = false;
late TextEditingController _controller;
@override
void initState() {
super.initState();
_controller = TextEditingController();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
void _toggleEditMode(bool value) {
value ? widget.onEdit() : widget.onEditDone();
setState(() => _editMode = value);
}
Future<void> _editLemma() async {
try {
final existingTokens = widget.pangeaMessageEvent.originalSent!.tokens!
.map((token) => PangeaToken.fromJson(token.toJson()))
.toList();
// change the morphological tag in the selected token
final tokenIndex = existingTokens.indexWhere(
(token) => token.text.offset == widget.token.text.offset,
);
if (tokenIndex == -1) {
throw Exception("Token not found in message");
}
existingTokens[tokenIndex].lemma.text = _controller.text;
await widget.pangeaMessageEvent.room.pangeaSendTextEvent(
widget.pangeaMessageEvent.messageDisplayText,
editEventId: widget.pangeaMessageEvent.eventId,
originalSent: widget.pangeaMessageEvent.originalSent?.content,
originalWritten: widget.pangeaMessageEvent.originalWritten?.content,
tokensSent: PangeaMessageTokens(
tokens: existingTokens,
detections: widget.pangeaMessageEvent.originalSent!.detections,
),
tokensWritten: widget.pangeaMessageEvent.originalWritten?.tokens != null
? PangeaMessageTokens(
tokens: widget.pangeaMessageEvent.originalWritten!.tokens!,
detections:
widget.pangeaMessageEvent.originalWritten?.detections,
)
: null,
choreo: widget.pangeaMessageEvent.originalSent?.choreo,
messageTag: ModelKey.messageTagLemmaEdit,
);
_toggleEditMode(false);
} catch (e) {
SnackBar(
content: Text(L10n.of(context).oopsSomethingWentWrong),
);
ErrorHandler.logError(
e: e,
data: {
"token": widget.token.toJson(),
"pangeaMessageEvent": widget.pangeaMessageEvent.event.content,
},
);
}
}
@override
Widget build(BuildContext context) {
if (_editMode) {
_controller.text = widget.token.lemma.text;
return Material(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
spacing: 10.0,
mainAxisSize: MainAxisSize.min,
children: [
Text(
"${L10n.of(context).pangeaBotIsFallible} ${L10n.of(context).whatIsLemma}",
textAlign: TextAlign.center,
style: const TextStyle(fontStyle: FontStyle.italic),
),
TextField(
minLines: 1,
maxLines: 3,
controller: _controller,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () => _toggleEditMode(false),
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
),
padding: const EdgeInsets.symmetric(horizontal: 10),
),
child: Text(L10n.of(context).cancel),
),
const SizedBox(width: 10),
ElevatedButton(
onPressed: () {
_controller.text != widget.token.lemma.text
? showFutureLoadingDialog(
context: context,
future: () async => _editLemma(),
)
: null;
},
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
),
padding: const EdgeInsets.symmetric(horizontal: 10),
),
child: Text(L10n.of(context).saveChanges),
),
],
),
],
),
),
);
}
return Expanded(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Flexible(
child: Padding(
padding: const EdgeInsets.all(4.0),
child: Text(
widget.token.lemma.text,
style: Theme.of(context).textTheme.headlineSmall,
textAlign: TextAlign.center,
),
),
),
WordAudioButton(
text: widget.token.lemma.text,
baseOpacity: 0.4,
uniqueID: "lemma-content-${widget.token.text.content}",
langCode: widget.pangeaMessageEvent.messageDisplayLangCode,
padding: const EdgeInsets.all(4.0),
),
],
),
);
}
}

@ -1,17 +1,20 @@
import 'package:flutter/material.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/pangea/analytics_details_popup/analytics_details_popup.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart';
import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart';
import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart';
import 'package:fluffychat/pangea/learning_settings/models/language_model.dart';
import 'package:fluffychat/pangea/learning_settings/utils/p_language_store.dart';
import 'package:fluffychat/pangea/lemmas/construct_xp_widget.dart';
import 'package:fluffychat/pangea/lemmas/lemma_reaction_picker.dart';
import 'package:fluffychat/pangea/phonetic_transcription/phonetic_transcription_widget.dart';
import 'package:fluffychat/pangea/practice_activities/activity_type_enum.dart';
import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart';
import 'package:fluffychat/pangea/toolbar/widgets/word_zoom/lemma_meaning_widget.dart';
import 'package:fluffychat/pangea/toolbar/widgets/word_zoom/lemma_widget.dart';
import 'package:fluffychat/pangea/toolbar/widgets/practice_activity/word_audio_button.dart';
import 'package:fluffychat/pangea/toolbar/widgets/word_zoom/lemma_meaning_builder.dart';
import 'package:fluffychat/widgets/matrix.dart';
class WordZoomWidget extends StatelessWidget {
@ -28,8 +31,6 @@ class WordZoomWidget extends StatelessWidget {
PangeaToken get _selectedToken => overlayController.selectedToken!;
void _onEditDone() => overlayController.initializeTokensAndMode();
bool get hasEmojiActivity =>
overlayController.practiceSelection?.hasActiveActivityByToken(
ActivityTypeEnum.emoji,
@ -40,159 +41,199 @@ class WordZoomWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ConstrainedBox(
return Container(
padding: const EdgeInsets.all(12.0),
constraints: const BoxConstraints(
minHeight: AppConfig.toolbarMinHeight,
maxHeight: AppConfig.toolbarMaxHeight,
minHeight: AppConfig.toolbarMinHeight - 8,
maxHeight: AppConfig.toolbarMaxHeight - 8,
maxWidth: AppConfig.toolbarMinWidth,
),
child: SingleChildScrollView(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
spacing: 12.0,
mainAxisSize: MainAxisSize.min,
children: [
Container(
constraints: const BoxConstraints(
minHeight: 40,
),
color: Theme.of(context).colorScheme.surface,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
//@ggurdin - might need to play with size to properly center
SizedBox(
width: 24.0,
height: 24.0,
child: IconButton(
onPressed: () => overlayController.updateSelectedSpan(
Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(
width: 24.0,
height: 24.0,
child: MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
onTap: () => overlayController.updateSelectedSpan(
token.text,
),
icon: const Icon(Icons.close),
style: IconButton.styleFrom(
padding: EdgeInsets.zero,
child: const Icon(
Icons.close,
size: 16.0,
),
),
),
LemmaWidget(
token: _selectedToken,
pangeaMessageEvent: messageEvent,
// onEdit: () => _setHideCenterContent(true),
onEdit: () {
debugPrint("what are we doing edits with?");
},
onEditDone: () {
debugPrint("what are we doing edits with?");
_onEditDone();
},
overlayController: overlayController,
),
Text(
token.text.content,
style: TextStyle(
fontSize: 32.0,
fontWeight: FontWeight.w600,
height: 1.2,
color: Theme.of(context).brightness == Brightness.light
? AppConfig.yellowDark
: AppConfig.yellowLight,
),
ConstructXpWidget(
id: token.vocabConstructID,
onTap: () => showDialog<AnalyticsPopupWrapper>(
context: context,
builder: (context) => AnalyticsPopupWrapper(
constructZoom: token.vocabConstructID,
view: ConstructTypeEnum.vocab,
),
),
ConstructXpWidget(
id: token.vocabConstructID,
onTap: () => showDialog<AnalyticsPopupWrapper>(
context: context,
builder: (context) => AnalyticsPopupWrapper(
constructZoom: token.vocabConstructID,
view: ConstructTypeEnum.vocab,
),
),
],
),
),
const SizedBox(
height: 8.0,
),
LemmaReactionPicker(
cId: _selectedToken.vocabConstructID,
controller: overlayController.widget.chatController,
),
],
),
// Row(
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// Container(
// constraints: const BoxConstraints(
// minHeight: 40,
// ),
// alignment: Alignment.center,
// child: LemmaEmojiRow(
// cId: _selectedToken.vocabConstructID,
// onTapOverride: overlayController.hideWordCardContent &&
// hasEmojiActivity
// ? () => overlayController.updateToolbarMode(
// MessageMode.wordEmoji,
// )
// : null,
// isSelected:
// overlayController.toolbarMode == MessageMode.wordEmoji,
// emojiSetCallback: () => overlayController.setState(() {}),
// shouldShowEmojis: !hasEmojiActivity,
// ),
// ),
// ],
// ),
const SizedBox(
height: 8.0,
),
Container(
constraints: const BoxConstraints(
minHeight: 40,
),
alignment: Alignment.center,
child: Wrap(
alignment: WrapAlignment.center,
runAlignment: WrapAlignment.center,
crossAxisAlignment: WrapCrossAlignment.center,
spacing: 8,
children: [
LemmaMeaningWidget(
constructUse: token.vocabConstructID.constructUses,
langCode: MatrixState.pangeaController.languageController
.userL2?.langCodeShort ??
LanguageKeys.defaultLanguage,
token: overlayController.selectedToken!,
controller: overlayController,
style: Theme.of(context).textTheme.bodyLarge,
),
],
),
LemmaMeaningBuilder(
langCode: messageEvent.messageDisplayLangCode,
constructId: token.vocabConstructID,
builder: (context, controller) {
if (controller.editMode) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
"${L10n.of(context).pangeaBotIsFallible} ${L10n.of(context).whatIsMeaning(
token.vocabConstructID.lemma,
token.vocabConstructID.category,
)}",
textAlign: TextAlign.center,
style: const TextStyle(fontStyle: FontStyle.italic),
),
const SizedBox(height: 10),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: TextField(
minLines: 1,
maxLines: 3,
controller: controller.controller,
decoration: InputDecoration(
hintText: controller.lemmaInfo?.meaning,
),
),
),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () => controller.toggleEditMode(false),
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
),
padding:
const EdgeInsets.symmetric(horizontal: 10),
),
child: Text(L10n.of(context).cancel),
),
const SizedBox(width: 10),
ElevatedButton(
onPressed: () => controller.controller.text !=
controller.lemmaInfo?.meaning &&
controller.controller.text.isNotEmpty
? controller.editLemmaMeaning(
controller.controller.text,
)
: null,
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
),
padding:
const EdgeInsets.symmetric(horizontal: 10),
),
child: Text(L10n.of(context).saveChanges),
),
],
),
],
);
}
return Column(
spacing: 12.0,
mainAxisSize: MainAxisSize.min,
children: [
if (MatrixState
.pangeaController.languageController.showTrancription)
PhoneticTranscriptionWidget(
text: token.text.content,
textLanguage: PLanguageStore.byLangCode(
messageEvent.messageDisplayLangCode,
) ??
LanguageModel.unknown,
style: const TextStyle(fontSize: 14.0),
iconSize: 24.0,
)
else
WordAudioButton(
text: token.text.content,
uniqueID: "lemma-content-${token.text.content}",
langCode: messageEvent.messageDisplayLangCode,
iconSize: 24.0,
),
LemmaReactionPicker(
cId: _selectedToken.vocabConstructID,
controller: overlayController.widget.chatController,
),
if (controller.error != null)
Text(
L10n.of(context).oopsSomethingWentWrong,
textAlign: TextAlign.center,
)
else if (controller.isLoading ||
controller.lemmaInfo == null)
const CircularProgressIndicator.adaptive()
else
GestureDetector(
onLongPress: () => controller.toggleEditMode(true),
onDoubleTap: () => controller.toggleEditMode(true),
child: token.lemma.text == token.text.content
? Text(
controller.lemmaInfo!.meaning,
style: const TextStyle(fontSize: 14.0),
textAlign: TextAlign.center,
)
: RichText(
text: TextSpan(
style: DefaultTextStyle.of(context)
.style
.copyWith(
fontSize: 14.0,
),
children: [
TextSpan(text: token.lemma.text),
const WidgetSpan(
child: SizedBox(width: 8.0),
),
const TextSpan(text: ":"),
const WidgetSpan(
child: SizedBox(width: 8.0),
),
TextSpan(
text: controller.lemmaInfo!.meaning,
),
],
),
),
),
],
);
},
),
// const SizedBox(
// height: 8.0,
// ),
// Wrap(
// alignment: WrapAlignment.center,
// crossAxisAlignment: WrapCrossAlignment.center,
// spacing: 8.0,
// children: [
// if (token.text.content.toLowerCase() !=
// token.lemma.text.toLowerCase()) ...[
// Text(
// _selectedToken.text.content,
// style: Theme.of(context).textTheme.bodyLarge,
// overflow: TextOverflow.ellipsis,
// ),
// WordAudioButton(
// text: _selectedToken.text.content,
// baseOpacity: 0.4,
// uniqueID: "word-zoom-audio-${_selectedToken.text.content}",
// langCode: overlayController
// .pangeaMessageEvent!.messageDisplayLangCode,
// ),
// ],
// ..._selectedToken.morphsBasicallyEligibleForPracticeByPriority
// .map(
// (cId) => MorphologicalListItem(
// morphFeature: MorphFeaturesEnumExtension.fromString(
// cId.category,
// ),
// token: _selectedToken,
// overlayController: overlayController,
// ),
// ),
// ],
// ),
],
),
),

Loading…
Cancel
Save