chore: fix scrolling bug and issues with lemma edit widget (#2133)
This commit is contained in:
parent
6df46c73a1
commit
c6e5c2ad29
9 changed files with 86 additions and 90 deletions
|
|
@ -12,7 +12,6 @@ import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
|
|||
import 'package:fluffychat/pangea/events/models/pangea_token_text_model.dart';
|
||||
import 'package:fluffychat/pangea/lemmas/lemma.dart';
|
||||
import 'package:fluffychat/pangea/morphs/get_grammar_copy.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/controllers/tts_controller.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/practice_activity/word_audio_button.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/word_zoom/lemma_meaning_widget.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
|
@ -98,7 +97,6 @@ class VocabDetailsView extends StatelessWidget {
|
|||
const SizedBox(width: 10.0),
|
||||
WordAudioButton(
|
||||
text: _construct.lemma,
|
||||
ttsController: TtsController(),
|
||||
size: 24,
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -77,9 +77,6 @@ class Choreographer {
|
|||
trialStream = pangeaController
|
||||
.subscriptionController.trialActivationStream.stream
|
||||
.listen((_) => _onChangeListener);
|
||||
|
||||
tts.setupTTS();
|
||||
|
||||
clear();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import 'package:fluffychat/pangea/user/controllers/user_controller.dart';
|
|||
import 'package:fluffychat/utils/platform_infos.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
class TtsController extends ChangeNotifier {
|
||||
class TtsController {
|
||||
final ChatController? chatController;
|
||||
|
||||
String? get l2LangCode =>
|
||||
|
|
@ -35,6 +35,8 @@ class TtsController extends ChangeNotifier {
|
|||
final flutter_tts.FlutterTts _tts = flutter_tts.FlutterTts();
|
||||
final TextToSpeech _alternativeTTS = TextToSpeech();
|
||||
StreamSubscription? _languageSubscription;
|
||||
final StreamController<bool> loadingChoreoStream =
|
||||
StreamController<bool>.broadcast();
|
||||
|
||||
UserController get userController =>
|
||||
MatrixState.pangeaController.userController;
|
||||
|
|
@ -49,20 +51,10 @@ class TtsController extends ChangeNotifier {
|
|||
return PlatformInfos.isWindows;
|
||||
}
|
||||
|
||||
bool? _hasLoadedTextToSpeech;
|
||||
bool? get hasLoadedTextToSpeech => _hasLoadedTextToSpeech;
|
||||
set hasLoadedTextToSpeech(bool? value) {
|
||||
if (_hasLoadedTextToSpeech != value) {
|
||||
_hasLoadedTextToSpeech = value;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
await _tts.stop();
|
||||
await _languageSubscription?.cancel();
|
||||
super.dispose();
|
||||
await loadingChoreoStream.close();
|
||||
}
|
||||
|
||||
void _onError(dynamic message) {
|
||||
|
|
@ -269,9 +261,10 @@ class TtsController extends ChangeNotifier {
|
|||
}
|
||||
|
||||
Future<void> _speakFromChoreo(String text) async {
|
||||
TextToSpeechResponse? ttsRes;
|
||||
try {
|
||||
hasLoadedTextToSpeech = false;
|
||||
final ttsRes = await chatController?.pangeaController.textToSpeech.get(
|
||||
loadingChoreoStream.add(true);
|
||||
ttsRes = await chatController?.pangeaController.textToSpeech.get(
|
||||
TextToSpeechRequest(
|
||||
text: text,
|
||||
langCode: l2LangCode ?? LanguageKeys.unknownLanguage,
|
||||
|
|
@ -280,11 +273,23 @@ class TtsController extends ChangeNotifier {
|
|||
userL2: LanguageKeys.unknownLanguage,
|
||||
),
|
||||
);
|
||||
hasLoadedTextToSpeech = true;
|
||||
if (ttsRes != null) {
|
||||
final audioContent = base64Decode(ttsRes.audioContent);
|
||||
await playAudio(audioContent, ttsRes.mimeType);
|
||||
}
|
||||
} catch (e, s) {
|
||||
ErrorHandler.logError(
|
||||
e: e,
|
||||
s: s,
|
||||
data: {
|
||||
'text': text,
|
||||
},
|
||||
);
|
||||
} finally {
|
||||
loadingChoreoStream.add(false);
|
||||
}
|
||||
|
||||
if (ttsRes == null) return;
|
||||
|
||||
try {
|
||||
final audioContent = base64Decode(ttsRes.audioContent);
|
||||
await playAudio(audioContent, ttsRes.mimeType);
|
||||
} catch (e, s) {
|
||||
ErrorHandler.logError(
|
||||
e: e,
|
||||
|
|
|
|||
|
|
@ -212,7 +212,6 @@ class MultipleChoiceActivityState extends State<MultipleChoiceActivity> {
|
|||
ActivityTypeEnum.wordFocusListening)
|
||||
WordAudioButton(
|
||||
text: practiceActivity.content.answers.first,
|
||||
ttsController: tts,
|
||||
),
|
||||
if (practiceActivity.activityType ==
|
||||
ActivityTypeEnum.hiddenWordListening)
|
||||
|
|
@ -242,25 +241,18 @@ class MultipleChoiceActivityState extends State<MultipleChoiceActivity> {
|
|||
],
|
||||
);
|
||||
|
||||
return practiceActivity.activityType ==
|
||||
ActivityTypeEnum.hiddenWordListening ||
|
||||
practiceActivity.activityType == ActivityTypeEnum.messageMeaning
|
||||
? ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
// see https://github.com/pangeachat/client/issues/1422
|
||||
maxWidth: AppConfig.toolbarMinWidth,
|
||||
maxHeight: AppConfig.toolbarMaxHeight,
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: content,
|
||||
),
|
||||
),
|
||||
)
|
||||
: Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: content,
|
||||
);
|
||||
return ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
// see https://github.com/pangeachat/client/issues/1422
|
||||
maxWidth: AppConfig.toolbarMinWidth,
|
||||
maxHeight: AppConfig.toolbarMaxHeight,
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: content,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,13 +8,11 @@ import 'package:fluffychat/widgets/matrix.dart';
|
|||
|
||||
class WordAudioButton extends StatefulWidget {
|
||||
final String text;
|
||||
final TtsController ttsController;
|
||||
final double size;
|
||||
|
||||
const WordAudioButton({
|
||||
super.key,
|
||||
required this.text,
|
||||
required this.ttsController,
|
||||
this.size = 24,
|
||||
});
|
||||
|
||||
|
|
@ -23,8 +21,15 @@ class WordAudioButton extends StatefulWidget {
|
|||
}
|
||||
|
||||
class WordAudioButtonState extends State<WordAudioButton> {
|
||||
final TtsController tts = TtsController();
|
||||
bool _isPlaying = false;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
tts.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CompositedTransformTarget(
|
||||
|
|
@ -40,7 +45,7 @@ class WordAudioButtonState extends State<WordAudioButton> {
|
|||
iconSize: widget.size,
|
||||
onPressed: () async {
|
||||
if (_isPlaying) {
|
||||
await widget.ttsController.stop();
|
||||
await tts.stop();
|
||||
if (mounted) {
|
||||
setState(() => _isPlaying = false);
|
||||
}
|
||||
|
|
@ -49,7 +54,7 @@ class WordAudioButtonState extends State<WordAudioButton> {
|
|||
setState(() => _isPlaying = true);
|
||||
}
|
||||
try {
|
||||
await widget.ttsController.tryToSpeak(
|
||||
await tts.tryToSpeak(
|
||||
widget.text,
|
||||
context,
|
||||
targetID: 'word-audio-button',
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
|
|
@ -6,13 +8,11 @@ import 'package:fluffychat/widgets/matrix.dart';
|
|||
|
||||
class WordTextWithAudioButton extends StatefulWidget {
|
||||
final String text;
|
||||
final TtsController ttsController;
|
||||
final double? textSize;
|
||||
|
||||
const WordTextWithAudioButton({
|
||||
super.key,
|
||||
required this.text,
|
||||
required this.ttsController,
|
||||
this.textSize,
|
||||
});
|
||||
|
||||
|
|
@ -21,28 +21,31 @@ class WordTextWithAudioButton extends StatefulWidget {
|
|||
}
|
||||
|
||||
class WordAudioButtonState extends State<WordTextWithAudioButton> {
|
||||
bool _isPlaying = false;
|
||||
// initialize as null because we don't know if we need to load
|
||||
// audio from choreo yet. This shall remain null if user device support
|
||||
// text to speech
|
||||
final bool? _isLoadingAudio = null;
|
||||
final TtsController tts = TtsController();
|
||||
|
||||
bool _isPlaying = false;
|
||||
bool _isLoading = false;
|
||||
StreamSubscription? _loadingChoreoSubscription;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
widget.ttsController.addListener(_onTtsControllerChange);
|
||||
_loadingChoreoSubscription = tts.loadingChoreoStream.stream.listen((val) {
|
||||
if (mounted) setState(() => _isLoading = val);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
widget.ttsController.removeListener(_onTtsControllerChange);
|
||||
_loadingChoreoSubscription?.cancel();
|
||||
tts.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _onTtsControllerChange() {
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
double get textSize =>
|
||||
widget.textSize ?? Theme.of(context).textTheme.titleLarge?.fontSize ?? 16;
|
||||
|
||||
|
|
@ -65,7 +68,7 @@ class WordAudioButtonState extends State<WordTextWithAudioButton> {
|
|||
return;
|
||||
}
|
||||
if (_isPlaying) {
|
||||
await widget.ttsController.stop();
|
||||
await tts.stop();
|
||||
if (mounted) {
|
||||
setState(() => _isPlaying = false);
|
||||
}
|
||||
|
|
@ -74,7 +77,7 @@ class WordAudioButtonState extends State<WordTextWithAudioButton> {
|
|||
setState(() => _isPlaying = true);
|
||||
}
|
||||
try {
|
||||
await widget.ttsController.tryToSpeak(
|
||||
await tts.tryToSpeak(
|
||||
widget.text,
|
||||
context,
|
||||
targetID: 'text-audio-button',
|
||||
|
|
@ -118,7 +121,7 @@ class WordAudioButtonState extends State<WordTextWithAudioButton> {
|
|||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
if (widget.ttsController.hasLoadedTextToSpeech == false)
|
||||
if (_isLoading)
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: 4,
|
||||
|
|
|
|||
|
|
@ -162,17 +162,14 @@ class LemmaWidgetState extends State<LemmaWidget> {
|
|||
);
|
||||
}
|
||||
|
||||
return Flexible(
|
||||
child: Tooltip(
|
||||
triggerMode: TooltipTriggerMode.tap,
|
||||
message: L10n.of(context).doubleClickToEdit,
|
||||
child: GestureDetector(
|
||||
onLongPress: () => _toggleEditMode(true),
|
||||
onDoubleTap: () => _toggleEditMode(true),
|
||||
child: WordTextWithAudioButton(
|
||||
text: widget.token.lemma.text,
|
||||
ttsController: widget.tts,
|
||||
),
|
||||
return Tooltip(
|
||||
triggerMode: TooltipTriggerMode.tap,
|
||||
message: L10n.of(context).doubleClickToEdit,
|
||||
child: GestureDetector(
|
||||
onLongPress: () => _toggleEditMode(true),
|
||||
onDoubleTap: () => _toggleEditMode(true),
|
||||
child: WordTextWithAudioButton(
|
||||
text: widget.token.lemma.text,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -211,7 +211,6 @@ class MorphFocusWidgetState extends State<MorphFocusWidget> {
|
|||
child: Padding(
|
||||
padding: const EdgeInsets.all(4.0),
|
||||
child: Column(
|
||||
spacing: 4.0,
|
||||
children: [
|
||||
Text(
|
||||
"${L10n.of(context).pangeaBotIsFallible} ${L10n.of(context).chooseCorrectLabel}",
|
||||
|
|
@ -219,13 +218,13 @@ class MorphFocusWidgetState extends State<MorphFocusWidget> {
|
|||
style: const TextStyle(fontStyle: FontStyle.italic),
|
||||
),
|
||||
Container(
|
||||
constraints: const BoxConstraints(maxHeight: 70),
|
||||
constraints: const BoxConstraints(maxHeight: 50),
|
||||
child: Scrollbar(
|
||||
controller: _scrollController,
|
||||
thumbVisibility: true,
|
||||
child: SingleChildScrollView(
|
||||
controller: _scrollController,
|
||||
scrollDirection: Axis.vertical,
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: FutureBuilder(
|
||||
|
|
@ -237,8 +236,7 @@ class MorphFocusWidgetState extends State<MorphFocusWidget> {
|
|||
.getDisplayTags(widget.morphFeature);
|
||||
|
||||
return snapshot.connectionState == ConnectionState.done
|
||||
? Wrap(
|
||||
alignment: WrapAlignment.center,
|
||||
? Row(
|
||||
children: allMorphTagsForEdit.map((tag) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.all(2),
|
||||
|
|
|
|||
|
|
@ -78,18 +78,20 @@ class WordZoomWidget extends StatelessWidget {
|
|||
),
|
||||
isSelected: _mode == MessageMode.wordEmoji,
|
||||
),
|
||||
LemmaWidget(
|
||||
token: _selectedToken,
|
||||
pangeaMessageEvent: messageEvent,
|
||||
// onEdit: () => _setHideCenterContent(true),
|
||||
onEdit: () {
|
||||
debugPrint("what are we doing edits with?");
|
||||
},
|
||||
onEditDone: () {
|
||||
debugPrint("what are we doing edits with?");
|
||||
onEditDone();
|
||||
},
|
||||
tts: tts,
|
||||
Expanded(
|
||||
child: LemmaWidget(
|
||||
token: _selectedToken,
|
||||
pangeaMessageEvent: messageEvent,
|
||||
// onEdit: () => _setHideCenterContent(true),
|
||||
onEdit: () {
|
||||
debugPrint("what are we doing edits with?");
|
||||
},
|
||||
onEditDone: () {
|
||||
debugPrint("what are we doing edits with?");
|
||||
onEditDone();
|
||||
},
|
||||
tts: tts,
|
||||
),
|
||||
),
|
||||
ConstructXpWidget(
|
||||
id: token.vocabConstructID,
|
||||
|
|
@ -129,7 +131,6 @@ class WordZoomWidget extends StatelessWidget {
|
|||
if (!_selectedToken.doesLemmaTextMatchTokenText)
|
||||
WordTextWithAudioButton(
|
||||
text: _selectedToken.text.content,
|
||||
ttsController: tts,
|
||||
textSize:
|
||||
Theme.of(context).textTheme.titleMedium?.fontSize,
|
||||
),
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue