5825 remove unreferenced writing assistance code (#5826)
* chore: delete span details * remove IT * fix null check error * more cleanup
This commit is contained in:
parent
43628427c1
commit
774432ef49
33 changed files with 68 additions and 1842 deletions
|
|
@ -572,7 +572,6 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
|
||||
void _pangeaInit() {
|
||||
choreographer = Choreographer(inputFocus);
|
||||
choreographer.timesDismissedIT.addListener(_onCloseIT);
|
||||
final updater = Matrix.of(context).analyticsDataService.updateDispatcher;
|
||||
|
||||
_levelSubscription = updater.levelUpdateStream.stream.listen(_onLevelUp);
|
||||
|
|
@ -861,7 +860,6 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
_constructsSubscription?.cancel();
|
||||
_tokensSubscription?.cancel();
|
||||
_router.routeInformationProvider.removeListener(_onRouteChanged);
|
||||
choreographer.timesDismissedIT.removeListener(_onCloseIT);
|
||||
scrollController.dispose();
|
||||
inputFocus.dispose();
|
||||
depressMessageButton.dispose();
|
||||
|
|
@ -2287,11 +2285,6 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
return;
|
||||
}
|
||||
|
||||
if (matchToShow.updatedMatch.isITStart) {
|
||||
choreographer.itController.openIT(sendController.text);
|
||||
return;
|
||||
}
|
||||
|
||||
final isSpanCardOpen = MatrixState.pAnyState.isOverlayOpen(
|
||||
overlayKey: 'span-card-overlay',
|
||||
);
|
||||
|
|
@ -2397,12 +2390,6 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
}
|
||||
}
|
||||
|
||||
void _onCloseIT() {
|
||||
if (choreographer.timesDismissedIT.value >= 3) {
|
||||
showDisableLanguageToolsPopup();
|
||||
}
|
||||
}
|
||||
|
||||
void showDisableLanguageToolsPopup() {
|
||||
if (InstructionsEnum.disableLanguageTools.isToggledOff) {
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ import 'package:fluffychat/pangea/activity_sessions/activity_session_start/activ
|
|||
import 'package:fluffychat/pangea/analytics_misc/level_up/star_rain_widget.dart';
|
||||
import 'package:fluffychat/pangea/chat/widgets/chat_floating_action_button.dart';
|
||||
import 'package:fluffychat/pangea/chat/widgets/chat_input_bar.dart';
|
||||
import 'package:fluffychat/pangea/chat/widgets/chat_view_background.dart';
|
||||
import 'package:fluffychat/pangea/common/config/environment.dart';
|
||||
import 'package:fluffychat/pangea/navigation/navigation_util.dart';
|
||||
import 'package:fluffychat/utils/account_config.dart';
|
||||
|
|
@ -474,21 +473,11 @@ class ChatView extends StatelessWidget {
|
|||
// #Pangea
|
||||
// onTap: controller.clearSingleSelectedEvent,
|
||||
// child: ChatEventList(controller: controller),
|
||||
child: Stack(
|
||||
children: [
|
||||
ListenableBuilder(
|
||||
listenable:
|
||||
controller.timelineUpdateNotifier,
|
||||
builder: (context, _) {
|
||||
return ChatEventList(
|
||||
controller: controller,
|
||||
);
|
||||
},
|
||||
),
|
||||
ChatViewBackground(
|
||||
controller.choreographer.itController.open,
|
||||
),
|
||||
],
|
||||
child: ListenableBuilder(
|
||||
listenable: controller.timelineUpdateNotifier,
|
||||
builder: (context, _) {
|
||||
return ChatEventList(controller: controller);
|
||||
},
|
||||
),
|
||||
// Pangea#
|
||||
),
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import 'package:slugify/slugify.dart';
|
|||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/choreo_constants.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/choreo_mode_enum.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/choreographer.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/igc/pangea_match_state_model.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/text_editing/pangea_text_controller.dart';
|
||||
|
|
@ -484,7 +483,6 @@ class InputBar extends StatelessWidget {
|
|||
contextMenuBuilder: (c, e) =>
|
||||
markdownContextBuilder(c, e, controller!),
|
||||
onTap: () => _onInputTap(context),
|
||||
readOnly: choreographer.choreoMode == ChoreoModeEnum.it,
|
||||
autocorrect: MatrixState.pangeaController.userController
|
||||
.isToolEnabled(ToolSetting.enableAutocorrect),
|
||||
// Pangea#
|
||||
|
|
@ -532,9 +530,7 @@ class InputBar extends StatelessWidget {
|
|||
builder: (context, _) => SizedBox(
|
||||
height: 24,
|
||||
child: ShrinkableText(
|
||||
text: choreographer.itController.open.value
|
||||
? L10n.of(context).buildTranslation
|
||||
: _defaultHintText(context),
|
||||
text: _defaultHintText(context),
|
||||
maxWidth: double.infinity,
|
||||
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
||||
color: Theme.of(context).disabledColor,
|
||||
|
|
|
|||
|
|
@ -1,43 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/instructions/instructions_inline_tooltip.dart';
|
||||
|
||||
class ActivityRoleTooltip extends StatelessWidget {
|
||||
final Room room;
|
||||
final ValueNotifier<bool> hide;
|
||||
|
||||
const ActivityRoleTooltip({
|
||||
required this.room,
|
||||
required this.hide,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: hide,
|
||||
builder: (context, hide, _) {
|
||||
if (!room.showActivityChatUI || room.ownRole?.goal == null || hide) {
|
||||
return const SizedBox();
|
||||
}
|
||||
|
||||
return InlineTooltip(
|
||||
message: room.ownRole!.goal!,
|
||||
isClosed: room.hasDismissedGoalTooltip,
|
||||
onClose: () async {
|
||||
await room.dismissGoalTooltip();
|
||||
},
|
||||
padding: EdgeInsets.only(
|
||||
left: 16.0,
|
||||
right: 16.0,
|
||||
top: FluffyThemes.isColumnMode(context) ? 16.0 : 8.0,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -16,7 +16,6 @@ class ChatFloatingActionButton extends StatelessWidget {
|
|||
return ListenableBuilder(
|
||||
listenable: Listenable.merge([
|
||||
controller.choreographer.errorService,
|
||||
controller.choreographer.itController.open,
|
||||
controller.scrollController,
|
||||
controller.scrollableNotifier,
|
||||
]),
|
||||
|
|
@ -31,8 +30,7 @@ class ChatFloatingActionButton extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
|
||||
if (controller.choreographer.errorService.error != null &&
|
||||
!controller.choreographer.itController.open.value) {
|
||||
if (controller.choreographer.errorService.error != null) {
|
||||
return ChoreographerHasErrorButton(
|
||||
controller.choreographer.errorService.error!,
|
||||
controller.choreographer,
|
||||
|
|
|
|||
|
|
@ -5,10 +5,8 @@ import 'package:fluffychat/l10n/l10n.dart';
|
|||
import 'package:fluffychat/pages/chat/chat.dart';
|
||||
import 'package:fluffychat/pages/chat/chat_emoji_picker.dart';
|
||||
import 'package:fluffychat/pages/chat/reply_display.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_session_chat/activity_role_tooltip.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/chat/widgets/pangea_chat_input_row.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/it/it_bar.dart';
|
||||
import 'package:fluffychat/pangea/instructions/instructions_enum.dart';
|
||||
import 'package:fluffychat/pangea/instructions/instructions_inline_tooltip.dart';
|
||||
|
||||
class ChatInputBar extends StatelessWidget {
|
||||
|
|
@ -27,36 +25,27 @@ class ChatInputBar extends StatelessWidget {
|
|||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ValueListenableBuilder(
|
||||
valueListenable: controller.choreographer.itController.open,
|
||||
builder: (context, open, _) {
|
||||
return open
|
||||
? Container(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: FluffyThemes.maxTimelineWidth,
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: InstructionsInlineTooltip(
|
||||
instructionsEnum: InstructionsEnum.clickBestOption,
|
||||
animate: false,
|
||||
padding: EdgeInsets.only(
|
||||
left: 16.0,
|
||||
right: 16.0,
|
||||
top: FluffyThemes.isColumnMode(context) ? 16.0 : 8.0,
|
||||
),
|
||||
),
|
||||
)
|
||||
: Container(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: FluffyThemes.maxTimelineWidth,
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: ActivityRoleTooltip(
|
||||
room: controller.room,
|
||||
hide: controller.choreographer.itController.open,
|
||||
),
|
||||
);
|
||||
},
|
||||
Container(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: FluffyThemes.maxTimelineWidth,
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child:
|
||||
controller.room.showActivityChatUI &&
|
||||
controller.room.ownRole?.goal != null
|
||||
? InlineTooltip(
|
||||
message: controller.room.ownRole!.goal!,
|
||||
isClosed: controller.room.hasDismissedGoalTooltip,
|
||||
onClose: () async {
|
||||
await controller.room.dismissGoalTooltip();
|
||||
},
|
||||
padding: EdgeInsets.only(
|
||||
left: 16.0,
|
||||
right: 16.0,
|
||||
top: FluffyThemes.isColumnMode(context) ? 16.0 : 8.0,
|
||||
),
|
||||
)
|
||||
: SizedBox(),
|
||||
),
|
||||
Container(
|
||||
margin: EdgeInsets.all(
|
||||
|
|
@ -75,7 +64,6 @@ class ChatInputBar extends StatelessWidget {
|
|||
: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ITBar(choreographer: controller.choreographer),
|
||||
ReplyDisplay(controller),
|
||||
PangeaChatInputRow(controller: controller),
|
||||
ChatEmojiPicker(controller),
|
||||
|
|
|
|||
|
|
@ -1,38 +0,0 @@
|
|||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ChatViewBackground extends StatelessWidget {
|
||||
final ValueNotifier<bool> visible;
|
||||
const ChatViewBackground(this.visible, {super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: visible,
|
||||
builder: (context, value, _) {
|
||||
return value
|
||||
? Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
child: Material(
|
||||
borderOnForeground: false,
|
||||
color: const Color.fromRGBO(0, 0, 0, 1).withAlpha(150),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 2.5, sigmaY: 2.5),
|
||||
child: Container(
|
||||
height: double.infinity,
|
||||
width: double.infinity,
|
||||
color: Colors.transparent,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -69,15 +69,7 @@ class PangeaChatInputRow extends StatelessWidget {
|
|||
duration: FluffyThemes.animationDuration,
|
||||
curve: FluffyThemes.animationCurve,
|
||||
height: height,
|
||||
width:
|
||||
text.text.isEmpty &&
|
||||
!controller
|
||||
.choreographer
|
||||
.itController
|
||||
.open
|
||||
.value
|
||||
? height
|
||||
: 0,
|
||||
width: text.text.isEmpty ? height : 0,
|
||||
alignment: Alignment.center,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
decoration: const BoxDecoration(),
|
||||
|
|
@ -298,12 +290,7 @@ class PangeaChatInputRow extends StatelessWidget {
|
|||
alignment: Alignment.center,
|
||||
child:
|
||||
PlatformInfos.platformCanRecord &&
|
||||
text.text.isEmpty &&
|
||||
!controller
|
||||
.choreographer
|
||||
.itController
|
||||
.open
|
||||
.value
|
||||
text.text.isEmpty
|
||||
? IconButton(
|
||||
tooltip: L10n.of(context).voiceMessage,
|
||||
onPressed: () => recordingViewModel
|
||||
|
|
|
|||
|
|
@ -1,13 +1,4 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class ChoreoConstants {
|
||||
static const numberOfITChoices = 4;
|
||||
static const levelThresholdForGreen = 1;
|
||||
static const levelThresholdForYellow = 2;
|
||||
static const levelThresholdForRed = 3;
|
||||
static const green = Colors.green;
|
||||
static const yellow = Color.fromARGB(255, 206, 152, 2);
|
||||
static const red = Colors.red;
|
||||
static const int msBeforeIGCStart = 10000;
|
||||
static const int maxLength = 1000;
|
||||
static const String inputTransformTargetKey = 'input_text_field';
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
enum ChoreoModeEnum { igc, it }
|
||||
|
|
@ -4,7 +4,7 @@ import 'package:fluffychat/pangea/choreographer/choreo_edit_model.dart';
|
|||
import 'package:fluffychat/pangea/choreographer/igc/pangea_match_model.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/igc/pangea_match_status_enum.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/igc/span_data_model.dart';
|
||||
import 'it/completed_it_step_model.dart';
|
||||
import 'completed_it_step_model.dart';
|
||||
|
||||
/// this class lives within a [PangeaIGCEvent]
|
||||
/// it always has a [RepresentationEvent] parent
|
||||
|
|
|
|||
|
|
@ -6,46 +6,37 @@ import 'package:async/async.dart';
|
|||
|
||||
import 'package:fluffychat/pangea/choreographer/assistance_state_enum.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/choreo_constants.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/choreo_mode_enum.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/choreo_record_model.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/choreographer_state_extension.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/igc/igc_controller.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/igc/pangea_match_state_model.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/igc/pangea_match_status_enum.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/it/completed_it_step_model.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/pangea_message_content_model.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/text_editing/edit_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/text_editing/pangea_text_controller.dart';
|
||||
import 'package:fluffychat/pangea/events/models/representation_content_model.dart';
|
||||
import 'package:fluffychat/pangea/events/models/tokens_event_content_model.dart';
|
||||
import 'package:fluffychat/pangea/events/repo/token_api_models.dart';
|
||||
import 'package:fluffychat/pangea/events/repo/tokens_repo.dart';
|
||||
import 'package:fluffychat/pangea/languages/language_constants.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/tool_settings_enum.dart';
|
||||
import 'package:fluffychat/pangea/subscription/controllers/subscription_controller.dart';
|
||||
import 'package:fluffychat/pangea/text_to_speech/tts_controller.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
import '../../widgets/matrix.dart';
|
||||
import 'choreographer_error_controller.dart';
|
||||
import 'it/it_controller.dart';
|
||||
|
||||
class Choreographer extends ChangeNotifier {
|
||||
final FocusNode inputFocus;
|
||||
|
||||
late final PangeaTextController textController;
|
||||
late final ITController itController;
|
||||
late final IgcController igcController;
|
||||
late final ChoreographerErrorController errorService;
|
||||
|
||||
ChoreoRecordModel? _choreoRecord;
|
||||
|
||||
final ValueNotifier<bool> _isFetching = ValueNotifier(false);
|
||||
final ValueNotifier<int> _timesDismissedIT = ValueNotifier(0);
|
||||
|
||||
int _timesClicked = 0;
|
||||
Timer? _debounceTimer;
|
||||
String? _lastChecked;
|
||||
ChoreoModeEnum _choreoMode = ChoreoModeEnum.igc;
|
||||
|
||||
DateTime? _lastIgcError;
|
||||
DateTime? _lastTokensError;
|
||||
|
|
@ -54,17 +45,13 @@ class Choreographer extends ChangeNotifier {
|
|||
|
||||
StreamSubscription? _languageSub;
|
||||
StreamSubscription? _settingsUpdateSub;
|
||||
StreamSubscription? _acceptedContinuanceSub;
|
||||
StreamSubscription? _updatedMatchSub;
|
||||
|
||||
Choreographer(this.inputFocus) {
|
||||
_initialize();
|
||||
}
|
||||
|
||||
int get timesClicked => _timesClicked;
|
||||
ValueNotifier<bool> get isFetching => _isFetching;
|
||||
ValueNotifier<int> get timesDismissedIT => _timesDismissedIT;
|
||||
ChoreoModeEnum get choreoMode => _choreoMode;
|
||||
String get currentText => textController.text;
|
||||
|
||||
ChoreoRecordModel get _record => _choreoRecord ??= ChoreoRecordModel(
|
||||
|
|
@ -86,12 +73,6 @@ class Choreographer extends ChangeNotifier {
|
|||
errorService = ChoreographerErrorController();
|
||||
errorService.addListener(notifyListeners);
|
||||
|
||||
itController = ITController(
|
||||
(e) => errorService.setErrorAndLock(ChoreoError(raw: e)),
|
||||
);
|
||||
itController.open.addListener(_onUpdateITOpenStatus);
|
||||
itController.editing.addListener(_onSubmitSourceTextEdits);
|
||||
|
||||
igcController = IgcController(
|
||||
(e) {
|
||||
errorService.setErrorAndLock(ChoreoError(raw: e));
|
||||
|
|
@ -121,9 +102,6 @@ class Choreographer extends ChangeNotifier {
|
|||
notifyListeners();
|
||||
});
|
||||
|
||||
_acceptedContinuanceSub ??= itController.acceptedContinuanceStream.stream
|
||||
.listen(_onAcceptContinuance);
|
||||
|
||||
_updatedMatchSub ??= igcController.matchUpdateStream.stream.listen(
|
||||
_onUpdateMatch,
|
||||
);
|
||||
|
|
@ -131,36 +109,27 @@ class Choreographer extends ChangeNotifier {
|
|||
|
||||
void clear() {
|
||||
_lastChecked = null;
|
||||
_timesClicked = 0;
|
||||
_isFetching.value = false;
|
||||
_choreoRecord = null;
|
||||
itController.closeIT();
|
||||
itController.clearSourceText();
|
||||
itController.clearSession();
|
||||
igcController.clear();
|
||||
_resetDebounceTimer();
|
||||
_setChoreoMode(ChoreoModeEnum.igc);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
errorService.removeListener(notifyListeners);
|
||||
itController.open.removeListener(_onCloseIT);
|
||||
itController.editing.removeListener(_onSubmitSourceTextEdits);
|
||||
textController.removeListener(_onChange);
|
||||
|
||||
_languageSub?.cancel();
|
||||
_settingsUpdateSub?.cancel();
|
||||
_acceptedContinuanceSub?.cancel();
|
||||
_updatedMatchSub?.cancel();
|
||||
_debounceTimer?.cancel();
|
||||
|
||||
igcController.dispose();
|
||||
itController.dispose();
|
||||
errorService.dispose();
|
||||
textController.dispose();
|
||||
_isFetching.dispose();
|
||||
_timesDismissedIT.dispose();
|
||||
|
||||
TtsController.stop();
|
||||
super.dispose();
|
||||
|
|
@ -168,23 +137,6 @@ class Choreographer extends ChangeNotifier {
|
|||
|
||||
void onPaste(String value) => _record.pastedStrings.add(value);
|
||||
|
||||
void onClickSend() {
|
||||
if (assistanceState == AssistanceStateEnum.fetched) {
|
||||
_timesClicked++;
|
||||
|
||||
// if user is doing IT, call closeIT here to
|
||||
// ensure source text is replaced when needed
|
||||
if (itController.open.value && _timesClicked > 1) {
|
||||
itController.closeIT(dismiss: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _setChoreoMode(ChoreoModeEnum mode) {
|
||||
_choreoMode = mode;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void _resetDebounceTimer() {
|
||||
if (_debounceTimer != null) {
|
||||
_debounceTimer?.cancel();
|
||||
|
|
@ -228,10 +180,8 @@ class Choreographer extends ChangeNotifier {
|
|||
_lastChecked = textController.text;
|
||||
if (errorService.isError) return;
|
||||
if (textController.editType == EditTypeEnum.keyboard) {
|
||||
if (igcController.currentText != null ||
|
||||
itController.sourceText.value != null) {
|
||||
if (igcController.currentText != null) {
|
||||
igcController.clear();
|
||||
itController.clearSourceText();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
|
|
@ -254,9 +204,7 @@ class Choreographer extends ChangeNotifier {
|
|||
MatrixState.pangeaController.userController.userL1 == null ||
|
||||
(!ToolSetting.interactiveGrammar.enabled &&
|
||||
!ToolSetting.interactiveTranslator.enabled) ||
|
||||
(!ToolSetting.autoIGC.enabled &&
|
||||
!manual &&
|
||||
_choreoMode != ChoreoModeEnum.it) ||
|
||||
(!ToolSetting.autoIGC.enabled && !manual) ||
|
||||
_backoffRequest(_lastIgcError, _igcErrorBackoff)) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -334,20 +282,9 @@ class Choreographer extends ChangeNotifier {
|
|||
tokensResp = res.isValue ? res.result : null;
|
||||
}
|
||||
|
||||
final hasOriginalWritten =
|
||||
_record.includedIT && itController.sourceText.value != null;
|
||||
|
||||
return PangeaMessageContentModel(
|
||||
message: message,
|
||||
choreo: _record,
|
||||
originalWritten: hasOriginalWritten
|
||||
? PangeaRepresentation(
|
||||
langCode: l1LangCode ?? LanguageKeys.unknownLanguage,
|
||||
text: itController.sourceText.value!,
|
||||
originalWritten: true,
|
||||
originalSent: false,
|
||||
)
|
||||
: null,
|
||||
tokensSent: tokensResp != null
|
||||
? PangeaMessageTokens(
|
||||
tokens: tokensResp.tokens,
|
||||
|
|
@ -357,60 +294,6 @@ class Choreographer extends ChangeNotifier {
|
|||
);
|
||||
}
|
||||
|
||||
void _onUpdateITOpenStatus() {
|
||||
itController.open.value ? _onOpenIT() : _onCloseIT();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void _onOpenIT() {
|
||||
inputFocus.unfocus();
|
||||
final itMatch = igcController.openMatches.firstWhere(
|
||||
(match) => match.updatedMatch.isITStart,
|
||||
orElse: () =>
|
||||
throw Exception("Attempted to open IT without an ITStart match"),
|
||||
);
|
||||
|
||||
igcController.clear();
|
||||
itMatch.setStatus(PangeaMatchStatusEnum.accepted);
|
||||
_record.addRecord("", match: itMatch.updatedMatch);
|
||||
|
||||
_setChoreoMode(ChoreoModeEnum.it);
|
||||
textController.setSystemText("", EditTypeEnum.it);
|
||||
}
|
||||
|
||||
void _onCloseIT() {
|
||||
if (itController.dismissed &&
|
||||
currentText.isEmpty &&
|
||||
itController.sourceText.value != null) {
|
||||
textController.setSystemText(
|
||||
itController.sourceText.value!,
|
||||
EditTypeEnum.itDismissed,
|
||||
);
|
||||
}
|
||||
|
||||
if (itController.dismissed) {
|
||||
_timesDismissedIT.value = _timesDismissedIT.value + 1;
|
||||
}
|
||||
_setChoreoMode(ChoreoModeEnum.igc);
|
||||
errorService.resetError();
|
||||
}
|
||||
|
||||
void _onSubmitSourceTextEdits() {
|
||||
if (itController.editing.value) return;
|
||||
textController.setSystemText("", EditTypeEnum.it);
|
||||
}
|
||||
|
||||
void _onAcceptContinuance(CompletedITStepModel step) {
|
||||
textController.setSystemText(
|
||||
textController.text + step.continuances[step.chosen].text,
|
||||
EditTypeEnum.it,
|
||||
);
|
||||
|
||||
_record.addRecord(textController.text, step: step);
|
||||
inputFocus.requestFocus();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void clearMatches(Object error) {
|
||||
MatrixState.pAnyState.closeAllOverlays();
|
||||
igcController.clearMatches();
|
||||
|
|
|
|||
|
|
@ -8,11 +8,6 @@ class ChoreographerSendButton extends StatelessWidget {
|
|||
final ChatController controller;
|
||||
const ChoreographerSendButton({super.key, required this.controller});
|
||||
|
||||
Future<void> _onPressed(BuildContext context) async {
|
||||
controller.choreographer.onClickSend();
|
||||
controller.onInputBarSubmitted();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ValueListenableBuilder(
|
||||
|
|
@ -26,7 +21,7 @@ class ChoreographerSendButton extends StatelessWidget {
|
|||
color: controller.choreographer.assistanceState.sendButtonColor(
|
||||
context,
|
||||
),
|
||||
onPressed: fetching ? null : () => _onPressed(context),
|
||||
onPressed: fetching ? null : controller.onInputBarSubmitted,
|
||||
tooltip: L10n.of(context).send,
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import 'package:fluffychat/pangea/choreographer/assistance_state_enum.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/choreo_mode_enum.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/choreographer.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
|
|
@ -8,7 +7,7 @@ extension ChoregrapherStateExtension on Choreographer {
|
|||
final isSubscribed =
|
||||
MatrixState.pangeaController.subscriptionController.isSubscribed;
|
||||
if (isSubscribed == false) return AssistanceStateEnum.noSub;
|
||||
if (currentText.trim().isEmpty && itController.sourceText.value == null) {
|
||||
if (currentText.trim().isEmpty) {
|
||||
return AssistanceStateEnum.noMessage;
|
||||
}
|
||||
|
||||
|
|
@ -16,15 +15,12 @@ extension ChoregrapherStateExtension on Choreographer {
|
|||
return AssistanceStateEnum.error;
|
||||
}
|
||||
|
||||
if (igcController.openMatches.isNotEmpty ||
|
||||
(choreoMode == ChoreoModeEnum.it &&
|
||||
itController.currentITStep.value?.isFinal != true)) {
|
||||
if (igcController.openMatches.isNotEmpty) {
|
||||
return AssistanceStateEnum.fetched;
|
||||
}
|
||||
|
||||
if (isFetching.value) return AssistanceStateEnum.fetching;
|
||||
if (igcController.currentText == null &&
|
||||
itController.sourceText.value == null) {
|
||||
if (igcController.currentText == null) {
|
||||
return AssistanceStateEnum.notFetched;
|
||||
}
|
||||
return AssistanceStateEnum.complete;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,3 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import '../choreo_constants.dart';
|
||||
|
||||
class CompletedITStepModel {
|
||||
final List<ContinuanceModel> continuances;
|
||||
final int chosen;
|
||||
|
|
@ -84,58 +79,6 @@ class ContinuanceModel {
|
|||
return data;
|
||||
}
|
||||
|
||||
ContinuanceModel copyWith({
|
||||
double? probability,
|
||||
int? level,
|
||||
String? text,
|
||||
String? description,
|
||||
int? indexSavedByServer,
|
||||
bool? wasClicked,
|
||||
bool? inDictionary,
|
||||
bool? hasInfo,
|
||||
bool? gold,
|
||||
}) {
|
||||
return ContinuanceModel(
|
||||
probability: probability ?? this.probability,
|
||||
level: level ?? this.level,
|
||||
text: text ?? this.text,
|
||||
description: description ?? this.description,
|
||||
indexSavedByServer: indexSavedByServer ?? this.indexSavedByServer,
|
||||
wasClicked: wasClicked ?? this.wasClicked,
|
||||
inDictionary: inDictionary ?? this.inDictionary,
|
||||
hasInfo: hasInfo ?? this.hasInfo,
|
||||
gold: gold ?? this.gold,
|
||||
);
|
||||
}
|
||||
|
||||
Color? get color {
|
||||
if (!wasClicked) return null;
|
||||
switch (level) {
|
||||
case ChoreoConstants.levelThresholdForGreen:
|
||||
return ChoreoConstants.green;
|
||||
case ChoreoConstants.levelThresholdForYellow:
|
||||
return ChoreoConstants.yellow;
|
||||
case ChoreoConstants.levelThresholdForRed:
|
||||
return ChoreoConstants.red;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
String? feedbackText(BuildContext context) {
|
||||
final L10n l10n = L10n.of(context);
|
||||
switch (level) {
|
||||
case ChoreoConstants.levelThresholdForGreen:
|
||||
return l10n.greenFeedback;
|
||||
case ChoreoConstants.levelThresholdForYellow:
|
||||
return l10n.yellowFeedback;
|
||||
case ChoreoConstants.levelThresholdForRed:
|
||||
return l10n.redFeedback;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
|
|
@ -1,96 +0,0 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:async/async.dart';
|
||||
import 'package:http/http.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/choreographer/igc/span_data_model.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/igc/span_data_request.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/igc/span_data_response.dart';
|
||||
import 'package:fluffychat/pangea/common/config/environment.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import '../../common/network/requests.dart';
|
||||
import '../../common/network/urls.dart';
|
||||
|
||||
class _SpanDetailsCacheItem {
|
||||
final Future<SpanData> data;
|
||||
final DateTime timestamp;
|
||||
|
||||
const _SpanDetailsCacheItem({required this.data, required this.timestamp});
|
||||
}
|
||||
|
||||
class SpanDataRepo {
|
||||
static final Map<String, _SpanDetailsCacheItem> _cache = {};
|
||||
static const Duration _cacheDuration = Duration(minutes: 10);
|
||||
|
||||
static Future<Result<SpanData>> get(
|
||||
String? accessToken, {
|
||||
required SpanDetailsRequest request,
|
||||
}) async {
|
||||
final cached = _getCached(request);
|
||||
if (cached != null) {
|
||||
return _getResult(request, cached);
|
||||
}
|
||||
|
||||
final future = _fetch(accessToken, request: request);
|
||||
_setCached(request, future);
|
||||
return _getResult(request, future);
|
||||
}
|
||||
|
||||
static Future<SpanData> _fetch(
|
||||
String? accessToken, {
|
||||
required SpanDetailsRequest request,
|
||||
}) async {
|
||||
final Requests req = Requests(
|
||||
accessToken: accessToken,
|
||||
choreoApiKey: Environment.choreoApiKey,
|
||||
);
|
||||
|
||||
final Response res = await req.post(
|
||||
url: PApiUrls.spanDetails,
|
||||
body: request.toJson(),
|
||||
);
|
||||
|
||||
if (res.statusCode != 200) {
|
||||
throw Exception('Failed to load span details');
|
||||
}
|
||||
|
||||
final respModel = SpanDetailsResponse.fromJson(
|
||||
jsonDecode(utf8.decode(res.bodyBytes)),
|
||||
);
|
||||
return respModel.span;
|
||||
}
|
||||
|
||||
static Future<Result<SpanData>> _getResult(
|
||||
SpanDetailsRequest request,
|
||||
Future<SpanData> future,
|
||||
) async {
|
||||
try {
|
||||
final res = await future;
|
||||
return Result.value(res);
|
||||
} catch (e, s) {
|
||||
_cache.remove(request.hashCode.toString());
|
||||
ErrorHandler.logError(e: e, s: s, data: request.toJson());
|
||||
return Result.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
static Future<SpanData>? _getCached(SpanDetailsRequest request) {
|
||||
final cacheKeys = [..._cache.keys];
|
||||
for (final key in cacheKeys) {
|
||||
if (DateTime.now().difference(_cache[key]!.timestamp) >= _cacheDuration) {
|
||||
_cache.remove(key);
|
||||
}
|
||||
}
|
||||
return _cache[request.hashCode.toString()]?.data;
|
||||
}
|
||||
|
||||
static void _setCached(
|
||||
SpanDetailsRequest request,
|
||||
Future<SpanData> response,
|
||||
) {
|
||||
_cache[request.hashCode.toString()] = _SpanDetailsCacheItem(
|
||||
data: response,
|
||||
timestamp: DateTime.now(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
import 'package:fluffychat/pangea/choreographer/igc/span_data_model.dart';
|
||||
import 'package:fluffychat/pangea/common/constants/model_keys.dart';
|
||||
|
||||
class SpanDetailsRequest {
|
||||
final String userL1;
|
||||
final String userL2;
|
||||
final bool enableIT;
|
||||
final bool enableIGC;
|
||||
final SpanData span;
|
||||
|
||||
const SpanDetailsRequest({
|
||||
required this.userL1,
|
||||
required this.userL2,
|
||||
required this.enableIGC,
|
||||
required this.enableIT,
|
||||
required this.span,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
ModelKey.userL1: userL1,
|
||||
ModelKey.userL2: userL2,
|
||||
ModelKey.enableIT: enableIT,
|
||||
ModelKey.enableIGC: enableIGC,
|
||||
'span': span.toJson(),
|
||||
};
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
if (other is! SpanDetailsRequest) return false;
|
||||
if (other.userL1 != userL1) return false;
|
||||
if (other.userL2 != userL2) return false;
|
||||
if (other.enableIT != enableIT) return false;
|
||||
if (other.enableIGC != enableIGC) return false;
|
||||
if (other.span != span) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return userL1.hashCode ^
|
||||
userL2.hashCode ^
|
||||
enableIT.hashCode ^
|
||||
enableIGC.hashCode ^
|
||||
span.hashCode;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
import 'package:fluffychat/pangea/choreographer/igc/span_data_model.dart';
|
||||
|
||||
class SpanDetailsResponse {
|
||||
final String userL1;
|
||||
final String userL2;
|
||||
final bool enableIT;
|
||||
final bool enableIGC;
|
||||
final SpanData span;
|
||||
|
||||
const SpanDetailsResponse({
|
||||
required this.userL1,
|
||||
required this.userL2,
|
||||
required this.enableIGC,
|
||||
required this.enableIT,
|
||||
required this.span,
|
||||
});
|
||||
|
||||
factory SpanDetailsResponse.fromJson(Map<String, dynamic> json) =>
|
||||
SpanDetailsResponse(
|
||||
userL1: json['user_l1'] as String,
|
||||
userL2: json['user_l2'] as String,
|
||||
enableIT: json['enable_it'] as bool,
|
||||
enableIGC: json['enable_igc'] as bool,
|
||||
span: SpanData.fromJson(json['span']),
|
||||
);
|
||||
}
|
||||
|
|
@ -1,92 +0,0 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:async/async.dart';
|
||||
import 'package:http/http.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/choreographer/it/contextual_definition_request_model.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/it/contextual_definition_response_model.dart';
|
||||
import 'package:fluffychat/pangea/common/config/environment.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import '../../common/network/requests.dart';
|
||||
import '../../common/network/urls.dart';
|
||||
|
||||
class ContextualDefinitionRepo {
|
||||
static final Map<String, Future<String>> _cache = {};
|
||||
|
||||
static Future<Result<String>> get(
|
||||
String accessToken,
|
||||
ContextualDefinitionRequestModel request,
|
||||
) async {
|
||||
final cached = _getCached(request);
|
||||
if (cached != null) {
|
||||
try {
|
||||
return Result.value(await cached);
|
||||
} catch (e, s) {
|
||||
_cache.remove(request.hashCode.toString());
|
||||
ErrorHandler.logError(e: e, s: s, data: request.toJson());
|
||||
return Result.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
final future = _fetch(accessToken, request);
|
||||
_setCached(request, future);
|
||||
return _getResult(request, future);
|
||||
}
|
||||
|
||||
static Future<String> _fetch(
|
||||
String accessToken,
|
||||
ContextualDefinitionRequestModel request,
|
||||
) async {
|
||||
final Requests req = Requests(
|
||||
choreoApiKey: Environment.choreoApiKey,
|
||||
accessToken: accessToken,
|
||||
);
|
||||
|
||||
final Response res = await req.post(
|
||||
url: PApiUrls.contextualDefinition,
|
||||
body: request.toJson(),
|
||||
);
|
||||
|
||||
if (res.statusCode != 200) {
|
||||
throw Exception(
|
||||
"Contextual definition request failed with status code ${res.statusCode}",
|
||||
);
|
||||
}
|
||||
|
||||
final ContextualDefinitionResponseModel response =
|
||||
ContextualDefinitionResponseModel.fromJson(
|
||||
jsonDecode(utf8.decode(res.bodyBytes).toString()),
|
||||
);
|
||||
|
||||
if (response.text.isEmpty) {
|
||||
ErrorHandler.logError(
|
||||
e: Exception("empty text in contextual definition response"),
|
||||
data: {"request": request.toJson(), "accessToken": accessToken},
|
||||
);
|
||||
}
|
||||
|
||||
return response.text;
|
||||
}
|
||||
|
||||
static Future<Result<String>> _getResult(
|
||||
ContextualDefinitionRequestModel request,
|
||||
Future<String> future,
|
||||
) async {
|
||||
try {
|
||||
final res = await future;
|
||||
return Result.value(res);
|
||||
} catch (e, s) {
|
||||
_cache.remove(request.hashCode.toString());
|
||||
ErrorHandler.logError(e: e, s: s, data: request.toJson());
|
||||
return Result.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
static Future<String>? _getCached(ContextualDefinitionRequestModel request) =>
|
||||
_cache[request.hashCode.toString()];
|
||||
|
||||
static void _setCached(
|
||||
ContextualDefinitionRequestModel request,
|
||||
Future<String> response,
|
||||
) => _cache[request.hashCode.toString()] = response;
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
import 'package:fluffychat/pangea/common/constants/model_keys.dart';
|
||||
|
||||
class ContextualDefinitionRequestModel {
|
||||
final String fullText;
|
||||
final String word;
|
||||
final String feedbackLang;
|
||||
final String fullTextLang;
|
||||
final String wordLang;
|
||||
|
||||
const ContextualDefinitionRequestModel({
|
||||
required this.fullText,
|
||||
required this.word,
|
||||
required this.feedbackLang,
|
||||
required this.fullTextLang,
|
||||
required this.wordLang,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
ModelKey.fullText: fullText,
|
||||
ModelKey.word: word,
|
||||
ModelKey.lang: feedbackLang,
|
||||
ModelKey.fullTextLang: fullTextLang,
|
||||
ModelKey.wordLang: wordLang,
|
||||
};
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is ContextualDefinitionRequestModel &&
|
||||
runtimeType == other.runtimeType &&
|
||||
fullText == other.fullText &&
|
||||
word == other.word &&
|
||||
feedbackLang == other.feedbackLang &&
|
||||
fullTextLang == other.fullTextLang &&
|
||||
wordLang == other.wordLang;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
fullText.hashCode ^
|
||||
word.hashCode ^
|
||||
feedbackLang.hashCode ^
|
||||
fullTextLang.hashCode ^
|
||||
wordLang.hashCode;
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
class ContextualDefinitionResponseModel {
|
||||
final String text;
|
||||
|
||||
const ContextualDefinitionResponseModel({required this.text});
|
||||
|
||||
factory ContextualDefinitionResponseModel.fromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => ContextualDefinitionResponseModel(text: json["response"]);
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
import 'package:fluffychat/pangea/choreographer/it/completed_it_step_model.dart';
|
||||
|
||||
class GoldRouteTrackerModel {
|
||||
final String _originalText;
|
||||
final List<ContinuanceModel> continuances;
|
||||
|
||||
const GoldRouteTrackerModel(this.continuances, String originalText)
|
||||
: _originalText = originalText;
|
||||
|
||||
ContinuanceModel? currentContinuance({
|
||||
required String currentText,
|
||||
required String sourceText,
|
||||
}) {
|
||||
if (_originalText != sourceText) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String stack = "";
|
||||
for (final cont in continuances) {
|
||||
if (stack == currentText) {
|
||||
return cont;
|
||||
}
|
||||
stack += cont.text;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
String? get fullTranslation {
|
||||
if (continuances.isEmpty) return null;
|
||||
String full = "";
|
||||
for (final cont in continuances) {
|
||||
full += cont.text;
|
||||
}
|
||||
return full;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,410 +0,0 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/analytics_summary/animated_progress_bar.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/choreo_constants.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/choreographer.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/it/completed_it_step_model.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/it/it_feedback_card.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/it/word_data_card.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/common/widgets/error_indicator.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/settings_learning.dart';
|
||||
import 'package:fluffychat/pangea/translation/full_text_translation_request_model.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import '../../common/utils/overlay.dart';
|
||||
import '../../common/widgets/choice_array.dart';
|
||||
|
||||
class ITBar extends StatefulWidget {
|
||||
final Choreographer choreographer;
|
||||
const ITBar({super.key, required this.choreographer});
|
||||
|
||||
@override
|
||||
ITBarState createState() => ITBarState();
|
||||
}
|
||||
|
||||
class ITBarState extends State<ITBar> with SingleTickerProviderStateMixin {
|
||||
late AnimationController _controller;
|
||||
late Animation<double> _animation;
|
||||
final TextEditingController _sourceTextController = TextEditingController();
|
||||
|
||||
Timer? _successTimer;
|
||||
bool _visible = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_controller = AnimationController(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
vsync: this,
|
||||
);
|
||||
_animation = CurvedAnimation(parent: _controller, curve: Curves.easeInOut);
|
||||
_openListener();
|
||||
_open.addListener(_openListener);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
_sourceTextController.dispose();
|
||||
_successTimer?.cancel();
|
||||
_open.removeListener(_openListener);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
FullTextTranslationRequestModel _translationRequest(String text) =>
|
||||
FullTextTranslationRequestModel(
|
||||
text: text,
|
||||
tgtLang: MatrixState.pangeaController.userController.userL1!.langCode,
|
||||
userL1: MatrixState.pangeaController.userController.userL1!.langCode,
|
||||
userL2: MatrixState.pangeaController.userController.userL2!.langCode,
|
||||
);
|
||||
|
||||
void _openListener() {
|
||||
if (!mounted) return;
|
||||
|
||||
final nextText = _sourceText.value ?? widget.choreographer.currentText;
|
||||
if (_sourceTextController.text != nextText) {
|
||||
_sourceTextController.text = nextText;
|
||||
}
|
||||
|
||||
if (_open.value) {
|
||||
setState(() => _visible = true);
|
||||
_controller.forward();
|
||||
} else {
|
||||
_controller.reverse().then((value) {
|
||||
if (!mounted) return;
|
||||
setState(() => _visible = false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ValueNotifier<String?> get _sourceText =>
|
||||
widget.choreographer.itController.sourceText;
|
||||
ValueNotifier<bool> get _open => widget.choreographer.itController.open;
|
||||
|
||||
void _showFeedbackCard(
|
||||
ContinuanceModel continuance, [
|
||||
Color? borderColor,
|
||||
bool selected = false,
|
||||
]) {
|
||||
final text = continuance.text;
|
||||
MatrixState.pAnyState.closeOverlay("it_feedback_card");
|
||||
OverlayUtil.showPositionedCard(
|
||||
context: context,
|
||||
cardToShow: selected
|
||||
? WordDataCard(
|
||||
word: text,
|
||||
langCode:
|
||||
MatrixState.pangeaController.userController.userL2!.langCode,
|
||||
fullText: _sourceText.value ?? widget.choreographer.currentText,
|
||||
)
|
||||
: ITFeedbackCard(_translationRequest(text)),
|
||||
maxHeight: 300,
|
||||
maxWidth: 300,
|
||||
borderColor: borderColor,
|
||||
transformTargetId: 'it_bar',
|
||||
isScrollable: false,
|
||||
overlayKey: "it_feedback_card",
|
||||
ignorePointer: true,
|
||||
);
|
||||
}
|
||||
|
||||
void _selectContinuance(int index) {
|
||||
MatrixState.pAnyState.closeOverlay("it_feedback_card");
|
||||
ContinuanceModel continuance;
|
||||
try {
|
||||
continuance = widget.choreographer.itController.selectContinuance(index);
|
||||
} catch (e, s) {
|
||||
ErrorHandler.logError(
|
||||
e: e,
|
||||
s: s,
|
||||
level: SentryLevel.warning,
|
||||
data: {"index": index},
|
||||
);
|
||||
widget.choreographer.itController.closeIT();
|
||||
return;
|
||||
}
|
||||
|
||||
if (continuance.level == 1) {
|
||||
_onCorrectSelection(index);
|
||||
} else {
|
||||
_showFeedbackCard(
|
||||
continuance,
|
||||
continuance.level == 2 ? ChoreoConstants.yellow : ChoreoConstants.red,
|
||||
true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _onCorrectSelection(int index) {
|
||||
_successTimer?.cancel();
|
||||
_successTimer = Timer(const Duration(milliseconds: 500), () {
|
||||
if (!mounted) return;
|
||||
try {
|
||||
widget.choreographer.itController.acceptContinuance(index);
|
||||
} catch (e, s) {
|
||||
ErrorHandler.logError(
|
||||
e: e,
|
||||
s: s,
|
||||
level: SentryLevel.warning,
|
||||
data: {"index": index},
|
||||
);
|
||||
widget.choreographer.itController.closeIT();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!_visible) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return AnimatedBuilder(
|
||||
animation: _animation,
|
||||
builder: (context, child) => SizeTransition(
|
||||
sizeFactor: _animation,
|
||||
axisAlignment: -1.0,
|
||||
child: child,
|
||||
),
|
||||
child: CompositedTransformTarget(
|
||||
link: MatrixState.pAnyState.layerLinkAndKey('it_bar').link,
|
||||
child: Container(
|
||||
key: MatrixState.pAnyState.layerLinkAndKey('it_bar').key,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(24),
|
||||
topRight: Radius.circular(24),
|
||||
),
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
),
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Column(
|
||||
spacing: 12.0,
|
||||
children: [
|
||||
_ITBarHeader(
|
||||
onClose: () =>
|
||||
widget.choreographer.itController.closeIT(dismiss: true),
|
||||
setEditing:
|
||||
widget.choreographer.itController.setEditingSourceText,
|
||||
editing: widget.choreographer.itController.editing,
|
||||
progress: widget.choreographer.itController.progress,
|
||||
sourceTextController: _sourceTextController,
|
||||
sourceText: _sourceText,
|
||||
onSubmitEdits: (_) {
|
||||
widget.choreographer.itController.submitSourceTextEdits(
|
||||
_sourceTextController.text,
|
||||
);
|
||||
},
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
constraints: const BoxConstraints(minHeight: 80),
|
||||
child: Center(
|
||||
child: widget.choreographer.errorService.isError
|
||||
? Row(
|
||||
spacing: 8.0,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ErrorIndicator(
|
||||
message: L10n.of(context).translationError,
|
||||
style: TextStyle(
|
||||
fontStyle: FontStyle.italic,
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed:
|
||||
widget.choreographer.itController.closeIT,
|
||||
icon: const Icon(Icons.close, size: 20),
|
||||
),
|
||||
],
|
||||
)
|
||||
: ValueListenableBuilder(
|
||||
valueListenable:
|
||||
widget.choreographer.itController.currentITStep,
|
||||
builder: (context, step, _) {
|
||||
return step == null
|
||||
? CircularProgressIndicator(
|
||||
strokeWidth: 2.0,
|
||||
color: Theme.of(
|
||||
context,
|
||||
).colorScheme.primary,
|
||||
)
|
||||
: _ITChoices(
|
||||
continuances: step.continuances,
|
||||
onPressed: _selectContinuance,
|
||||
onLongPressed: _showFeedbackCard,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ITBarHeader extends StatelessWidget {
|
||||
final VoidCallback onClose;
|
||||
final Function(String) onSubmitEdits;
|
||||
final Function(bool) setEditing;
|
||||
|
||||
final ValueNotifier<bool> editing;
|
||||
final ValueNotifier<double> progress;
|
||||
final TextEditingController sourceTextController;
|
||||
final ValueNotifier<String?> sourceText;
|
||||
|
||||
const _ITBarHeader({
|
||||
required this.onClose,
|
||||
required this.setEditing,
|
||||
required this.editing,
|
||||
required this.progress,
|
||||
required this.onSubmitEdits,
|
||||
required this.sourceTextController,
|
||||
required this.sourceText,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: editing,
|
||||
builder: (context, isEditing, _) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
AnimatedCrossFade(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
crossFadeState: isEditing
|
||||
? CrossFadeState.showFirst
|
||||
: CrossFadeState.showSecond,
|
||||
firstChild: Row(
|
||||
spacing: 12.0,
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: sourceTextController,
|
||||
autofocus: true,
|
||||
enableSuggestions: false,
|
||||
maxLines: null,
|
||||
textInputAction: TextInputAction.send,
|
||||
onSubmitted: onSubmitEdits,
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
icon: const Icon(Icons.close_outlined),
|
||||
onPressed: () => setEditing(false),
|
||||
),
|
||||
],
|
||||
),
|
||||
secondChild: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 8.0, right: 8.0),
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable: progress,
|
||||
builder: (context, value, _) => AnimatedProgressBar(
|
||||
height: 20.0,
|
||||
widthPercent: value,
|
||||
backgroundColor: Theme.of(
|
||||
context,
|
||||
).colorScheme.surfaceContainerHighest,
|
||||
barColor: Theme.of(
|
||||
context,
|
||||
).colorScheme.primary.withAlpha(180),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
onPressed: () => setEditing(true),
|
||||
icon: const Icon(Icons.edit_outlined),
|
||||
),
|
||||
IconButton(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
icon: const Icon(Icons.settings_outlined),
|
||||
onPressed: () => showDialog(
|
||||
context: context,
|
||||
builder: (c) => const SettingsLearning(),
|
||||
barrierDismissible: false,
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
icon: const Icon(Icons.close_outlined),
|
||||
onPressed: onClose,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
isEditing
|
||||
? const SizedBox(height: 24.0)
|
||||
: ValueListenableBuilder(
|
||||
valueListenable: sourceText,
|
||||
builder: (context, text, _) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
constraints: const BoxConstraints(minHeight: 24.0),
|
||||
child: sourceText.value != null
|
||||
? Text(
|
||||
sourceText.value!,
|
||||
textAlign: TextAlign.center,
|
||||
)
|
||||
: const SizedBox(),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ITChoices extends StatelessWidget {
|
||||
final List<ContinuanceModel> continuances;
|
||||
final Function(int) onPressed;
|
||||
final Function(ContinuanceModel) onLongPressed;
|
||||
|
||||
const _ITChoices({
|
||||
required this.continuances,
|
||||
required this.onPressed,
|
||||
required this.onLongPressed,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ChoicesArray(
|
||||
id: Object.hashAll(continuances).toString(),
|
||||
isLoading: false,
|
||||
choices: [
|
||||
...continuances.map(
|
||||
(e) => Choice(
|
||||
text: e.text.trim(),
|
||||
color: e.color,
|
||||
isGold: e.description == "best",
|
||||
),
|
||||
),
|
||||
],
|
||||
onPressed: (value, index) => onPressed(index),
|
||||
onLongPress: (value, index) => onLongPressed(continuances[index]),
|
||||
selectedChoiceIndex: null,
|
||||
langCode: MatrixState.pangeaController.userController.userL2Code!,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,239 +0,0 @@
|
|||
import 'dart:async';
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import 'package:async/async.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/choreographer/it/gold_route_tracker_model.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/it/it_repo.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/it/it_response_model.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/it/it_step_model.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'completed_it_step_model.dart';
|
||||
import 'it_request_model.dart';
|
||||
|
||||
class ITController {
|
||||
final Function(Object) onError;
|
||||
|
||||
final Queue<Completer<ITStepModel>> _queue = Queue();
|
||||
GoldRouteTrackerModel? _goldRouteTracker;
|
||||
|
||||
final ValueNotifier<String?> _sourceText = ValueNotifier(null);
|
||||
final ValueNotifier<ITStepModel?> _currentITStep = ValueNotifier(null);
|
||||
final ValueNotifier<bool> _open = ValueNotifier(false);
|
||||
final ValueNotifier<bool> _editing = ValueNotifier(false);
|
||||
final ValueNotifier<double> _progress = ValueNotifier(0.0);
|
||||
|
||||
ITController(this.onError);
|
||||
|
||||
ValueNotifier<bool> get open => _open;
|
||||
ValueNotifier<bool> get editing => _editing;
|
||||
ValueNotifier<double> get progress => _progress;
|
||||
|
||||
ValueNotifier<ITStepModel?> get currentITStep => _currentITStep;
|
||||
ValueNotifier<String?> get sourceText => _sourceText;
|
||||
StreamController<CompletedITStepModel> acceptedContinuanceStream =
|
||||
StreamController.broadcast();
|
||||
|
||||
bool _continuing = false;
|
||||
bool dismissed = false;
|
||||
|
||||
ITRequestModel _request(String textInput) {
|
||||
assert(_sourceText.value != null);
|
||||
return ITRequestModel(
|
||||
text: _sourceText.value!,
|
||||
customInput: textInput,
|
||||
sourceLangCode: MatrixState.pangeaController.userController.userL1Code!,
|
||||
targetLangCode: MatrixState.pangeaController.userController.userL2Code!,
|
||||
goldTranslation: _goldRouteTracker?.fullTranslation,
|
||||
goldContinuances: _goldRouteTracker?.continuances,
|
||||
);
|
||||
}
|
||||
|
||||
Future<Result<ITResponseModel>> _safeRequest(String text) {
|
||||
return ITRepo.get(_request(text)).timeout(
|
||||
const Duration(seconds: 10),
|
||||
onTimeout: () => Result.error(
|
||||
TimeoutException("ITRepo.get timed out after 10 seconds"),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void clearSourceText() {
|
||||
_sourceText.value = null;
|
||||
}
|
||||
|
||||
void clearSession() {
|
||||
dismissed = false;
|
||||
_progress.value = 0.0;
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
acceptedContinuanceStream.close();
|
||||
_open.dispose();
|
||||
_editing.dispose();
|
||||
_currentITStep.dispose();
|
||||
_sourceText.dispose();
|
||||
}
|
||||
|
||||
void openIT(String text) {
|
||||
_sourceText.value = text;
|
||||
_open.value = true;
|
||||
_continueIT();
|
||||
}
|
||||
|
||||
void closeIT({bool dismiss = false}) {
|
||||
MatrixState.pAnyState.closeOverlay("it_feedback_card");
|
||||
|
||||
setEditingSourceText(false);
|
||||
if (dismiss) {
|
||||
dismissed = true;
|
||||
}
|
||||
|
||||
_open.value = false;
|
||||
_queue.clear();
|
||||
_currentITStep.value = null;
|
||||
_goldRouteTracker = null;
|
||||
}
|
||||
|
||||
void setEditingSourceText(bool value) {
|
||||
_editing.value = value;
|
||||
}
|
||||
|
||||
void submitSourceTextEdits(String text) {
|
||||
_queue.clear();
|
||||
_currentITStep.value = null;
|
||||
_goldRouteTracker = null;
|
||||
_progress.value = 0.0;
|
||||
_sourceText.value = text;
|
||||
setEditingSourceText(false);
|
||||
_continueIT();
|
||||
}
|
||||
|
||||
ContinuanceModel selectContinuance(int index) {
|
||||
if (_currentITStep.value == null) {
|
||||
throw "onSelectContinuance called when _currentITStep is null";
|
||||
}
|
||||
|
||||
if (index < 0 || index >= _currentITStep.value!.continuances.length) {
|
||||
throw "onSelectContinuance called with invalid index $index";
|
||||
}
|
||||
|
||||
final currentStep = _currentITStep.value!;
|
||||
currentStep.continuances[index] = currentStep.continuances[index].copyWith(
|
||||
wasClicked: true,
|
||||
);
|
||||
_currentITStep.value = _currentITStep.value!.copyWith(
|
||||
continuances: currentStep.continuances,
|
||||
);
|
||||
return _currentITStep.value!.continuances[index];
|
||||
}
|
||||
|
||||
void acceptContinuance(int chosenIndex) {
|
||||
if (_currentITStep.value == null) {
|
||||
throw "onAcceptContinuance called when _currentITStep is null";
|
||||
}
|
||||
|
||||
if (chosenIndex < 0 ||
|
||||
chosenIndex >= _currentITStep.value!.continuances.length) {
|
||||
throw "onAcceptContinuance called with invalid index $chosenIndex";
|
||||
}
|
||||
|
||||
acceptedContinuanceStream.add(
|
||||
CompletedITStepModel(
|
||||
_currentITStep.value!.continuances,
|
||||
chosen: chosenIndex,
|
||||
),
|
||||
);
|
||||
final progress =
|
||||
(_goldRouteTracker!.continuances.indexWhere(
|
||||
(c) =>
|
||||
c.text ==
|
||||
_currentITStep.value!.continuances[chosenIndex].text,
|
||||
) +
|
||||
1) /
|
||||
_goldRouteTracker!.continuances.length;
|
||||
_progress.value = progress;
|
||||
_continueIT();
|
||||
}
|
||||
|
||||
Future<void> _continueIT() async {
|
||||
if (_continuing) return;
|
||||
_continuing = true;
|
||||
|
||||
try {
|
||||
if (_currentITStep.value == null) {
|
||||
await _initTranslationData();
|
||||
} else if (_queue.isEmpty) {
|
||||
closeIT();
|
||||
} else {
|
||||
final nextStepCompleter = _queue.removeFirst();
|
||||
_currentITStep.value = await nextStepCompleter.future;
|
||||
}
|
||||
} catch (e) {
|
||||
onError(e);
|
||||
} finally {
|
||||
_continuing = false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _initTranslationData() async {
|
||||
final res = await _safeRequest("");
|
||||
if (_sourceText.value == null || !_open.value) return;
|
||||
if (res.isError || res.result?.goldContinuances == null) {
|
||||
onError(res.asError!);
|
||||
return;
|
||||
}
|
||||
|
||||
final result = res.result!;
|
||||
_goldRouteTracker = GoldRouteTrackerModel(
|
||||
result.goldContinuances!,
|
||||
_sourceText.value!,
|
||||
);
|
||||
|
||||
_currentITStep.value = ITStepModel.fromResponse(
|
||||
sourceText: _sourceText.value!,
|
||||
currentText: "",
|
||||
responseModel: res.result!,
|
||||
storedGoldContinuances: _goldRouteTracker!.continuances,
|
||||
);
|
||||
|
||||
_fillITStepQueue();
|
||||
}
|
||||
|
||||
Future<void> _fillITStepQueue() async {
|
||||
if (_sourceText.value == null ||
|
||||
_goldRouteTracker!.continuances.length < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
final sourceText = _sourceText.value!;
|
||||
final goldContinuances = _goldRouteTracker!.continuances;
|
||||
String currentText = goldContinuances[0].text;
|
||||
for (int i = 1; i < goldContinuances.length; i++) {
|
||||
if (_sourceText.value == null || !_open.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
final completer = Completer<ITStepModel>();
|
||||
_queue.add(completer);
|
||||
final resp = await _safeRequest(currentText);
|
||||
if (resp.isError) {
|
||||
completer.completeError(resp.asError!);
|
||||
break;
|
||||
} else {
|
||||
final step = ITStepModel.fromResponse(
|
||||
sourceText: sourceText,
|
||||
currentText: currentText,
|
||||
responseModel: resp.result!,
|
||||
storedGoldContinuances: goldContinuances,
|
||||
);
|
||||
completer.complete(step);
|
||||
}
|
||||
|
||||
currentText += goldContinuances[i].text;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:async/async.dart';
|
||||
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/text_loading_shimmer.dart';
|
||||
import 'package:fluffychat/pangea/translation/full_text_translation_repo.dart';
|
||||
import 'package:fluffychat/pangea/translation/full_text_translation_request_model.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
import '../../../widgets/matrix.dart';
|
||||
import '../../bot/utils/bot_style.dart';
|
||||
import '../../common/widgets/card_error_widget.dart';
|
||||
|
||||
class ITFeedbackCard extends StatelessWidget {
|
||||
final FullTextTranslationRequestModel req;
|
||||
|
||||
const ITFeedbackCard(this.req, {super.key});
|
||||
|
||||
Future<Result<String>> _getFeedback() {
|
||||
return FullTextTranslationRepo.get(
|
||||
MatrixState.pangeaController.userController.accessToken,
|
||||
req,
|
||||
).timeout(
|
||||
const Duration(seconds: 10),
|
||||
onTimeout: () => Result.error("Timeout getting translation"),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FutureBuilder<Result<String>>(
|
||||
future: _getFeedback(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasError) {
|
||||
return CardErrorWidget(L10n.of(context).errorFetchingDefinition);
|
||||
}
|
||||
|
||||
return Container(
|
||||
constraints: const BoxConstraints(maxWidth: 300),
|
||||
alignment: Alignment.center,
|
||||
child: Wrap(
|
||||
spacing: 10,
|
||||
alignment: WrapAlignment.center,
|
||||
children: [
|
||||
Text(req.text, style: BotStyle.text(context)),
|
||||
Text("≈", style: BotStyle.text(context)),
|
||||
snapshot.hasData
|
||||
? Text(snapshot.data!.result!, style: BotStyle.text(context))
|
||||
: TextLoadingShimmer(width: min(140, 10.0 * req.text.length)),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:async/async.dart';
|
||||
import 'package:http/http.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/common/config/environment.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import '../../common/network/requests.dart';
|
||||
import '../../common/network/urls.dart';
|
||||
import 'it_request_model.dart';
|
||||
import 'it_response_model.dart';
|
||||
|
||||
class _ITCacheItem {
|
||||
final Future<ITResponseModel> response;
|
||||
final DateTime timestamp;
|
||||
|
||||
const _ITCacheItem({required this.response, required this.timestamp});
|
||||
}
|
||||
|
||||
class ITRepo {
|
||||
static final Map<String, _ITCacheItem> _cache = {};
|
||||
static const Duration _cacheDuration = Duration(minutes: 10);
|
||||
|
||||
static Future<Result<ITResponseModel>> get(ITRequestModel request) {
|
||||
final cached = _getCached(request);
|
||||
if (cached != null) {
|
||||
return _getResult(request, cached);
|
||||
}
|
||||
|
||||
final future = _fetch(request);
|
||||
_setCached(request, future);
|
||||
return _getResult(request, future);
|
||||
}
|
||||
|
||||
static Future<ITResponseModel> _fetch(ITRequestModel request) async {
|
||||
final Requests req = Requests(
|
||||
choreoApiKey: Environment.choreoApiKey,
|
||||
accessToken: MatrixState.pangeaController.userController.accessToken,
|
||||
);
|
||||
final Response res = await req.post(
|
||||
url: PApiUrls.firstStep,
|
||||
body: request.toJson(),
|
||||
);
|
||||
|
||||
if (res.statusCode != 200) {
|
||||
throw Exception('Failed to load interactive translation');
|
||||
}
|
||||
|
||||
final json = jsonDecode(utf8.decode(res.bodyBytes).toString());
|
||||
return ITResponseModel.fromJson(json);
|
||||
}
|
||||
|
||||
static Future<Result<ITResponseModel>> _getResult(
|
||||
ITRequestModel request,
|
||||
Future<ITResponseModel> future,
|
||||
) async {
|
||||
try {
|
||||
final res = await future;
|
||||
return Result.value(res);
|
||||
} catch (e, s) {
|
||||
_cache.remove(request.hashCode.toString());
|
||||
ErrorHandler.logError(e: e, s: s, data: request.toJson());
|
||||
return Result.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
static Future<ITResponseModel>? _getCached(ITRequestModel request) {
|
||||
final cacheKeys = [..._cache.keys];
|
||||
for (final key in cacheKeys) {
|
||||
if (DateTime.now().difference(_cache[key]!.timestamp) >= _cacheDuration) {
|
||||
_cache.remove(key);
|
||||
}
|
||||
}
|
||||
return _cache[request.hashCode.toString()]?.response;
|
||||
}
|
||||
|
||||
static void _setCached(
|
||||
ITRequestModel request,
|
||||
Future<ITResponseModel> response,
|
||||
) {
|
||||
_cache[request.hashCode.toString()] = _ITCacheItem(
|
||||
response: response,
|
||||
timestamp: DateTime.now(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/choreographer/it/completed_it_step_model.dart';
|
||||
import 'package:fluffychat/pangea/common/constants/model_keys.dart';
|
||||
|
||||
class ITRequestModel {
|
||||
final String text;
|
||||
final String customInput;
|
||||
final String sourceLangCode;
|
||||
final String targetLangCode;
|
||||
|
||||
final String? goldTranslation;
|
||||
final List<ContinuanceModel>? goldContinuances;
|
||||
|
||||
const ITRequestModel({
|
||||
required this.text,
|
||||
required this.customInput,
|
||||
required this.sourceLangCode,
|
||||
required this.targetLangCode,
|
||||
required this.goldTranslation,
|
||||
required this.goldContinuances,
|
||||
});
|
||||
|
||||
factory ITRequestModel.fromJson(Map<String, dynamic> json) => ITRequestModel(
|
||||
text: json[ModelKey.text],
|
||||
customInput: json['custom_input'],
|
||||
sourceLangCode: json[ModelKey.srcLang],
|
||||
targetLangCode: json[ModelKey.tgtLang],
|
||||
goldTranslation: json[ModelKey.goldTranslation],
|
||||
goldContinuances: json['gold_continuances'] != null
|
||||
? (json['gold_continuances'])
|
||||
.map((e) => ContinuanceModel.fromJson(e))
|
||||
.toList()
|
||||
: null,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
ModelKey.text: text,
|
||||
'custom_input': customInput,
|
||||
ModelKey.srcLang: sourceLangCode,
|
||||
ModelKey.tgtLang: targetLangCode,
|
||||
ModelKey.goldTranslation: goldTranslation,
|
||||
'gold_continuances': goldContinuances != null
|
||||
? List.from(goldContinuances!.map((e) => e.toJson()))
|
||||
: null,
|
||||
};
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
|
||||
return other is ITRequestModel &&
|
||||
other.text == text &&
|
||||
other.customInput == customInput &&
|
||||
other.sourceLangCode == sourceLangCode &&
|
||||
other.targetLangCode == targetLangCode &&
|
||||
other.goldTranslation == goldTranslation &&
|
||||
listEquals(other.goldContinuances, goldContinuances);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
text.hashCode ^
|
||||
customInput.hashCode ^
|
||||
sourceLangCode.hashCode ^
|
||||
targetLangCode.hashCode ^
|
||||
goldTranslation.hashCode ^
|
||||
Object.hashAll(goldContinuances ?? []);
|
||||
}
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/choreographer/choreo_constants.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/it/completed_it_step_model.dart';
|
||||
|
||||
class ITResponseModel {
|
||||
final String fullTextTranslation;
|
||||
final List<ContinuanceModel> continuances;
|
||||
final List<ContinuanceModel>? goldContinuances;
|
||||
final bool isFinal;
|
||||
final String? translationId;
|
||||
final int payloadId;
|
||||
|
||||
const ITResponseModel({
|
||||
required this.fullTextTranslation,
|
||||
required this.continuances,
|
||||
required this.translationId,
|
||||
required this.goldContinuances,
|
||||
required this.isFinal,
|
||||
required this.payloadId,
|
||||
});
|
||||
|
||||
factory ITResponseModel.fromJson(Map<String, dynamic> json) {
|
||||
//PTODO - is continuances a variable type? can we change that?
|
||||
if (json['continuances'].runtimeType == String) {
|
||||
debugPrint("continuances was string - ${json['continuances']}");
|
||||
json['continuances'] = [];
|
||||
json['finished'] = true;
|
||||
}
|
||||
|
||||
final List<ContinuanceModel> interimCont = (json['continuances'] as List)
|
||||
.mapIndexed((index, e) {
|
||||
e["index"] = index;
|
||||
return ContinuanceModel.fromJson(e);
|
||||
})
|
||||
.toList()
|
||||
.take(ChoreoConstants.numberOfITChoices)
|
||||
.toList()
|
||||
.cast<ContinuanceModel>()
|
||||
//can't do this on the backend because step translation can't filter them out
|
||||
.where((element) => element.inDictionary)
|
||||
.toList();
|
||||
|
||||
interimCont.shuffle();
|
||||
|
||||
return ITResponseModel(
|
||||
fullTextTranslation: json["full_text_translation"] ?? json["translation"],
|
||||
continuances: interimCont,
|
||||
translationId: json['translation_id'],
|
||||
payloadId: json['payload_id'] ?? 0,
|
||||
isFinal: json['finished'] ?? false,
|
||||
goldContinuances: json['gold_continuances'] != null
|
||||
? (json['gold_continuances'] as Iterable).map((e) {
|
||||
e["gold"] = true;
|
||||
return ContinuanceModel.fromJson(e);
|
||||
}).toList()
|
||||
: null,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['full_text_translation'] = fullTextTranslation;
|
||||
data['continuances'] = continuances.map((v) => v.toJson()).toList();
|
||||
if (translationId != null) {
|
||||
data['translation_id'] = translationId;
|
||||
}
|
||||
data['payload_id'] = payloadId;
|
||||
data["finished"] = isFinal;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ItShimmer extends StatelessWidget {
|
||||
const ItShimmer({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final color = Theme.of(context).colorScheme.primary.withAlpha(50);
|
||||
|
||||
return Wrap(
|
||||
alignment: WrapAlignment.center,
|
||||
spacing: 4,
|
||||
runSpacing: 4,
|
||||
children: List.generate(3, (_) {
|
||||
return ImageFiltered(
|
||||
imageFilter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
|
||||
child: TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
minimumSize: const Size(50, 36),
|
||||
backgroundColor: color,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 7),
|
||||
),
|
||||
onPressed: null,
|
||||
child: const Text(
|
||||
" ", // 10 spaces
|
||||
style: TextStyle(color: Colors.transparent, fontSize: 16),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
import 'package:fluffychat/pangea/choreographer/choreo_constants.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/it/completed_it_step_model.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/it/gold_route_tracker_model.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/it/it_response_model.dart';
|
||||
|
||||
class ITStepModel {
|
||||
late List<ContinuanceModel> continuances;
|
||||
late bool isFinal;
|
||||
|
||||
ITStepModel({this.continuances = const [], this.isFinal = false});
|
||||
|
||||
factory ITStepModel.fromResponse({
|
||||
required String sourceText,
|
||||
required String currentText,
|
||||
required ITResponseModel responseModel,
|
||||
required List<ContinuanceModel>? storedGoldContinuances,
|
||||
}) {
|
||||
final List<ContinuanceModel> gold =
|
||||
storedGoldContinuances ?? responseModel.goldContinuances ?? [];
|
||||
final goldTracker = GoldRouteTrackerModel(gold, sourceText);
|
||||
|
||||
final isFinal = responseModel.isFinal;
|
||||
List<ContinuanceModel> continuances;
|
||||
if (responseModel.continuances.isEmpty) {
|
||||
continuances = [];
|
||||
} else {
|
||||
final ContinuanceModel? goldCont = goldTracker.currentContinuance(
|
||||
currentText: currentText,
|
||||
sourceText: sourceText,
|
||||
);
|
||||
if (goldCont != null) {
|
||||
continuances = [
|
||||
...responseModel.continuances
|
||||
.where((c) => c.text.toLowerCase() != goldCont.text.toLowerCase())
|
||||
.map((e) {
|
||||
//we only want one green choice and for that to be our gold
|
||||
if (e.level == ChoreoConstants.levelThresholdForGreen) {
|
||||
return e.copyWith(
|
||||
level: ChoreoConstants.levelThresholdForYellow,
|
||||
);
|
||||
}
|
||||
return e;
|
||||
}),
|
||||
goldCont,
|
||||
];
|
||||
continuances.shuffle();
|
||||
} else {
|
||||
continuances = List<ContinuanceModel>.from(responseModel.continuances);
|
||||
}
|
||||
}
|
||||
|
||||
return ITStepModel(continuances: continuances, isFinal: isFinal);
|
||||
}
|
||||
|
||||
ITStepModel copyWith({List<ContinuanceModel>? continuances, bool? isFinal}) {
|
||||
return ITStepModel(
|
||||
continuances: continuances ?? this.continuances,
|
||||
isFinal: isFinal ?? this.isFinal,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:async/async.dart';
|
||||
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/bot/utils/bot_style.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/it/contextual_definition_repo.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/it/contextual_definition_request_model.dart';
|
||||
import 'package:fluffychat/pangea/common/widgets/content_loading_indicator.dart';
|
||||
import 'package:fluffychat/pangea/languages/language_constants.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
class WordDataCard extends StatelessWidget {
|
||||
final String word;
|
||||
final String fullText;
|
||||
final String langCode;
|
||||
|
||||
const WordDataCard({
|
||||
super.key,
|
||||
required this.word,
|
||||
required this.fullText,
|
||||
required this.langCode,
|
||||
});
|
||||
|
||||
ContextualDefinitionRequestModel get _request =>
|
||||
ContextualDefinitionRequestModel(
|
||||
fullText: fullText,
|
||||
word: word,
|
||||
fullTextLang: langCode,
|
||||
wordLang: langCode,
|
||||
feedbackLang:
|
||||
MatrixState.pangeaController.userController.userL1Code ??
|
||||
LanguageKeys.defaultLanguage,
|
||||
);
|
||||
|
||||
Future<Result<String>> _fetchDefinition() {
|
||||
return ContextualDefinitionRepo.get(
|
||||
MatrixState.pangeaController.userController.accessToken,
|
||||
_request,
|
||||
).timeout(
|
||||
const Duration(seconds: 10),
|
||||
onTimeout: () => Result.error("Timeout getting definition"),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Center(
|
||||
child: FutureBuilder<Result<String>>(
|
||||
future: _fetchDefinition(),
|
||||
builder: (context, snapshot) {
|
||||
if (!snapshot.hasData) {
|
||||
return const ContentLoadingIndicator();
|
||||
}
|
||||
final result = snapshot.data!;
|
||||
if (result.isError) {
|
||||
return Text(
|
||||
L10n.of(context).sorryNoResults,
|
||||
style: BotStyle.text(context),
|
||||
textAlign: TextAlign.center,
|
||||
);
|
||||
}
|
||||
return Text(result.result!, style: BotStyle.text(context));
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -31,20 +31,13 @@ class PApiUrls {
|
|||
"${PApiUrls._choreoEndpoint}/language_detection";
|
||||
|
||||
static String igcLite = "${PApiUrls._choreoEndpoint}/grammar_v2";
|
||||
static String spanDetails = "${PApiUrls._choreoEndpoint}/span_details";
|
||||
|
||||
static String simpleTranslation =
|
||||
"${PApiUrls._choreoEndpoint}/translation/direct";
|
||||
static String tokenize = "${PApiUrls._choreoEndpoint}/tokenize";
|
||||
static String contextualDefinition =
|
||||
"${PApiUrls._choreoEndpoint}/contextual_definition";
|
||||
|
||||
static String firstStep = "${PApiUrls._choreoEndpoint}/it_initialstep";
|
||||
|
||||
static String textToSpeech = "${PApiUrls._choreoEndpoint}/text_to_speech";
|
||||
static String speechToText = "${PApiUrls._choreoEndpoint}/speech_to_text";
|
||||
static String phoneticTranscription =
|
||||
"${PApiUrls._choreoEndpoint}/phonetic_transcription";
|
||||
static String phoneticTranscriptionV2 =
|
||||
"${PApiUrls._choreoEndpoint}/phonetic_transcription_v2";
|
||||
|
||||
|
|
@ -55,15 +48,6 @@ class PApiUrls {
|
|||
"${PApiUrls._choreoEndpoint}/lemma_definition";
|
||||
static String morphDictionary = "${PApiUrls._choreoEndpoint}/morph_meaning";
|
||||
|
||||
// static String activityPlan = "${PApiUrls._choreoEndpoint}/activity_plan";
|
||||
// static String activityPlanGeneration =
|
||||
// "${PApiUrls._choreoEndpoint}/activity_plan/generate";
|
||||
// static String activityPlanSearch =
|
||||
// "${PApiUrls._choreoEndpoint}/activity_plan/search";
|
||||
// static String activityModeList = "${PApiUrls._choreoEndpoint}/modes";
|
||||
// static String objectiveList = "${PApiUrls._choreoEndpoint}/objectives";
|
||||
// static String topicList = "${PApiUrls._choreoEndpoint}/topics";
|
||||
|
||||
static String activitySummary =
|
||||
"${PApiUrls._choreoEndpoint}/activity_summary";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
|
|
@ -8,7 +10,6 @@ import 'package:fluffychat/pangea/common/widgets/choice_animation.dart';
|
|||
import 'package:fluffychat/pangea/text_to_speech/tts_controller.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import '../../bot/utils/bot_style.dart';
|
||||
import '../../choreographer/it/it_shimmer.dart';
|
||||
|
||||
typedef ChoiceCallback = void Function(String value, int index);
|
||||
|
||||
|
|
@ -46,7 +47,33 @@ class ChoicesArray extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return isLoading && (choices == null || choices!.length <= 1)
|
||||
? const ItShimmer()
|
||||
? Wrap(
|
||||
alignment: WrapAlignment.center,
|
||||
spacing: 4,
|
||||
runSpacing: 4,
|
||||
children: List.generate(3, (_) {
|
||||
return ImageFiltered(
|
||||
imageFilter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
|
||||
child: TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
minimumSize: const Size(50, 36),
|
||||
backgroundColor: Theme.of(
|
||||
context,
|
||||
).colorScheme.primary.withAlpha(50),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 7),
|
||||
),
|
||||
onPressed: null,
|
||||
child: const Text(
|
||||
" ", // 10 spaces
|
||||
style: TextStyle(color: Colors.transparent, fontSize: 16),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
)
|
||||
: Wrap(
|
||||
alignment: WrapAlignment.center,
|
||||
spacing: 4.0,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue