refactor: word card redesign
This commit is contained in:
parent
12e66cfcdc
commit
4571349eab
7 changed files with 396 additions and 546 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
107
lib/pangea/toolbar/widgets/word_zoom/lemma_meaning_builder.dart
Normal file
107
lib/pangea/toolbar/widgets/word_zoom/lemma_meaning_builder.dart
Normal file
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
return LemmaMeaningBuilder(
|
||||
langCode: langCode,
|
||||
constructId: constructUse.id,
|
||||
builder: (context, controller) {
|
||||
if (controller.isLoading) {
|
||||
return const TextLoadingShimmer();
|
||||
}
|
||||
|
||||
if (_isLoading) {
|
||||
return const TextLoadingShimmer();
|
||||
}
|
||||
if (controller.error != null) {
|
||||
debugger(when: kDebugMode);
|
||||
return Text(
|
||||
L10n.of(context).oopsSomethingWentWrong,
|
||||
textAlign: TextAlign.center,
|
||||
);
|
||||
}
|
||||
|
||||
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,
|
||||
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),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
),
|
||||
child: Text(L10n.of(context).cancel),
|
||||
Text(
|
||||
"${L10n.of(context).pangeaBotIsFallible} ${L10n.of(context).whatIsMeaning(
|
||||
constructUse.lemma,
|
||||
constructUse.category,
|
||||
)}",
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(fontStyle: FontStyle.italic),
|
||||
),
|
||||
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),
|
||||
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).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,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (widget.leading != null) widget.leading!,
|
||||
if (widget.leading != null)
|
||||
const WidgetSpan(child: SizedBox(width: 6.0)),
|
||||
TextSpan(
|
||||
text: _lemmaInfo?.meaning,
|
||||
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 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,
|
||||
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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// 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…
Add table
Reference in a new issue