several toolbar UI tweaks

pull/1398/head
William Jordan-Cooley 1 year ago
parent b7ab6038ac
commit 240b039ae7

@ -4231,7 +4231,7 @@
"inviteChat": "📨 Invite chat",
"chatName": "Chat name",
"reportContentIssueTitle": "Report content issue",
"feedback": "Your feedback (optional)",
"reportContentIssueDescription": "Sorry! AI can make personalized experiences but also may have issues. Please provide any feedback you have and we'll generate a new activity.",
"feedback": "Optional feedback",
"reportContentIssueDescription": "Uh oh! AI can faciliate personalized learning experiences but... also hallucinates. Please provide any feedback you have and we'll try again.",
"clickTheWordAgainToDeselect": "Click the selected word to deselect it."
}

@ -22,7 +22,7 @@ void main() async {
// #Pangea
try {
await dotenv.load(fileName: ".env");
await dotenv.load(fileName: ".env.local_choreo");
} catch (e) {
Logs().e('Failed to load .env file', e);
}

@ -225,6 +225,7 @@ class OriginalText extends StatelessWidget {
controller.sourceText != null
? Flexible(child: Text(controller.sourceText!))
: const LinearProgressIndicator(),
const SizedBox(width: 4),
if (controller.isEditingSourceText)
Expanded(
child: TextField(
@ -243,7 +244,7 @@ class OriginalText extends StatelessWidget {
if (!controller.isEditingSourceText && controller.sourceText != null)
AnimatedOpacity(
duration: const Duration(milliseconds: 500),
opacity: controller.nextITStep != null ? 1.0 : 0.0,
opacity: controller.nextITStep != null ? 0.7 : 0.0,
child: IconButton(
onPressed: () => {
if (controller.nextITStep != null)
@ -252,6 +253,7 @@ class OriginalText extends StatelessWidget {
},
},
icon: const Icon(Icons.edit_outlined),
iconSize: 20,
),
),
],

@ -113,6 +113,11 @@ class MyAnalyticsController extends BaseController<AnalyticsStream> {
_pangeaController.analytics
.filterConstructs(unfilteredConstructs: constructs)
.then((filtered) {
for (final use in filtered) {
debugPrint(
"_onNewAnalyticsData filtered use: ${use.constructType.string} ${use.useType.string} ${use.lemma} ${use.useType.pointValue}",
);
}
if (filtered.isEmpty) return;
// @ggurdin - are we sure this isn't happening twice? it's also above
@ -166,6 +171,14 @@ class MyAnalyticsController extends BaseController<AnalyticsStream> {
}
}
if (kDebugMode) {
for (final use in uses) {
debugPrint(
"Draft use: ${use.constructType.string} ${use.useType.string} ${use.lemma} ${use.useType.pointValue}",
);
}
}
// @ggurdin - if the point of draft uses is that we don't want to send them twice,
// then, if this is triggered here, couldn't that make a problem?
final level = _pangeaController.analytics.level;
@ -189,6 +202,7 @@ class MyAnalyticsController extends BaseController<AnalyticsStream> {
/// cache of recently sent messages
Future<void> _addLocalMessage(
String eventID,
// @ggurdin - why is this an eventID and not a roomID?
List<OneConstructUse> constructs,
) async {
try {

@ -29,13 +29,14 @@ enum ConstructUseTypeEnum {
/// encountered as distractor in IGC flow and selected it
incIGC,
/// selected correctly in practice activity flow
/// selected correctly in word meaning in context practice activity
corPA,
/// encountered as distractor in practice activity flow and correctly ignored it
/// encountered as distractor in word meaning in context practice activity and correctly ignored it
/// Currently not used
ignPA,
/// was target construct in practice activity but user did not select correctly
/// was target construct in word meaning in context practice activity and incorrectly selected
incPA,
}
@ -125,9 +126,9 @@ extension ConstructUseTypeExtension on ConstructUseTypeEnum {
case ConstructUseTypeEnum.unk:
return 0;
case ConstructUseTypeEnum.corPA:
return 2;
return 5;
case ConstructUseTypeEnum.incPA:
return -1;
return -2;
case ConstructUseTypeEnum.ignPA:
return 1;
}

@ -2,6 +2,7 @@ import 'package:fluffychat/pages/chat/events/audio_player.dart';
import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:fluffychat/pangea/widgets/chat/message_selection_overlay.dart';
import 'package:fluffychat/pangea/widgets/chat/message_toolbar.dart';
import 'package:fluffychat/pangea/widgets/chat/toolbar_content_loading_indicator.dart';
import 'package:fluffychat/pangea/widgets/igc/card_error_widget.dart';
import 'package:flutter/material.dart';
@ -86,6 +87,8 @@ class MessageAudioCardState extends State<MessageAudioCard> {
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(8),
constraints: const BoxConstraints(minHeight: minCardHeight),
alignment: Alignment.center,
child: _isLoading
? const ToolbarContentLoadingIndicator()
: localAudioEvent != null || audioFile != null

@ -16,6 +16,7 @@ import 'package:fluffychat/widgets/avatar.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:matrix/matrix.dart';
class MessageSelectionOverlay extends StatefulWidget {
@ -73,6 +74,26 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
setInitialToolbarMode();
}
/// We need to check if the setState call is safe to call immediately
/// Kept getting the error: setState() or markNeedsBuild() called during build.
/// This is a workaround to prevent that error
@override
void setState(VoidCallback fn) {
if (SchedulerBinding.instance.schedulerPhase == SchedulerPhase.idle ||
SchedulerBinding.instance.schedulerPhase ==
SchedulerPhase.postFrameCallbacks) {
// It's safe to call setState immediately
super.setState(fn);
} else {
// Defer the setState call to after the current frame
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
super.setState(fn);
}
});
}
}
bool get isPracticeComplete => activitiesLeftToComplete <= 0;
/// When an activity is completed, we need to update the state
@ -137,7 +158,8 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
void onClickOverlayMessageToken(
PangeaToken token,
) {
if (toolbarMode == MessageMode.practiceActivity) {
if ([MessageMode.practiceActivity, MessageMode.textToSpeech]
.contains(toolbarMode)) {
return;
}

@ -13,10 +13,13 @@ import 'package:fluffychat/pangea/widgets/chat/message_translation_card.dart';
import 'package:fluffychat/pangea/widgets/chat/message_unsubscribed_card.dart';
import 'package:fluffychat/pangea/widgets/igc/word_data_card.dart';
import 'package:fluffychat/pangea/widgets/practice_activity/practice_activity_card.dart';
import 'package:fluffychat/pangea/widgets/select_to_define.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
const double minCardHeight = 70;
class MessageToolbar extends StatefulWidget {
final PangeaMessageEvent pangeaMessageEvent;
final MessageOverlayController overLayController;

@ -6,6 +6,7 @@ import 'package:fluffychat/pangea/repo/full_text_translation_repo.dart';
import 'package:fluffychat/pangea/utils/bot_style.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:fluffychat/pangea/utils/inline_tooltip.dart';
import 'package:fluffychat/pangea/widgets/chat/message_toolbar.dart';
import 'package:fluffychat/pangea/widgets/chat/toolbar_content_loading_indicator.dart';
import 'package:fluffychat/pangea/widgets/igc/card_error_widget.dart';
import 'package:fluffychat/widgets/matrix.dart';
@ -135,6 +136,8 @@ class MessageTranslationCardState extends State<MessageTranslationCard> {
return Container(
padding: const EdgeInsets.all(8),
constraints: const BoxConstraints(minHeight: minCardHeight),
alignment: Alignment.center,
child: _fetchingTranslation
? const ToolbarContentLoadingIndicator()
: Column(

@ -0,0 +1,89 @@
import 'package:fluffychat/pangea/widgets/common/bot_face_svg.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
class ContentIssueButton extends StatelessWidget {
final bool isActive;
final void Function(String) submitFeedback;
const ContentIssueButton({
super.key,
required this.isActive,
required this.submitFeedback,
});
@override
Widget build(BuildContext context) {
return Opacity(
opacity: 0.8, // Slight opacity
child: Tooltip(
message: L10n.of(context)!.reportContentIssueTitle,
child: IconButton(
icon: const Icon(Icons.flag),
iconSize: 16,
onPressed: () {
if (!isActive) {
return;
}
final TextEditingController feedbackController =
TextEditingController();
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text(
L10n.of(context)!.reportContentIssueTitle,
textAlign: TextAlign.center,
),
content: Container(
constraints: const BoxConstraints(maxWidth: 300),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const BotFace(
width: 60,
expression: BotExpression.addled,
),
const SizedBox(height: 10),
Text(L10n.of(context)!.reportContentIssueDescription),
const SizedBox(height: 10),
SizedBox(
width: double.infinity,
child: TextField(
controller: feedbackController,
decoration: InputDecoration(
labelText: L10n.of(context)!.feedback,
border: const OutlineInputBorder(),
),
maxLines: 4,
),
),
],
),
),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop(); // Close the dialog
},
child: Text(L10n.of(context)!.cancel),
),
ElevatedButton(
onPressed: () {
// Call the additional callback function
submitFeedback(feedbackController.text);
Navigator.of(context).pop(); // Close the dialog
},
child: Text(L10n.of(context)!.submit),
),
],
);
},
);
},
),
),
);
}
}

@ -143,6 +143,8 @@ class SpanCardState extends State<SpanCard> {
}
}
/// @ggurdin - this seems like it would be including the correct answer as well
/// we only want to give this kind of points for ignored distractors
/// Returns the list of choices that are not selected
List<SpanChoice>? get ignoredMatches => widget.scm.pangeaMatch?.match.choices
?.where((choice) => !choice.selected)

@ -7,6 +7,7 @@ import 'package:fluffychat/pangea/models/language_model.dart';
import 'package:fluffychat/pangea/utils/bot_style.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:fluffychat/pangea/utils/firebase_analytics.dart';
import 'package:fluffychat/pangea/widgets/chat/message_toolbar.dart';
import 'package:fluffychat/pangea/widgets/common/p_circular_loader.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/foundation.dart';
@ -176,6 +177,8 @@ class WordDataCardView extends StatelessWidget {
return Container(
padding: const EdgeInsets.all(8),
constraints: const BoxConstraints(minHeight: minCardHeight),
alignment: Alignment.center,
child: Scrollbar(
thumbVisibility: true,
controller: scrollController,
@ -400,25 +403,3 @@ class PartOfSpeechBlock extends StatelessWidget {
);
}
}
class SelectToDefine extends StatelessWidget {
const SelectToDefine({
super.key,
});
@override
Widget build(BuildContext context) {
return Center(
child: Container(
height: 80,
padding: const EdgeInsets.all(8),
child: Center(
child: Text(
L10n.of(context)!.selectToDefine,
style: BotStyle.text(context),
),
),
),
);
}
}

@ -1,3 +1,4 @@
import 'package:fluffychat/pangea/utils/bot_style.dart';
import 'package:flutter/material.dart';
class StarAnimationWidget extends StatefulWidget {
@ -42,8 +43,8 @@ class _StarAnimationWidgetState extends State<StarAnimationWidget>
Widget build(BuildContext context) {
return SizedBox(
// Set constant height and width for the star container
height: 80.0,
width: 80.0,
height: 60.0,
width: 60.0,
child: Center(
child: AnimatedBuilder(
animation: _controller,
@ -74,6 +75,7 @@ class GamifiedTextWidget extends StatelessWidget {
child: Column(
mainAxisSize: MainAxisSize.min, // Adjusts the size to fit children
children: [
const SizedBox(height: 10), // Spacing between the star and text
// Star animation above the text
const StarAnimationWidget(),
const SizedBox(height: 10), // Spacing between the star and text
@ -84,10 +86,7 @@ class GamifiedTextWidget extends StatelessWidget {
padding: const EdgeInsets.all(8),
child: Text(
userMessage,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
style: BotStyle.text(context),
textAlign: TextAlign.center, // Center-align the text
),
),

@ -13,6 +13,8 @@ import 'package:fluffychat/pangea/utils/bot_style.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:fluffychat/pangea/widgets/animations/gain_points.dart';
import 'package:fluffychat/pangea/widgets/chat/message_selection_overlay.dart';
import 'package:fluffychat/pangea/widgets/chat/message_toolbar.dart';
import 'package:fluffychat/pangea/widgets/content_issue_button.dart';
import 'package:fluffychat/pangea/widgets/practice_activity/multiple_choice_activity.dart';
import 'package:fluffychat/pangea/widgets/practice_activity/no_more_practice_card.dart';
import 'package:fluffychat/pangea/widgets/practice_activity/target_tokens_controller.dart';
@ -54,7 +56,7 @@ class MessagePracticeActivityCardState extends State<PracticeActivityCard> {
// Used to show an animation when the user completes an activity
// while simultaneously fetching a new activity and not showing the loading spinner
// until the appropriate time has passed to 'savor the joy'
Duration appropriateTimeForJoy = const Duration(milliseconds: 1000);
Duration appropriateTimeForJoy = const Duration(milliseconds: 1500);
bool savoringTheJoy = false;
@override
@ -65,7 +67,7 @@ class MessagePracticeActivityCardState extends State<PracticeActivityCard> {
void _updateFetchingActivity(bool value) {
if (fetchingActivity == value) return;
setState(() => fetchingActivity = value);
if (mounted) setState(() => fetchingActivity = value);
}
void _setPracticeActivity(PracticeActivityEvent? activity) {
@ -177,19 +179,13 @@ class MessagePracticeActivityCardState extends State<PracticeActivityCard> {
);
Future<void> _savorTheJoy() async {
if (savoringTheJoy) {
//should not happen
debugger(when: kDebugMode);
}
savoringTheJoy = true;
debugger(when: savoringTheJoy && kDebugMode);
debugPrint('Savoring the joy');
setState(() => savoringTheJoy = true);
await Future.delayed(appropriateTimeForJoy);
savoringTheJoy = false;
debugPrint('Savoring the joy is over');
if (mounted) setState(() => savoringTheJoy = false);
}
/// Called when the user finishes an activity.
@ -233,6 +229,7 @@ class MessagePracticeActivityCardState extends State<PracticeActivityCard> {
widget.overlayController.onActivityFinish();
//
final Iterable<dynamic> result = await Future.wait([
_savorTheJoy(),
_fetchNewActivity(),
@ -254,50 +251,6 @@ class MessagePracticeActivityCardState extends State<PracticeActivityCard> {
}
}
void onFlagClick(BuildContext context) {
final TextEditingController feedbackController = TextEditingController();
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text(L10n.of(context)!.reportContentIssueTitle),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(L10n.of(context)!.reportContentIssueDescription),
const SizedBox(height: 10),
TextField(
controller: feedbackController,
decoration: InputDecoration(
labelText: L10n.of(context)!.feedback,
border: const OutlineInputBorder(),
),
maxLines: 3,
),
],
),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop(); // Close the dialog
},
child: Text(L10n.of(context)!.cancel),
),
ElevatedButton(
onPressed: () {
// Call the additional callback function
submitFeedback(feedbackController.text);
Navigator.of(context).pop(); // Close the dialog
},
child: Text(L10n.of(context)!.submit),
),
],
);
},
);
}
/// clear the current activity, record, and selection
/// fetch a new activity, including the offending activity in the request
void submitFeedback(String feedback) {
@ -385,6 +338,7 @@ class MessagePracticeActivityCardState extends State<PracticeActivityCard> {
constraints: const BoxConstraints(
maxWidth: 350,
minWidth: 350,
minHeight: minCardHeight,
),
child: Stack(
alignment: Alignment.center,
@ -412,18 +366,9 @@ class MessagePracticeActivityCardState extends State<PracticeActivityCard> {
Positioned(
top: 0,
right: 0,
child: Opacity(
opacity: 0.65, // Slight opacity
child: Tooltip(
message: L10n.of(context)!.reportContentIssueTitle,
child: IconButton(
padding: const EdgeInsets.all(2),
icon: const Icon(Icons.flag),
iconSize: 16,
onPressed: () =>
currentActivity == null ? null : onFlagClick(context),
),
),
child: ContentIssueButton(
isActive: currentActivity != null,
submitFeedback: submitFeedback,
),
),
],

@ -0,0 +1,26 @@
import 'package:fluffychat/pangea/utils/bot_style.dart';
import 'package:fluffychat/pangea/widgets/chat/message_toolbar.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
class SelectToDefine extends StatelessWidget {
const SelectToDefine({
super.key,
});
@override
Widget build(BuildContext context) {
return Center(
child: Container(
constraints: const BoxConstraints(minHeight: minCardHeight),
padding: const EdgeInsets.all(8),
child: Center(
child: Text(
L10n.of(context)!.selectToDefine,
style: BotStyle.text(context),
),
),
),
);
}
}
Loading…
Cancel
Save