exit practice if no activities to do

pull/1384/head
William Jordan-Cooley 1 year ago
parent 7c61ee7026
commit f589d2371b

@ -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'

@ -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);
}

@ -76,7 +76,7 @@ class PracticeGenerationController {
);
}
Future<PracticeActivityModel> _fetch({
Future<MessageActivityResponse> _fetch({
required String accessToken,
required MessageActivityRequest requestModel,
}) async {
@ -92,7 +92,7 @@ class PracticeGenerationController {
if (res.statusCode == 200) {
final Map<String, dynamic> 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<PracticeActivityEvent?> 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<PracticeActivityEvent?> eventFuture =
_sendAndPackageEvent(activity, event);
_sendAndPackageEvent(res.activity!, event);
_cache[cacheKey] =
_RequestCacheItem(req: req, practiceActivityEvent: eventFuture);

@ -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);
}

@ -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<PangeaToken> 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"

@ -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<String, dynamic> json) {
return MessageActivityResponse(
activity: json['activity'] != null
? PracticeActivityModel.fromJson(
json['activity'] as Map<String, dynamic>,
)
: null,
finished: json['finished'] as bool,
);
}
Map<String, dynamic> toJson() {
return {
'activity': activity?.toJson(),
'finished': finished,
};
}
}

@ -52,6 +52,7 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
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<MessageSelectionOverlay>
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<MessageSelectionOverlay>
}
Future<void> 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<MessageSelectionOverlay>
toolbarMode = MessageMode.textToSpeech;
return;
}
toolbarMode = MessageMode.translation;
setState(() {});
}

@ -250,7 +250,10 @@ class ToolbarButtonsState extends State<ToolbarButtons> {
.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<ToolbarButtons> {
@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<ToolbarButtons> {
widget.messageToolbarController.widget
.overLayController.toolbarMode,
pangeaMessageEvent.numberOfActivitiesCompleted,
widget.messageToolbarController.widget
.overLayController.isPracticeComplete,
),
),
),
onPressed: mode.isUnlocked(
index,
pangeaMessageEvent.numberOfActivitiesCompleted,
overlayController.isPracticeComplete,
)
? () => widget
.messageToolbarController.widget.overLayController

@ -110,6 +110,7 @@ class MessagePracticeActivityCardState extends State<PracticeActivityCard> {
if (targetTokens.isEmpty ||
!pangeaController.languageController.languagesSet) {
debugger(when: kDebugMode);
updateFetchingActivity(false);
return null;
}
@ -146,13 +147,6 @@ class MessagePracticeActivityCardState extends State<PracticeActivityCard> {
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<List<TokenWithXP>> getTargetTokens() async {
@ -226,6 +220,7 @@ class MessagePracticeActivityCardState extends State<PracticeActivityCard> {
/// future that simply waits for the appropriate time to savor the joy
Future<void> savorTheJoy() async {
joyTimer?.cancel();
if (savoringTheJoy) return;
savoringTheJoy = true;
joyTimer = Timer(appropriateTimeForJoy, () {
@ -245,13 +240,8 @@ class MessagePracticeActivityCardState extends State<PracticeActivityCard> {
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<PracticeActivityCard> {
}
}
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<PracticeActivityCard> {
}
}
@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<PracticeActivityCard> {
minHeight: 80,
),
child: Text(
userMessage,
userMessage!,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
@ -352,11 +358,10 @@ class MessagePracticeActivityCardState extends State<PracticeActivityCard> {
}
return Stack(
alignment: Alignment.topCenter,
alignment: Alignment.center,
children: [
// Main content
const Positioned(
top: 40,
child: PointsGainedAnimation(),
),
Column(

Loading…
Cancel
Save