refactor IT bar
This commit is contained in:
parent
2b522b6dd7
commit
978d70822f
5 changed files with 459 additions and 423 deletions
|
|
@ -303,13 +303,15 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
return;
|
||||
}
|
||||
if (!scrollController.hasClients) return;
|
||||
if (timeline?.allowNewEvent == false ||
|
||||
scrollController.position.pixels > 0 && _scrolledUp == false) {
|
||||
setState(() => _scrolledUp = true);
|
||||
} else if (scrollController.position.pixels <= 0 && _scrolledUp == true) {
|
||||
setState(() => _scrolledUp = false);
|
||||
setReadMarker();
|
||||
}
|
||||
// #Pangea
|
||||
// if (timeline?.allowNewEvent == false ||
|
||||
// scrollController.position.pixels > 0 && _scrolledUp == false) {
|
||||
// setState(() => _scrolledUp = true);
|
||||
// } else if (scrollController.position.pixels <= 0 && _scrolledUp == true) {
|
||||
// setState(() => _scrolledUp = false);
|
||||
// setReadMarker();
|
||||
// }
|
||||
// Pangea#
|
||||
|
||||
if (scrollController.position.pixels == 0 ||
|
||||
scrollController.position.pixels == 64) {
|
||||
|
|
@ -789,6 +791,8 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
_botAudioSubscription?.cancel();
|
||||
_router.routeInformationProvider.removeListener(_onRouteChanged);
|
||||
carouselController.dispose();
|
||||
scrollController.dispose();
|
||||
inputFocus.dispose();
|
||||
TokensUtil.clearNewTokenCache();
|
||||
//Pangea#
|
||||
super.dispose();
|
||||
|
|
@ -867,6 +871,7 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
inReplyTo: replyEvent,
|
||||
editEventId: editEvent?.eventId,
|
||||
);
|
||||
inputFocus.unfocus();
|
||||
sendController.setSystemText("", EditType.other);
|
||||
setState(() => _fakeEventIDs.add(eventID));
|
||||
|
||||
|
|
@ -1697,7 +1702,7 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
|
||||
void onSelectMessage(Event event) {
|
||||
// #Pangea
|
||||
if (choreographer.isITOpen) {
|
||||
if (choreographer.itController.open.value) {
|
||||
return;
|
||||
}
|
||||
// Pangea#
|
||||
|
|
@ -1751,14 +1756,20 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
await choreographer.send();
|
||||
} on ShowPaywallException {
|
||||
PaywallCard.show(context, choreographer.inputTransformTargetKey);
|
||||
return;
|
||||
} on OpenMatchesException {
|
||||
if (choreographer.firstIGCMatch != null) {
|
||||
OverlayUtil.showIGCMatch(
|
||||
choreographer.firstIGCMatch!,
|
||||
choreographer,
|
||||
context,
|
||||
);
|
||||
if (choreographer.firstOpenMatch != null) {
|
||||
if (choreographer.firstOpenMatch!.updatedMatch.isITStart) {
|
||||
choreographer.openIT(choreographer.firstOpenMatch!);
|
||||
} else {
|
||||
OverlayUtil.showIGCMatch(
|
||||
choreographer.firstOpenMatch!,
|
||||
choreographer,
|
||||
context,
|
||||
);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Pangea#
|
||||
FocusScope.of(context).requestFocus(inputFocus);
|
||||
|
|
@ -2064,13 +2075,6 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
});
|
||||
}
|
||||
|
||||
double inputBarHeight = 64;
|
||||
void updateInputBarHeight(double height) {
|
||||
if (mounted && height != inputBarHeight) {
|
||||
setState(() => inputBarHeight = height);
|
||||
}
|
||||
}
|
||||
|
||||
bool get displayChatDetailsColumn {
|
||||
try {
|
||||
return _displayChatDetailsColumn.value;
|
||||
|
|
@ -2207,15 +2211,27 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
OverlayUtil.showPositionedCard(
|
||||
context: context,
|
||||
cardToShow: LanguageMismatchPopup(
|
||||
targetLanguage: targetLanguage,
|
||||
onUpdate: () async {
|
||||
final igcMatch = await choreographer.requestLanguageAssistance();
|
||||
if (igcMatch != null) {
|
||||
OverlayUtil.showIGCMatch(
|
||||
igcMatch,
|
||||
choreographer,
|
||||
context,
|
||||
);
|
||||
onConfirm: () async {
|
||||
await MatrixState.pangeaController.userController.updateProfile(
|
||||
(profile) {
|
||||
profile.userSettings.targetLanguage = targetLanguage;
|
||||
return profile;
|
||||
},
|
||||
waitForDataInSync: true,
|
||||
);
|
||||
|
||||
await choreographer.requestLanguageAssistance();
|
||||
final openMatch = choreographer.firstOpenMatch;
|
||||
if (openMatch != null) {
|
||||
if (openMatch.updatedMatch.isITStart) {
|
||||
choreographer.openIT(openMatch);
|
||||
} else {
|
||||
OverlayUtil.showIGCMatch(
|
||||
openMatch,
|
||||
choreographer,
|
||||
context,
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
|
|
@ -2246,9 +2262,10 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
targetAnchor: Alignment.topRight,
|
||||
context: context,
|
||||
child: MessageAnalyticsFeedback(
|
||||
overlayId: "msg_analytics_feedback_$eventId",
|
||||
newGrammarConstructs: newGrammarConstructs,
|
||||
newVocabConstructs: newVocabConstructs,
|
||||
close: () => MatrixState.pAnyState
|
||||
.closeOverlay("msg_analytics_feedback_$eventId"),
|
||||
),
|
||||
transformTargetId: eventId,
|
||||
ignorePointer: true,
|
||||
|
|
|
|||
|
|
@ -44,13 +44,13 @@ class Choreographer extends ChangeNotifier {
|
|||
|
||||
ChoreoRecord? _choreoRecord;
|
||||
|
||||
bool _isFetching = false;
|
||||
final ValueNotifier<bool> _isFetching = ValueNotifier(false);
|
||||
int _timesClicked = 0;
|
||||
|
||||
Timer? _debounceTimer;
|
||||
String? _lastChecked;
|
||||
ChoreoMode _choreoMode = ChoreoMode.igc;
|
||||
String? _sourceText;
|
||||
final ValueNotifier<String?> _sourceText = ValueNotifier(null);
|
||||
|
||||
StreamSubscription? _languageStream;
|
||||
StreamSubscription? _settingsUpdateStream;
|
||||
|
|
@ -60,10 +60,10 @@ class Choreographer extends ChangeNotifier {
|
|||
}
|
||||
|
||||
int get timesClicked => _timesClicked;
|
||||
bool get isFetching => _isFetching;
|
||||
ValueNotifier<bool> get isFetching => _isFetching;
|
||||
ChoreoMode get choreoMode => _choreoMode;
|
||||
|
||||
String? get sourceText => _sourceText;
|
||||
ValueNotifier<String?> get sourceText => _sourceText;
|
||||
String get currentText => textController.text;
|
||||
|
||||
void _initialize() {
|
||||
|
|
@ -94,9 +94,9 @@ class Choreographer extends ChangeNotifier {
|
|||
_choreoMode = ChoreoMode.igc;
|
||||
_lastChecked = null;
|
||||
_timesClicked = 0;
|
||||
_isFetching = false;
|
||||
_isFetching.value = false;
|
||||
_choreoRecord = null;
|
||||
_sourceText = null;
|
||||
_sourceText.value = null;
|
||||
itController.clear();
|
||||
igc.clear();
|
||||
_resetDebounceTimer();
|
||||
|
|
@ -109,6 +109,8 @@ class Choreographer extends ChangeNotifier {
|
|||
textController.dispose();
|
||||
_languageStream?.cancel();
|
||||
_settingsUpdateStream?.cancel();
|
||||
_debounceTimer?.cancel();
|
||||
_isFetching.dispose();
|
||||
TtsController.stop();
|
||||
}
|
||||
|
||||
|
|
@ -123,7 +125,7 @@ class Choreographer extends ChangeNotifier {
|
|||
|
||||
// if user is doing IT, call closeIT here to
|
||||
// ensure source text is replaced when needed
|
||||
if (isITOpen && _timesClicked > 1) {
|
||||
if (itController.open.value && _timesClicked > 1) {
|
||||
closeIT();
|
||||
}
|
||||
}
|
||||
|
|
@ -151,27 +153,42 @@ class Choreographer extends ChangeNotifier {
|
|||
|
||||
void _startLoading() {
|
||||
_lastChecked = textController.text;
|
||||
_isFetching = true;
|
||||
_isFetching.value = true;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void _stopLoading() {
|
||||
_isFetching = false;
|
||||
_isFetching.value = false;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<PangeaMatchState?> requestLanguageAssistance() async {
|
||||
await _getLanguageAssistance(manual: true);
|
||||
if (igc.canShowFirstMatch) {
|
||||
return igc.onShowFirstMatch();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
Future<void> requestLanguageAssistance() =>
|
||||
_getLanguageAssistance(manual: true);
|
||||
|
||||
Future<void> send() async {
|
||||
Future<void> send([int recurrence = 0]) async {
|
||||
// if isFetching, already called to getLanguageHelp and hasn't completed yet
|
||||
// could happen if user clicked send button multiple times in a row
|
||||
if (_isFetching) return;
|
||||
if (_isFetching.value) return;
|
||||
|
||||
if (errorService.isError) {
|
||||
await _sendWithIGC();
|
||||
return;
|
||||
}
|
||||
|
||||
if (recurrence > 1) {
|
||||
ErrorHandler.logError(
|
||||
e: Exception("Choreographer send exceeded max recurrences"),
|
||||
level: SentryLevel.warning,
|
||||
data: {
|
||||
"currentText": chatController.sendController.text,
|
||||
"l1LangCode": l1LangCode,
|
||||
"l2LangCode": l2LangCode,
|
||||
"choreoRecord": _choreoRecord?.toJson(),
|
||||
},
|
||||
);
|
||||
await _sendWithIGC();
|
||||
return;
|
||||
}
|
||||
|
||||
if (igc.canShowFirstMatch) {
|
||||
throw OpenMatchesException();
|
||||
|
|
@ -200,9 +217,12 @@ class Choreographer extends ChangeNotifier {
|
|||
|
||||
if (!igc.hasIGCTextData && !itController.dismissed) {
|
||||
await _getLanguageAssistance();
|
||||
await send();
|
||||
// it's possible for this not to be true, i.e. if IGC has an error
|
||||
if (igc.hasIGCTextData) {
|
||||
await send(recurrence + 1);
|
||||
}
|
||||
} else {
|
||||
_sendWithIGC();
|
||||
await _sendWithIGC();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -230,7 +250,7 @@ class Choreographer extends ChangeNotifier {
|
|||
if (textController.editType == EditType.it) {
|
||||
_getLanguageAssistance();
|
||||
} else {
|
||||
_sourceText = null;
|
||||
_sourceText.value = null;
|
||||
_debounceTimer ??= Timer(
|
||||
const Duration(milliseconds: ChoreoConstants.msBeforeIGCStart),
|
||||
() => _getLanguageAssistance(),
|
||||
|
|
@ -278,10 +298,10 @@ class Choreographer extends ChangeNotifier {
|
|||
final message = chatController.sendController.text;
|
||||
final fakeEventId = chatController.sendFakeMessage();
|
||||
final PangeaRepresentation? originalWritten =
|
||||
_choreoRecord?.includedIT == true && _sourceText != null
|
||||
_choreoRecord?.includedIT == true && _sourceText.value != null
|
||||
? PangeaRepresentation(
|
||||
langCode: l1LangCode ?? LanguageKeys.unknownLanguage,
|
||||
text: _sourceText!,
|
||||
text: _sourceText.value!,
|
||||
originalWritten: true,
|
||||
originalSent: false,
|
||||
)
|
||||
|
|
@ -348,21 +368,21 @@ class Choreographer extends ChangeNotifier {
|
|||
if (!itMatch.updatedMatch.isITStart) {
|
||||
throw Exception("Attempted to open IT with a non-IT start match");
|
||||
}
|
||||
chatController.inputFocus.unfocus();
|
||||
|
||||
_choreoMode = ChoreoMode.it;
|
||||
_sourceText = textController.text;
|
||||
itController.openIT();
|
||||
|
||||
igc.clear();
|
||||
setChoreoMode(ChoreoMode.it);
|
||||
_sourceText.value = textController.text;
|
||||
textController.setSystemText("", EditType.it);
|
||||
|
||||
itController.openIT();
|
||||
igc.clear();
|
||||
|
||||
_initChoreoRecord();
|
||||
itMatch.setStatus(PangeaMatchStatus.accepted);
|
||||
_choreoRecord!.addRecord(
|
||||
textController.text,
|
||||
"",
|
||||
match: itMatch.updatedMatch,
|
||||
);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void closeIT() {
|
||||
|
|
@ -394,7 +414,7 @@ class Choreographer extends ChangeNotifier {
|
|||
}
|
||||
|
||||
void setSourceText(String? text) {
|
||||
_sourceText = text;
|
||||
_sourceText.value = text;
|
||||
}
|
||||
|
||||
void setEditingSourceText(bool value) {
|
||||
|
|
@ -403,7 +423,8 @@ class Choreographer extends ChangeNotifier {
|
|||
}
|
||||
|
||||
void submitSourceTextEdits(String text) {
|
||||
_sourceText = text;
|
||||
_sourceText.value = text;
|
||||
textController.setSystemText("", EditType.it);
|
||||
itController.onSubmitEdits();
|
||||
notifyListeners();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,28 +19,27 @@ import 'choreographer.dart';
|
|||
class ITController {
|
||||
final Choreographer _choreographer;
|
||||
|
||||
ITStep? _currentITStep;
|
||||
ValueNotifier<ITStep?> _currentITStep = ValueNotifier(null);
|
||||
final List<Completer<ITStep>> _queue = [];
|
||||
GoldRouteTracker? _goldRouteTracker;
|
||||
|
||||
bool _open = false;
|
||||
bool _editing = false;
|
||||
final ValueNotifier<bool> _open = ValueNotifier(false);
|
||||
final ValueNotifier<bool> _editing = ValueNotifier(false);
|
||||
bool _dismissed = false;
|
||||
|
||||
ITController(this._choreographer);
|
||||
|
||||
bool get open => _open;
|
||||
bool get editing => _editing;
|
||||
ValueNotifier<bool> get open => _open;
|
||||
ValueNotifier<bool> get editing => _editing;
|
||||
bool get dismissed => _dismissed;
|
||||
List<Continuance>? get continuances => _currentITStep?.continuances;
|
||||
bool get isTranslationDone => _currentITStep?.isFinal ?? false;
|
||||
ValueNotifier<ITStep?> get currentITStep => _currentITStep;
|
||||
|
||||
String? get _sourceText => _choreographer.sourceText;
|
||||
ValueNotifier<String?> get _sourceText => _choreographer.sourceText;
|
||||
|
||||
ITRequestModel _request(String textInput) {
|
||||
assert(_sourceText != null);
|
||||
assert(_sourceText.value != null);
|
||||
return ITRequestModel(
|
||||
text: _sourceText!,
|
||||
text: _sourceText.value!,
|
||||
customInput: textInput,
|
||||
sourceLangCode:
|
||||
MatrixState.pangeaController.languageController.activeL1Code()!,
|
||||
|
|
@ -53,13 +52,15 @@ class ITController {
|
|||
);
|
||||
}
|
||||
|
||||
void openIT() => _open = true;
|
||||
void openIT() {
|
||||
_open.value = true;
|
||||
}
|
||||
|
||||
void closeIT() {
|
||||
// if the user hasn't gone through any IT steps, reset the text
|
||||
if (_choreographer.currentText.isEmpty && _sourceText != null) {
|
||||
if (_choreographer.currentText.isEmpty && _sourceText.value != null) {
|
||||
_choreographer.textController.setSystemText(
|
||||
_sourceText!,
|
||||
_sourceText.value!,
|
||||
EditType.itDismissed,
|
||||
);
|
||||
}
|
||||
|
|
@ -70,77 +71,81 @@ class ITController {
|
|||
void clear({bool dismissed = false}) {
|
||||
MatrixState.pAnyState.closeOverlay("it_feedback_card");
|
||||
|
||||
_open = false;
|
||||
_editing = false;
|
||||
_open.value = false;
|
||||
_editing.value = false;
|
||||
_dismissed = dismissed;
|
||||
_queue.clear();
|
||||
_currentITStep = null;
|
||||
_currentITStep = ValueNotifier(null);
|
||||
_goldRouteTracker = null;
|
||||
|
||||
_choreographer.setChoreoMode(ChoreoMode.igc);
|
||||
_choreographer.setSourceText(null);
|
||||
}
|
||||
|
||||
void setEditing(bool value) => _editing = value;
|
||||
void setEditing(bool value) {
|
||||
_editing.value = value;
|
||||
}
|
||||
|
||||
void onSubmitEdits() {
|
||||
_editing = false;
|
||||
_editing.value = false;
|
||||
_queue.clear();
|
||||
_currentITStep = null;
|
||||
_currentITStep = ValueNotifier(null);
|
||||
_goldRouteTracker = null;
|
||||
continueIT();
|
||||
}
|
||||
|
||||
Continuance onSelectContinuance(int index) {
|
||||
if (_currentITStep == null) {
|
||||
throw "onSelectContinuance called with null currentITStep";
|
||||
if (_currentITStep.value == null) {
|
||||
throw "onSelectContinuance called when _currentITStep is null";
|
||||
}
|
||||
|
||||
if (index < 0 || index >= _currentITStep!.continuances.length) {
|
||||
if (index < 0 || index >= _currentITStep.value!.continuances.length) {
|
||||
throw "onSelectContinuance called with invalid index $index";
|
||||
}
|
||||
|
||||
final step = _currentITStep!.continuances[index];
|
||||
_currentITStep!.continuances[index] = step.copyWith(
|
||||
final currentStep = _currentITStep.value!;
|
||||
currentStep.continuances[index] = currentStep.continuances[index].copyWith(
|
||||
wasClicked: true,
|
||||
);
|
||||
return _currentITStep!.continuances[index];
|
||||
_currentITStep.value = _currentITStep.value!.copyWith(
|
||||
continuances: currentStep.continuances,
|
||||
);
|
||||
return _currentITStep.value!.continuances[index];
|
||||
}
|
||||
|
||||
CompletedITStep getAcceptedITStep(int chosenIndex) {
|
||||
if (_currentITStep == null) {
|
||||
throw "getAcceptedITStep called with null currentITStep";
|
||||
if (_currentITStep.value == null) {
|
||||
throw "getAcceptedITStep called when _currentITStep is null";
|
||||
}
|
||||
|
||||
if (chosenIndex < 0 || chosenIndex >= _currentITStep!.continuances.length) {
|
||||
if (chosenIndex < 0 ||
|
||||
chosenIndex >= _currentITStep.value!.continuances.length) {
|
||||
throw "getAcceptedITStep called with invalid index $chosenIndex";
|
||||
}
|
||||
|
||||
return CompletedITStep(
|
||||
_currentITStep!.continuances,
|
||||
_currentITStep.value!.continuances,
|
||||
chosen: chosenIndex,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> continueIT() async {
|
||||
if (_currentITStep == null) {
|
||||
if (_currentITStep.value == null) {
|
||||
await _initTranslationData();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_queue.isEmpty) {
|
||||
_choreographer.closeIT();
|
||||
return;
|
||||
}
|
||||
|
||||
final nextStepCompleter = _queue.removeAt(0);
|
||||
try {
|
||||
_currentITStep = await nextStepCompleter.future;
|
||||
} catch (e) {
|
||||
if (_open) {
|
||||
_choreographer.errorService.setErrorAndLock(
|
||||
ChoreoError(raw: e),
|
||||
);
|
||||
} else {
|
||||
try {
|
||||
final nextStepCompleter = _queue.removeAt(0);
|
||||
_currentITStep.value = await nextStepCompleter.future;
|
||||
} catch (e) {
|
||||
if (_open.value) {
|
||||
_choreographer.errorService.setErrorAndLock(
|
||||
ChoreoError(raw: e),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -156,7 +161,7 @@ class ITController {
|
|||
},
|
||||
);
|
||||
|
||||
if (_sourceText == null || !_open) return;
|
||||
if (_sourceText.value == null || !_open.value) return;
|
||||
if (res.isError || res.result?.goldContinuances == null) {
|
||||
_choreographer.errorService.setErrorAndLock(
|
||||
ChoreoError(raw: res.asError),
|
||||
|
|
@ -167,11 +172,11 @@ class ITController {
|
|||
final result = res.result!;
|
||||
_goldRouteTracker = GoldRouteTracker(
|
||||
result.goldContinuances!,
|
||||
_sourceText!,
|
||||
_sourceText.value!,
|
||||
);
|
||||
|
||||
_currentITStep = ITStep(
|
||||
sourceText: _sourceText!,
|
||||
_currentITStep.value = ITStep.fromResponse(
|
||||
sourceText: _sourceText.value!,
|
||||
currentText: currentText,
|
||||
responseModel: res.result!,
|
||||
storedGoldContinuances: _goldRouteTracker!.continuances,
|
||||
|
|
@ -181,11 +186,13 @@ class ITController {
|
|||
}
|
||||
|
||||
Future<void> _fillITStepQueue() async {
|
||||
if (_sourceText == null || _goldRouteTracker!.continuances.length < 2) {
|
||||
if (_sourceText.value == null ||
|
||||
_goldRouteTracker!.continuances.length < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
final sourceText = _sourceText!;
|
||||
final sourceText = _sourceText.value!;
|
||||
final goldContinuances = _goldRouteTracker!.continuances;
|
||||
String currentText =
|
||||
_choreographer.currentText + _goldRouteTracker!.continuances[0].text;
|
||||
|
||||
|
|
@ -199,21 +206,22 @@ class ITController {
|
|||
);
|
||||
},
|
||||
);
|
||||
if (_queue.isEmpty) break;
|
||||
|
||||
if (res.isError) {
|
||||
_queue.last.completeError(res.asError!);
|
||||
break;
|
||||
} else {
|
||||
final step = ITStep(
|
||||
final step = ITStep.fromResponse(
|
||||
sourceText: sourceText,
|
||||
currentText: currentText,
|
||||
responseModel: res.result!,
|
||||
storedGoldContinuances: _goldRouteTracker!.continuances,
|
||||
storedGoldContinuances: goldContinuances,
|
||||
);
|
||||
_queue.last.complete(step);
|
||||
}
|
||||
|
||||
currentText += _goldRouteTracker!.continuances[i].text;
|
||||
currentText += goldContinuances[i].text;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -259,7 +267,9 @@ class ITStep {
|
|||
late List<Continuance> continuances;
|
||||
late bool isFinal;
|
||||
|
||||
ITStep({
|
||||
ITStep({this.continuances = const [], this.isFinal = false});
|
||||
|
||||
factory ITStep.fromResponse({
|
||||
required String sourceText,
|
||||
required String currentText,
|
||||
required ITResponseModel responseModel,
|
||||
|
|
@ -269,8 +279,8 @@ class ITStep {
|
|||
storedGoldContinuances ?? responseModel.goldContinuances ?? [];
|
||||
final goldTracker = GoldRouteTracker(gold, sourceText);
|
||||
|
||||
isFinal = responseModel.isFinal;
|
||||
|
||||
final isFinal = responseModel.isFinal;
|
||||
List<Continuance> continuances;
|
||||
if (responseModel.continuances.isEmpty) {
|
||||
continuances = [];
|
||||
} else {
|
||||
|
|
@ -298,5 +308,20 @@ class ITStep {
|
|||
continuances = List<Continuance>.from(responseModel.continuances);
|
||||
}
|
||||
}
|
||||
|
||||
return ITStep(
|
||||
continuances: continuances,
|
||||
isFinal: isFinal,
|
||||
);
|
||||
}
|
||||
|
||||
ITStep copyWith({
|
||||
List<Continuance>? continuances,
|
||||
bool? isFinal,
|
||||
}) {
|
||||
return ITStep(
|
||||
continuances: continuances ?? this.continuances,
|
||||
isFinal: isFinal ?? this.isFinal,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ class PangeaTextController extends TextEditingController {
|
|||
final SubscriptionStatus canSendStatus = choreographer
|
||||
.pangeaController.subscriptionController.subscriptionStatus;
|
||||
if (canSendStatus == SubscriptionStatus.shouldShowPaywall &&
|
||||
!choreographer.isFetching &&
|
||||
!choreographer.isFetching.value &&
|
||||
text.isNotEmpty) {
|
||||
return TextSpan(
|
||||
text: text,
|
||||
|
|
@ -214,7 +214,7 @@ class PangeaTextController extends TextEditingController {
|
|||
spans.add(TextSpan(text: text, style: defaultStyle));
|
||||
}
|
||||
|
||||
final openMatch = choreographer.openIGCMatch?.updatedMatch.match;
|
||||
final openMatch = choreographer.openMatch?.updatedMatch.match;
|
||||
final style = _textStyle(
|
||||
match.updatedMatch,
|
||||
defaultStyle,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
import 'dart:async';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||
|
|
@ -10,7 +8,6 @@ import 'package:fluffychat/l10n/l10n.dart';
|
|||
import 'package:fluffychat/pangea/choreographer/constants/choreo_constants.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/controllers/choreographer.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/controllers/extensions/choregrapher_user_settings_extension.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/controllers/extensions/choreographer_state_extension.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/controllers/extensions/choreographer_ui_extension.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/models/it_step.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/repo/full_text_translation_request_model.dart';
|
||||
|
|
@ -34,223 +31,49 @@ class ITBar extends StatefulWidget {
|
|||
}
|
||||
|
||||
class ITBarState extends State<ITBar> with SingleTickerProviderStateMixin {
|
||||
bool showedClickInstruction = false;
|
||||
late AnimationController _controller;
|
||||
late Animation<double> _animation;
|
||||
bool wasOpen = false;
|
||||
final TextEditingController _sourceTextController = TextEditingController();
|
||||
|
||||
Timer? _successTimer;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
// Rebuild the widget each time there's an update from choreo.
|
||||
widget.choreographer.addListener(() {
|
||||
if (widget.choreographer.isITOpen != wasOpen) {
|
||||
widget.choreographer.isITOpen
|
||||
? _controller.forward()
|
||||
: _controller.reverse();
|
||||
}
|
||||
wasOpen = widget.choreographer.isITOpen;
|
||||
setState(() {});
|
||||
});
|
||||
|
||||
wasOpen = widget.choreographer.isITOpen;
|
||||
|
||||
_controller = AnimationController(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
vsync: this,
|
||||
);
|
||||
_animation = CurvedAnimation(parent: _controller, curve: Curves.easeInOut);
|
||||
|
||||
// Start in the correct state
|
||||
widget.choreographer.isITOpen
|
||||
? _controller.forward()
|
||||
: _controller.reverse();
|
||||
_open.value ? _controller.forward() : _controller.reverse();
|
||||
_open.addListener(() {
|
||||
final nextText = _sourceText.value ?? widget.choreographer.currentText;
|
||||
if (_sourceTextController.text != nextText) {
|
||||
_sourceTextController.text = nextText;
|
||||
}
|
||||
_open.value ? _controller.forward() : _controller.reverse();
|
||||
});
|
||||
}
|
||||
|
||||
bool get showITInstructionsTooltip {
|
||||
final toggledOff = InstructionsEnum.clickBestOption.isToggledOff;
|
||||
if (!toggledOff) {
|
||||
setState(() => showedClickInstruction = true);
|
||||
}
|
||||
return !toggledOff;
|
||||
}
|
||||
|
||||
bool get showTranslationsChoicesTooltip {
|
||||
return !showedClickInstruction &&
|
||||
!showITInstructionsTooltip &&
|
||||
!widget.choreographer.isFetching &&
|
||||
!widget.choreographer.isEditingSourceText &&
|
||||
!widget.choreographer.isITDone &&
|
||||
widget.choreographer.itStepContinuances?.isNotEmpty == true;
|
||||
}
|
||||
|
||||
final double iconDimension = 36;
|
||||
final double iconSize = 20;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizeTransition(
|
||||
sizeFactor: _animation,
|
||||
axis: Axis.vertical,
|
||||
axisAlignment: -1.0,
|
||||
child: CompositedTransformTarget(
|
||||
link: widget.choreographer.itBarLinkAndKey.link,
|
||||
child: Column(
|
||||
spacing: 8.0,
|
||||
children: [
|
||||
if (showITInstructionsTooltip)
|
||||
const InstructionsInlineTooltip(
|
||||
instructionsEnum: InstructionsEnum.clickBestOption,
|
||||
animate: false,
|
||||
),
|
||||
if (showTranslationsChoicesTooltip)
|
||||
const InstructionsInlineTooltip(
|
||||
instructionsEnum: InstructionsEnum.translationChoices,
|
||||
animate: false,
|
||||
),
|
||||
Container(
|
||||
key: widget.choreographer.itBarLinkAndKey.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(3),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
if (widget.choreographer.isEditingSourceText)
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 20,
|
||||
right: 10,
|
||||
top: 10,
|
||||
),
|
||||
child: TextField(
|
||||
controller: TextEditingController(
|
||||
text: widget.choreographer.sourceText,
|
||||
),
|
||||
autofocus: true,
|
||||
enableSuggestions: false,
|
||||
maxLines: null,
|
||||
textInputAction: TextInputAction.send,
|
||||
onSubmitted:
|
||||
widget.choreographer.submitSourceTextEdits,
|
||||
obscureText: false,
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (!widget.choreographer.isEditingSourceText &&
|
||||
widget.choreographer.sourceText != null)
|
||||
SizedBox(
|
||||
width: iconDimension,
|
||||
height: iconDimension,
|
||||
child: IconButton(
|
||||
iconSize: iconSize,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
onPressed: () => widget.choreographer
|
||||
.setEditingSourceText(true),
|
||||
icon: const Icon(Icons.edit_outlined),
|
||||
// iconSize: 20,
|
||||
),
|
||||
),
|
||||
if (!widget.choreographer.isEditingSourceText)
|
||||
SizedBox(
|
||||
width: iconDimension,
|
||||
height: iconDimension,
|
||||
child: IconButton(
|
||||
iconSize: iconSize,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
icon: const Icon(Icons.settings_outlined),
|
||||
onPressed: () => showDialog(
|
||||
context: context,
|
||||
builder: (c) => const SettingsLearning(),
|
||||
barrierDismissible: false,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: iconDimension,
|
||||
height: iconDimension,
|
||||
child: IconButton(
|
||||
iconSize: iconSize,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
icon: const Icon(Icons.close_outlined),
|
||||
onPressed: () {
|
||||
widget.choreographer.isEditingSourceText
|
||||
? widget.choreographer
|
||||
.setEditingSourceText(false)
|
||||
: widget.choreographer.closeIT();
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (!widget.choreographer.isEditingSourceText)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: !widget.choreographer.isITOpen
|
||||
? const SizedBox()
|
||||
: widget.choreographer.sourceText != null
|
||||
? Text(
|
||||
widget.choreographer.sourceText!,
|
||||
textAlign: TextAlign.center,
|
||||
)
|
||||
: const LinearProgressIndicator(),
|
||||
),
|
||||
const SizedBox(height: 8.0),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
||||
constraints: const BoxConstraints(minHeight: 80),
|
||||
child: AnimatedSize(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
child: Center(
|
||||
child: widget.choreographer.errorService.isError
|
||||
? ITError(choreographer: widget.choreographer)
|
||||
: widget.choreographer.isITDone
|
||||
? const SizedBox()
|
||||
: ITChoices(
|
||||
choreographer: widget.choreographer,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
_sourceTextController.dispose();
|
||||
_successTimer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class ITChoices extends StatelessWidget {
|
||||
final Choreographer choreographer;
|
||||
const ITChoices({
|
||||
super.key,
|
||||
required this.choreographer,
|
||||
});
|
||||
ValueNotifier<String?> get _sourceText => widget.choreographer.sourceText;
|
||||
ValueNotifier<bool> get _open => widget.choreographer.itController.open;
|
||||
|
||||
void showCard(
|
||||
BuildContext context,
|
||||
void _showFeedbackCard(
|
||||
int index, [
|
||||
Color? borderColor,
|
||||
String? choiceFeedback,
|
||||
]) {
|
||||
if (choreographer.itStepContinuances == null) {
|
||||
final currentStep = widget.choreographer.itController.currentITStep.value;
|
||||
if (currentStep == null) {
|
||||
ErrorHandler.logError(
|
||||
m: "currentITStep is null in showCard",
|
||||
s: StackTrace.current,
|
||||
|
|
@ -261,45 +84,40 @@ class ITChoices extends StatelessWidget {
|
|||
return;
|
||||
}
|
||||
|
||||
final text = choreographer.itStepContinuances![index].text;
|
||||
choreographer.chatController.inputFocus.unfocus();
|
||||
final text = currentStep.continuances[index].text;
|
||||
MatrixState.pAnyState.closeOverlay("it_feedback_card");
|
||||
OverlayUtil.showPositionedCard(
|
||||
context: context,
|
||||
cardToShow: choiceFeedback == null
|
||||
? WordDataCard(
|
||||
word: text,
|
||||
wordLang: choreographer.l2LangCode!,
|
||||
fullText: choreographer.sourceText ?? choreographer.currentText,
|
||||
fullTextLang: choreographer.sourceText != null
|
||||
? choreographer.l1LangCode!
|
||||
: choreographer.l2LangCode!,
|
||||
choiceFeedback: choiceFeedback,
|
||||
wordLang: widget.choreographer.l2LangCode!,
|
||||
fullText: _sourceText.value ?? widget.choreographer.currentText,
|
||||
fullTextLang: widget.choreographer.l1LangCode!,
|
||||
)
|
||||
: ITFeedbackCard(
|
||||
req: FullTextTranslationRequestModel(
|
||||
FullTextTranslationRequestModel(
|
||||
text: text,
|
||||
tgtLang: choreographer.l2LangCode!,
|
||||
userL1: choreographer.l1LangCode!,
|
||||
userL2: choreographer.l2LangCode!,
|
||||
tgtLang: widget.choreographer.l1LangCode!,
|
||||
userL1: widget.choreographer.l1LangCode!,
|
||||
userL2: widget.choreographer.l2LangCode!,
|
||||
),
|
||||
choiceFeedback: choiceFeedback,
|
||||
),
|
||||
maxHeight: 300,
|
||||
maxWidth: 300,
|
||||
borderColor: borderColor,
|
||||
transformTargetId: choreographer.itBarTransformTargetKey,
|
||||
transformTargetId: widget.choreographer.itBarTransformTargetKey,
|
||||
isScrollable: choiceFeedback == null,
|
||||
overlayKey: "it_feedback_card",
|
||||
ignorePointer: true,
|
||||
);
|
||||
}
|
||||
|
||||
void selectContinuance(int index, BuildContext context) {
|
||||
void _selectContinuance(int index) {
|
||||
MatrixState.pAnyState.closeOverlay("it_feedback_card");
|
||||
Continuance continuance;
|
||||
try {
|
||||
continuance = choreographer.onSelectContinuance(index);
|
||||
continuance = widget.choreographer.onSelectContinuance(index);
|
||||
} catch (e, s) {
|
||||
ErrorHandler.logError(
|
||||
e: e,
|
||||
|
|
@ -309,33 +127,15 @@ class ITChoices extends StatelessWidget {
|
|||
"index": index,
|
||||
},
|
||||
);
|
||||
choreographer.closeIT();
|
||||
widget.choreographer.closeIT();
|
||||
return;
|
||||
}
|
||||
|
||||
if (continuance.level == 1) {
|
||||
Future.delayed(
|
||||
const Duration(milliseconds: 500),
|
||||
() {
|
||||
try {
|
||||
choreographer.onAcceptContinuance(index);
|
||||
} catch (e, s) {
|
||||
ErrorHandler.logError(
|
||||
e: e,
|
||||
s: s,
|
||||
level: SentryLevel.warning,
|
||||
data: {
|
||||
"index": index,
|
||||
},
|
||||
);
|
||||
choreographer.closeIT();
|
||||
return;
|
||||
}
|
||||
},
|
||||
);
|
||||
// CTODO doesn't always go to next continuance
|
||||
_onCorrectSelection(index);
|
||||
} else {
|
||||
showCard(
|
||||
context,
|
||||
_showFeedbackCard(
|
||||
index,
|
||||
continuance.level == 2 ? ChoreoConstants.yellow : ChoreoConstants.red,
|
||||
continuance.feedbackText(context),
|
||||
|
|
@ -343,81 +143,254 @@ class ITChoices extends StatelessWidget {
|
|||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
try {
|
||||
if (choreographer.isEditingSourceText) {
|
||||
return const SizedBox();
|
||||
void _onCorrectSelection(int index) {
|
||||
_successTimer?.cancel();
|
||||
_successTimer = Timer(const Duration(milliseconds: 500), () {
|
||||
if (!mounted) return;
|
||||
try {
|
||||
widget.choreographer.onAcceptContinuance(index);
|
||||
} catch (e, s) {
|
||||
ErrorHandler.logError(
|
||||
e: e,
|
||||
s: s,
|
||||
level: SentryLevel.warning,
|
||||
data: {
|
||||
"index": index,
|
||||
},
|
||||
);
|
||||
widget.choreographer.closeIT();
|
||||
}
|
||||
if (choreographer.itStepContinuances == null) {
|
||||
return choreographer.isITOpen
|
||||
? CircularProgressIndicator(
|
||||
strokeWidth: 2.0,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
)
|
||||
: const SizedBox();
|
||||
}
|
||||
return ChoicesArray(
|
||||
id: Object.hashAll(choreographer.itStepContinuances!).toString(),
|
||||
isLoading: choreographer.isFetching ||
|
||||
choreographer.itStepContinuances == null,
|
||||
choices: choreographer.itStepContinuances!.map((e) {
|
||||
debugPrint("WAS CLICKED: ${e.wasClicked}");
|
||||
try {
|
||||
return Choice(
|
||||
text: e.text.trim(),
|
||||
color: e.color,
|
||||
isGold: e.description == "best",
|
||||
);
|
||||
} catch (e) {
|
||||
debugger(when: kDebugMode);
|
||||
return Choice(text: "error", color: Colors.red);
|
||||
}
|
||||
}).toList(),
|
||||
onPressed: (value, index) => selectContinuance(index, context),
|
||||
onLongPress: (value, index) => showCard(context, index),
|
||||
selectedChoiceIndex: null,
|
||||
langCode:
|
||||
choreographer.pangeaController.languageController.activeL2Code(),
|
||||
);
|
||||
} catch (e) {
|
||||
debugger(when: kDebugMode);
|
||||
return const SizedBox();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class ITError extends StatelessWidget {
|
||||
final Choreographer choreographer;
|
||||
const ITError({
|
||||
super.key,
|
||||
required this.choreographer,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: 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,
|
||||
return AnimatedBuilder(
|
||||
animation: _animation,
|
||||
builder: (context, child) => SizeTransition(
|
||||
sizeFactor: _animation,
|
||||
axisAlignment: -1.0,
|
||||
child: child,
|
||||
),
|
||||
child: CompositedTransformTarget(
|
||||
link: widget.choreographer.itBarLinkAndKey.link,
|
||||
child: Column(
|
||||
children: [
|
||||
if (!InstructionsEnum.clickBestOption.isToggledOff) ...[
|
||||
const InstructionsInlineTooltip(
|
||||
instructionsEnum: InstructionsEnum.clickBestOption,
|
||||
animate: false,
|
||||
),
|
||||
const SizedBox(height: 8.0),
|
||||
],
|
||||
Container(
|
||||
key: widget.choreographer.itBarLinkAndKey.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.closeIT,
|
||||
setEditing: widget.choreographer.itController.setEditing,
|
||||
editing: widget.choreographer.itController.editing,
|
||||
sourceTextController: _sourceTextController,
|
||||
sourceText: _sourceText,
|
||||
onSubmitEdits: widget.choreographer.submitSourceTextEdits,
|
||||
),
|
||||
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.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,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: choreographer.closeIT,
|
||||
icon: const Icon(
|
||||
Icons.close,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ITBarHeader extends StatelessWidget {
|
||||
final VoidCallback onClose;
|
||||
final Function(String) onSubmitEdits;
|
||||
final Function(bool) setEditing;
|
||||
|
||||
final ValueNotifier<bool> editing;
|
||||
final TextEditingController sourceTextController;
|
||||
final ValueNotifier<String?> sourceText;
|
||||
|
||||
const _ITBarHeader({
|
||||
required this.onClose,
|
||||
required this.setEditing,
|
||||
required this.editing,
|
||||
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(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
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<Continuance> continuances;
|
||||
final Function(int) onPressed;
|
||||
final Function(int) 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(index),
|
||||
selectedChoiceIndex: null,
|
||||
langCode: MatrixState.pangeaController.languageController.activeL2Code(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue