add translations for error questions
and some spacing tweaks to improve layout and overflow issues
This commit is contained in:
parent
be9ef801a9
commit
0068ef5965
5 changed files with 241 additions and 49 deletions
|
|
@ -1,7 +1,5 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/text_loading_shimmer.dart';
|
||||
import 'package:fluffychat/pangea/languages/language_constants.dart';
|
||||
|
|
@ -11,17 +9,20 @@ import 'package:fluffychat/pangea/morphs/morph_meaning/morph_info_repo.dart';
|
|||
import 'package:fluffychat/pangea/morphs/morph_meaning/morph_info_request.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class MorphMeaningWidget extends StatefulWidget {
|
||||
final MorphFeaturesEnum feature;
|
||||
final String tag;
|
||||
final TextStyle? style;
|
||||
final bool blankErrorFeedback;
|
||||
|
||||
const MorphMeaningWidget({
|
||||
super.key,
|
||||
required this.feature,
|
||||
required this.tag,
|
||||
this.style,
|
||||
this.blankErrorFeedback = false,
|
||||
});
|
||||
|
||||
@override
|
||||
|
|
@ -91,12 +92,13 @@ class MorphMeaningWidgetState extends State<MorphMeaningWidget> {
|
|||
);
|
||||
|
||||
if (result.isError) {
|
||||
return L10n.of(context).meaningNotFound;
|
||||
return widget.blankErrorFeedback ? '' : L10n.of(context).meaningNotFound;
|
||||
}
|
||||
|
||||
final morph = result.result!.getFeatureByCode(widget.feature.name);
|
||||
final data = morph?.getTagByCode(widget.tag);
|
||||
return data?.l1Description ?? L10n.of(context).meaningNotFound;
|
||||
return data?.l1Description ??
|
||||
(widget.blankErrorFeedback ? '' : L10n.of(context).meaningNotFound);
|
||||
}
|
||||
|
||||
void _toggleEditMode(bool value) => setState(() => _editMode = value);
|
||||
|
|
|
|||
|
|
@ -505,4 +505,5 @@ class AnalyticsPracticeState extends State<AnalyticsPractice>
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) => AnalyticsPracticeView(this);
|
||||
final request = activityTarget.value;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -267,6 +267,7 @@ class AnalyticsPracticeSessionRepo {
|
|||
choreo: choreo,
|
||||
stepIndex: i,
|
||||
eventID: event.eventId,
|
||||
event: event,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/analytics_details_popup/morph_meaning_widget.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart';
|
||||
|
|
@ -74,8 +75,7 @@ class AnalyticsPracticeView extends StatelessWidget {
|
|||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16.0,
|
||||
vertical: 24.0,
|
||||
horizontal: 8.0,
|
||||
),
|
||||
child: MaxWidthBody(
|
||||
withScrolling: false,
|
||||
|
|
@ -123,25 +123,36 @@ class _AnalyticsActivityView extends StatelessWidget {
|
|||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
spacing: 16.0,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable: controller.activityTarget,
|
||||
builder: (context, target, __) => target != null
|
||||
? Column(
|
||||
ValueListenableBuilder(
|
||||
valueListenable: controller.activityTarget,
|
||||
builder: (context, target, __) => target != null
|
||||
? Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 16.0,
|
||||
right: 16.0,
|
||||
top: 16.0,
|
||||
),
|
||||
child: Column(
|
||||
spacing: 12.0,
|
||||
children: [
|
||||
Text(
|
||||
target.promptText(context),
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleLarge
|
||||
?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
style: FluffyThemes.isColumnMode(context)
|
||||
? Theme.of(context)
|
||||
.textTheme
|
||||
.titleLarge
|
||||
?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
)
|
||||
: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium
|
||||
?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
if (controller.widget.type ==
|
||||
ConstructTypeEnum.vocab)
|
||||
|
|
@ -153,22 +164,56 @@ class _AnalyticsActivityView extends StatelessWidget {
|
|||
style: const TextStyle(fontSize: 14.0),
|
||||
),
|
||||
],
|
||||
)
|
||||
: const SizedBox(),
|
||||
),
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Center(
|
||||
Flexible(
|
||||
fit: FlexFit.loose,
|
||||
child: SingleChildScrollView(
|
||||
child: _AnalyticsPracticeCenterContent(
|
||||
controller: controller,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 6,
|
||||
child: _ActivityChoicesWidget(controller),
|
||||
),
|
||||
//reserve space for grammar category morph meaning to avoid shifting, but only in those questions
|
||||
AnimatedBuilder(
|
||||
animation: Listenable.merge([
|
||||
controller.activityState,
|
||||
controller.selectedMorphChoice,
|
||||
]),
|
||||
builder: (context, _) {
|
||||
final activityState = controller.activityState.value;
|
||||
final selectedChoice = controller.selectedMorphChoice.value;
|
||||
|
||||
final isGrammarCategory = activityState
|
||||
is AsyncLoaded<MultipleChoicePracticeActivityModel> &&
|
||||
activityState.value.activityType ==
|
||||
ActivityTypeEnum.grammarCategory;
|
||||
|
||||
if (!isGrammarCategory) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
minHeight: 80,
|
||||
),
|
||||
child: selectedChoice == null
|
||||
? const SizedBox.shrink()
|
||||
: SingleChildScrollView(
|
||||
child: MorphMeaningWidget(
|
||||
feature: selectedChoice.feature,
|
||||
tag: selectedChoice.tag,
|
||||
blankErrorFeedback: true,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
@ -193,8 +238,23 @@ class _AnalyticsPracticeCenterContent extends StatelessWidget {
|
|||
ActivityTypeEnum.grammarError => ValueListenableBuilder(
|
||||
valueListenable: controller.activityState,
|
||||
builder: (context, state, __) => switch (state) {
|
||||
AsyncLoaded(value: final activity) => _ErrorBlankWidget(
|
||||
activity: activity as GrammarErrorPracticeActivityModel,
|
||||
AsyncLoaded(
|
||||
value: final GrammarErrorPracticeActivityModel activity
|
||||
) =>
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_ErrorBlankWidget(
|
||||
activity: activity,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
_GrammarErrorTranslationButton(
|
||||
key: ValueKey(
|
||||
'${activity.eventID}_${activity.errorOffset}_${activity.errorLength}',
|
||||
),
|
||||
controller: controller,
|
||||
),
|
||||
],
|
||||
),
|
||||
_ => const SizedBox(),
|
||||
},
|
||||
|
|
@ -349,11 +409,10 @@ class _ActivityChoicesWidget extends StatelessWidget {
|
|||
|
||||
return Column(
|
||||
children: [
|
||||
Container(
|
||||
constraints: const BoxConstraints(maxHeight: 400.0),
|
||||
Expanded(
|
||||
child: Column(
|
||||
spacing: 4.0,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: choices
|
||||
.map(
|
||||
(choice) => _ChoiceCard(
|
||||
|
|
@ -372,20 +431,6 @@ class _ActivityChoicesWidget extends StatelessWidget {
|
|||
.toList(),
|
||||
),
|
||||
),
|
||||
if (value.activityType == ActivityTypeEnum.grammarCategory)
|
||||
ValueListenableBuilder(
|
||||
valueListenable: controller.selectedMorphChoice,
|
||||
builder: (context, selectedChoice, __) {
|
||||
if (selectedChoice == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return MorphMeaningWidget(
|
||||
feature: selectedChoice.feature,
|
||||
tag: selectedChoice.tag,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
|
|
@ -470,6 +515,7 @@ class _ChoiceCard extends StatelessWidget {
|
|||
tag: choiceText,
|
||||
onPressed: onPressed,
|
||||
isCorrect: isCorrect,
|
||||
height: cardHeight,
|
||||
enabled: enabled,
|
||||
);
|
||||
|
||||
|
|
@ -504,3 +550,144 @@ class _ChoiceCard extends StatelessWidget {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _GrammarErrorTranslationButton extends StatefulWidget {
|
||||
final AnalyticsPracticeState controller;
|
||||
|
||||
const _GrammarErrorTranslationButton({
|
||||
super.key,
|
||||
required this.controller,
|
||||
});
|
||||
|
||||
@override
|
||||
State<_GrammarErrorTranslationButton> createState() =>
|
||||
_GrammarErrorTranslationButtonState();
|
||||
}
|
||||
|
||||
class _GrammarErrorTranslationButtonState
|
||||
extends State<_GrammarErrorTranslationButton> {
|
||||
Future<String>? _translationFuture;
|
||||
bool _showTranslation = false;
|
||||
|
||||
void _toggleTranslation() {
|
||||
if (_showTranslation) {
|
||||
setState(() {
|
||||
_showTranslation = false;
|
||||
_translationFuture = null;
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
_showTranslation = true;
|
||||
_translationFuture = widget.controller.requestTranslation();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Center(
|
||||
child: GestureDetector(
|
||||
onTap: _toggleTranslation,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
spacing: 8.0,
|
||||
children: [
|
||||
if (_showTranslation)
|
||||
Flexible(
|
||||
child: FutureBuilder<String>(
|
||||
future: _translationFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 8,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Color.alphaBlend(
|
||||
Colors.white.withAlpha(180),
|
||||
ThemeData.dark().colorScheme.primary,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: const SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator.adaptive(
|
||||
strokeWidth: 2,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (snapshot.hasError) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 8,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Color.alphaBlend(
|
||||
Colors.white.withAlpha(180),
|
||||
ThemeData.dark().colorScheme.primary,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Text(
|
||||
L10n.of(context).oopsSomethingWentWrong,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onPrimaryFixed,
|
||||
fontSize: AppConfig.fontSizeFactor *
|
||||
AppConfig.messageFontSize,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (snapshot.hasData) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 8,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Color.alphaBlend(
|
||||
Colors.white.withAlpha(180),
|
||||
ThemeData.dark().colorScheme.primary,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Text(
|
||||
snapshot.data!,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onPrimaryFixed,
|
||||
fontSize: AppConfig.fontSizeFactor *
|
||||
AppConfig.messageFontSize,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return const SizedBox();
|
||||
},
|
||||
),
|
||||
),
|
||||
if (!_showTranslation)
|
||||
ElevatedButton(
|
||||
onPressed: _toggleTranslation,
|
||||
style: ElevatedButton.styleFrom(
|
||||
shape: const CircleBorder(),
|
||||
padding: const EdgeInsets.all(8),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.lightbulb_outline,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/choreo_record_model.dart';
|
||||
import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart';
|
||||
import 'package:fluffychat/pangea/morphs/morph_features_enum.dart';
|
||||
import 'package:fluffychat/pangea/practice_activities/activity_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/practice_activities/practice_activity_model.dart';
|
||||
import 'package:fluffychat/pangea/practice_activities/practice_target.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||
|
||||
// includes feedback text and the bad activity model
|
||||
class ActivityQualityFeedback {
|
||||
|
|
@ -45,11 +44,13 @@ class GrammarErrorRequestInfo {
|
|||
final ChoreoRecordModel choreo;
|
||||
final int stepIndex;
|
||||
final String eventID;
|
||||
final PangeaMessageEvent? event;
|
||||
|
||||
const GrammarErrorRequestInfo({
|
||||
required this.choreo,
|
||||
required this.stepIndex,
|
||||
required this.eventID,
|
||||
this.event,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue