exit practice if no activities to do
This commit is contained in:
parent
7c61ee7026
commit
f589d2371b
9 changed files with 128 additions and 39 deletions
16
env.ocal_choreo
Normal file
16
env.ocal_choreo
Normal file
|
|
@ -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> {
|
|||
}
|
||||
}
|
||||
|
||||
String? get userMessage {
|
||||
// if the user has finished all the activities to unlock the toolbar in this session
|
||||
if (widget.overlayController.finishedActivitiesThisSession) {
|
||||
return "Boom! Tools unlocked!";
|
||||
|
||||
// if we have no activities to show
|
||||
} else if (!fetchingActivity && currentActivity == null) {
|
||||
return L10n.of(context)!.noActivitiesFound;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
String? userMessage;
|
||||
if (widget.overlayController.finishedActivitiesThisSession) {
|
||||
userMessage = "Boom! Tools unlocked!";
|
||||
} else if (!fetchingActivity && currentActivity == null) {
|
||||
userMessage = L10n.of(context)!.noActivitiesFound;
|
||||
}
|
||||
|
||||
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…
Add table
Reference in a new issue