diff --git a/env.ocal_choreo b/env.ocal_choreo new file mode 100644 index 000000000..36c87c253 --- /dev/null +++ b/env.ocal_choreo @@ -0,0 +1,16 @@ +BASE_API='https://api.staging.pangea.chat/api/v1' +CHOREO_API = "http://localhost:8000/choreo" +FRONTEND_URL='https://app.pangea.chat' + +SYNAPSE_URL = 'matrix.staging.pangea.chat' +CHOREO_API_KEY = 'e6fa9fa97031ba0c852efe78457922f278a2fbc109752fe18e465337699e9873' + +RC_PROJECT = 'a499dc21' +RC_KEY = 'sk_eVGBdPyInaOfJrKlPBgFVnRynqKJB' + +RC_GOOGLE_KEY = 'goog_paQMrzFKGzuWZvcMTPkkvIsifJe' +RC_IOS_KEY = 'appl_DUPqnxuLjkBLzhBPTWeDjqNENuv' +RC_STRIPE_KEY = 'strp_YWZxWUeEfvagiefDNoofinaRCOl' +RC_OFFERING_NAME = 'test' + +STRIPE_MANAGEMENT_LINK = 'https://billing.stripe.com/p/login/test_9AQaI8d3O9lmaXe5kk' \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 6be6edc91..36add47dc 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -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); } diff --git a/lib/pangea/controllers/practice_activity_generation_controller.dart b/lib/pangea/controllers/practice_activity_generation_controller.dart index b487dc726..fe2ebb388 100644 --- a/lib/pangea/controllers/practice_activity_generation_controller.dart +++ b/lib/pangea/controllers/practice_activity_generation_controller.dart @@ -76,7 +76,7 @@ class PracticeGenerationController { ); } - Future _fetch({ + Future _fetch({ required String accessToken, required MessageActivityRequest requestModel, }) async { @@ -92,7 +92,7 @@ class PracticeGenerationController { if (res.statusCode == 200) { final Map json = jsonDecode(utf8.decode(res.bodyBytes)); - final response = PracticeActivityModel.fromJson(json); + final response = MessageActivityResponse.fromJson(json); return response; } else { @@ -101,6 +101,8 @@ class PracticeGenerationController { } } + //TODO - allow return of activity content before sending the event + // this requires some downstream changes to the way the event is handled Future getPracticeActivity( MessageActivityRequest req, PangeaMessageEvent event, @@ -112,13 +114,17 @@ class PracticeGenerationController { } else { //TODO - send request to server/bot, either via API or via event of type pangeaActivityReq // for now, just make and send the event from the client - final PracticeActivityModel activity = await _fetch( + final MessageActivityResponse res = await _fetch( accessToken: _pangeaController.userController.accessToken, requestModel: req, ); + if (res.activity == null) { + return null; + } + final Future eventFuture = - _sendAndPackageEvent(activity, event); + _sendAndPackageEvent(res.activity!, event); _cache[cacheKey] = _RequestCacheItem(req: req, practiceActivityEvent: eventFuture); diff --git a/lib/pangea/enum/message_mode_enum.dart b/lib/pangea/enum/message_mode_enum.dart index 780b8f9b7..c8659f0fc 100644 --- a/lib/pangea/enum/message_mode_enum.dart +++ b/lib/pangea/enum/message_mode_enum.dart @@ -82,17 +82,19 @@ extension MessageModeExtension on MessageMode { bool isUnlocked( int index, int numActivitiesCompleted, + bool totallyDone, ) => - numActivitiesCompleted >= index; + numActivitiesCompleted >= index || totallyDone; Color iconButtonColor( BuildContext context, int index, MessageMode currentMode, int numActivitiesCompleted, + bool totallyDone, ) { //locked - if (!isUnlocked(index, numActivitiesCompleted)) { + if (!isUnlocked(index, numActivitiesCompleted, totallyDone)) { return barAndLockedButtonColor(context); } diff --git a/lib/pangea/models/igc_text_data_model.dart b/lib/pangea/models/igc_text_data_model.dart index be64491c4..180273e30 100644 --- a/lib/pangea/models/igc_text_data_model.dart +++ b/lib/pangea/models/igc_text_data_model.dart @@ -144,15 +144,30 @@ class IGCTextData { pangeaMatch.match.offset + pangeaMatch.match.length, ) + 1; - // replace the tokens in the list - tokens.replaceRange(startIndex, endIndex, replacement.tokens); - //for all tokens after the replacement, update their offsets + // for all tokens after the replacement, update their offsets for (int i = endIndex; i < tokens.length; i++) { final PangeaToken token = tokens[i]; token.text.offset += replacement.value.length - pangeaMatch.match.length; } + // clone the list for debugging purposes + final List newTokens = List.from(tokens); + + // replace the tokens in the list + newTokens.replaceRange(startIndex, endIndex, replacement.tokens); + + final String newFullText = PangeaToken.reconstructText(newTokens); + + if (newFullText != originalInput) { + debugger(when: kDebugMode); + ErrorHandler.logError( + m: "reconstructed text does not match original input", + ); + } + + tokens = newTokens; + //update offsets in existing matches to reflect the change //Question - remove matches that overlap with the accepted one? // see case of "quiero ver un fix" diff --git a/lib/pangea/models/practice_activities.dart/message_activity_request.dart b/lib/pangea/models/practice_activities.dart/message_activity_request.dart index 5639057db..671df8ec6 100644 --- a/lib/pangea/models/practice_activities.dart/message_activity_request.dart +++ b/lib/pangea/models/practice_activities.dart/message_activity_request.dart @@ -148,3 +148,31 @@ class MessageActivityRequest { return messageId.hashCode ^ const ListEquality().hash(tokensWithXP); } } + +class MessageActivityResponse { + final PracticeActivityModel? activity; + final bool finished; + + MessageActivityResponse({ + required this.activity, + required this.finished, + }); + + factory MessageActivityResponse.fromJson(Map json) { + return MessageActivityResponse( + activity: json['activity'] != null + ? PracticeActivityModel.fromJson( + json['activity'] as Map, + ) + : null, + finished: json['finished'] as bool, + ); + } + + Map toJson() { + return { + 'activity': activity?.toJson(), + 'finished': finished, + }; + } +} diff --git a/lib/pangea/widgets/chat/message_selection_overlay.dart b/lib/pangea/widgets/chat/message_selection_overlay.dart index 72d87df4a..e1774ac98 100644 --- a/lib/pangea/widgets/chat/message_selection_overlay.dart +++ b/lib/pangea/widgets/chat/message_selection_overlay.dart @@ -52,6 +52,7 @@ class MessageOverlayController extends State PangeaTokenText? _selectedSpan; /// The number of activities that need to be completed before the toolbar is unlocked + /// If we don't have any good activities for them, we'll decrease this number int needed = 3; /// Whether the user has completed the activities needed to unlock the toolbar @@ -73,6 +74,8 @@ class MessageOverlayController extends State int get activitiesLeftToComplete => needed - widget._pangeaMessageEvent.numberOfActivitiesCompleted; + bool get isPracticeComplete => activitiesLeftToComplete <= 0; + /// In some cases, we need to exit the practice flow and let the user /// interact with the toolbar without completing activities void exitPracticeFlow() { @@ -82,13 +85,13 @@ class MessageOverlayController extends State } Future setInitialToolbarMode() async { - if (activitiesLeftToComplete > 0) { - toolbarMode = MessageMode.practiceActivity; + if (widget._pangeaMessageEvent.isAudioMessage) { + toolbarMode = MessageMode.speechToText; return; } - if (widget._pangeaMessageEvent.isAudioMessage) { - toolbarMode = MessageMode.speechToText; + if (activitiesLeftToComplete > 0) { + toolbarMode = MessageMode.practiceActivity; return; } @@ -97,6 +100,9 @@ class MessageOverlayController extends State toolbarMode = MessageMode.textToSpeech; return; } + + toolbarMode = MessageMode.translation; + setState(() {}); } diff --git a/lib/pangea/widgets/chat/message_toolbar.dart b/lib/pangea/widgets/chat/message_toolbar.dart index 0ade95ab5..deef7f232 100644 --- a/lib/pangea/widgets/chat/message_toolbar.dart +++ b/lib/pangea/widgets/chat/message_toolbar.dart @@ -250,7 +250,10 @@ class ToolbarButtonsState extends State { .toList(); static const double iconWidth = 36.0; - double get progressWidth => widget.width / modes.length; + double get progressWidth => widget.width / overlayController.needed; + + MessageOverlayController get overlayController => + widget.messageToolbarController.widget.overLayController; // @ggurdin - maybe this can be stateless now? @override @@ -260,6 +263,11 @@ class ToolbarButtonsState extends State { @override Widget build(BuildContext context) { + if (widget + .messageToolbarController.widget.pangeaMessageEvent.isAudioMessage) { + return const SizedBox(); + } + return SizedBox( width: widget.width, child: Stack( @@ -313,12 +321,15 @@ class ToolbarButtonsState extends State { widget.messageToolbarController.widget .overLayController.toolbarMode, pangeaMessageEvent.numberOfActivitiesCompleted, + widget.messageToolbarController.widget + .overLayController.isPracticeComplete, ), ), ), onPressed: mode.isUnlocked( index, pangeaMessageEvent.numberOfActivitiesCompleted, + overlayController.isPracticeComplete, ) ? () => widget .messageToolbarController.widget.overLayController diff --git a/lib/pangea/widgets/practice_activity/practice_activity_card.dart b/lib/pangea/widgets/practice_activity/practice_activity_card.dart index 03935060e..d118ca684 100644 --- a/lib/pangea/widgets/practice_activity/practice_activity_card.dart +++ b/lib/pangea/widgets/practice_activity/practice_activity_card.dart @@ -110,6 +110,7 @@ class MessagePracticeActivityCardState extends State { if (targetTokens.isEmpty || !pangeaController.languageController.languagesSet) { debugger(when: kDebugMode); + updateFetchingActivity(false); return null; } @@ -146,13 +147,6 @@ class MessagePracticeActivityCardState extends State { return ourNewActivity; } - RepresentationEvent? get representation => - widget.pangeaMessageEvent.originalSent; - - String get messsageText => representation!.text; - - PangeaController get pangeaController => MatrixState.pangeaController; - /// From the tokens in the message, do a preliminary filtering of which to target /// Then get the construct uses for those tokens Future> getTargetTokens() async { @@ -226,6 +220,7 @@ class MessagePracticeActivityCardState extends State { /// future that simply waits for the appropriate time to savor the joy Future savorTheJoy() async { + joyTimer?.cancel(); if (savoringTheJoy) return; savoringTheJoy = true; joyTimer = Timer(appropriateTimeForJoy, () { @@ -245,13 +240,8 @@ class MessagePracticeActivityCardState extends State { return; } - joyTimer?.cancel(); - savoringTheJoy = true; - joyTimer = Timer(appropriateTimeForJoy, () { - if (!mounted) return; - savoringTheJoy = false; - joyTimer?.cancel(); - }); + // start joy timer + savorTheJoy(); // if this is the last activity, set the flag to true // so we can give them some kudos @@ -299,6 +289,17 @@ class MessagePracticeActivityCardState extends State { } } + RepresentationEvent? get representation => + widget.pangeaMessageEvent.originalSent; + + String get messsageText => representation!.text; + + PangeaController get pangeaController => MatrixState.pangeaController; + + /// The widget that displays the current activity. + /// If there is no current activity, the widget returns a sizedbox with a height of 80. + /// If the activity type is multiple choice, the widget returns a MultipleChoiceActivity. + /// If the activity type is unknown, the widget logs an error and returns a text widget with an error message. Widget get activityWidget { if (currentActivity == null) { // return sizedbox with height of 80 @@ -325,15 +326,20 @@ class MessagePracticeActivityCardState extends State { } } - @override - Widget build(BuildContext context) { - String? userMessage; + String? get userMessage { + // if the user has finished all the activities to unlock the toolbar in this session if (widget.overlayController.finishedActivitiesThisSession) { - userMessage = "Boom! Tools unlocked!"; + return "Boom! Tools unlocked!"; + + // if we have no activities to show } else if (!fetchingActivity && currentActivity == null) { - userMessage = L10n.of(context)!.noActivitiesFound; + return L10n.of(context)!.noActivitiesFound; } + return null; + } + @override + Widget build(BuildContext context) { if (userMessage != null) { return Center( child: Container( @@ -341,7 +347,7 @@ class MessagePracticeActivityCardState extends State { minHeight: 80, ), child: Text( - userMessage, + userMessage!, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, @@ -352,11 +358,10 @@ class MessagePracticeActivityCardState extends State { } return Stack( - alignment: Alignment.topCenter, + alignment: Alignment.center, children: [ // Main content const Positioned( - top: 40, child: PointsGainedAnimation(), ), Column(