removed excessive calls to setState in chat.dart and replaced them with smaller, stateful widgets
parent
efa325fa43
commit
280915fc96
@ -1,46 +0,0 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/choreographer/controllers/choreographer.dart';
|
||||
import 'package:fluffychat/pangea/constants/language_constants.dart';
|
||||
import 'package:fluffychat/pangea/models/language_model.dart';
|
||||
import 'package:fluffychat/pangea/utils/firebase_analytics.dart';
|
||||
|
||||
class MessageOptions {
|
||||
Choreographer choreographer;
|
||||
LanguageModel? _selectedDisplayLang;
|
||||
|
||||
MessageOptions(this.choreographer);
|
||||
|
||||
LanguageModel? get selectedDisplayLang {
|
||||
if (_selectedDisplayLang != null &&
|
||||
_selectedDisplayLang!.langCode != LanguageKeys.unknownLanguage) {
|
||||
return _selectedDisplayLang;
|
||||
}
|
||||
_selectedDisplayLang = choreographer.l2Lang;
|
||||
return _selectedDisplayLang;
|
||||
}
|
||||
|
||||
bool get isTranslationOn =>
|
||||
_selectedDisplayLang?.langCode != choreographer.l2LangCode;
|
||||
|
||||
// void setSelectedDisplayLang(LanguageModel? newLang) {
|
||||
// _selectedDisplayLang = newLang;
|
||||
// choreographer.setState();
|
||||
// }
|
||||
|
||||
void toggleSelectedDisplayLang() {
|
||||
if (_selectedDisplayLang?.langCode == choreographer.l2LangCode) {
|
||||
_selectedDisplayLang = choreographer.l1Lang;
|
||||
} else {
|
||||
_selectedDisplayLang = choreographer.l2Lang;
|
||||
}
|
||||
debugPrint('toggleSelectedDisplayLang: ${_selectedDisplayLang?.langCode}');
|
||||
choreographer.setState();
|
||||
GoogleAnalytics.messageTranslate();
|
||||
}
|
||||
|
||||
void resetSelectedDisplayLang() {
|
||||
_selectedDisplayLang = choreographer.l2Lang;
|
||||
choreographer.setState();
|
||||
}
|
||||
}
|
||||
@ -1,56 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
||||
import '../../../config/app_config.dart';
|
||||
import '../../../pages/chat/chat.dart';
|
||||
|
||||
class LanguageDisplayToggle extends StatelessWidget {
|
||||
const LanguageDisplayToggle({
|
||||
super.key,
|
||||
required this.controller,
|
||||
});
|
||||
|
||||
final ChatController controller;
|
||||
|
||||
get onPressed =>
|
||||
controller.choreographer.messageOptions.toggleSelectedDisplayLang;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// if (!controller.choreographer.translationEnabled) {
|
||||
// return const SizedBox();
|
||||
// }
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: controller.choreographer.messageOptions.isTranslationOn
|
||||
? AppConfig.primaryColor
|
||||
: null,
|
||||
),
|
||||
child: IconButton(
|
||||
tooltip: L10n.of(context)!.toggleLanguages,
|
||||
onPressed: onPressed,
|
||||
icon: const Icon(Icons.translate_outlined),
|
||||
selectedIcon: const Icon(Icons.translate),
|
||||
isSelected: controller.choreographer.messageOptions.isTranslationOn,
|
||||
),
|
||||
);
|
||||
// return Tooltip(
|
||||
// message: L10n.of(context)!.toggleLanguages,
|
||||
// waitDuration: const Duration(milliseconds: 1000),
|
||||
// child: FloatingActionButton(
|
||||
// onPressed: onPressed,
|
||||
// backgroundColor: Colors.white,
|
||||
// mini: false,
|
||||
// shape: RoundedRectangleBorder(
|
||||
// borderRadius: BorderRadius.circular(200), // <-- Radius
|
||||
// ),
|
||||
// child: LanguageFlag(
|
||||
// flagUrl: controller
|
||||
// .choreographer.messageOptions.displayLang?.languageFlag,
|
||||
// size: 50,
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
}
|
||||
}
|
||||
@ -1,57 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
|
||||
class MeasurableWidget extends StatefulWidget {
|
||||
final Widget child;
|
||||
final Function(Size? size, Offset? position) onChange;
|
||||
|
||||
const MeasurableWidget({
|
||||
super.key,
|
||||
required this.onChange,
|
||||
required this.child,
|
||||
});
|
||||
|
||||
@override
|
||||
_WidgetSizeState createState() => _WidgetSizeState();
|
||||
}
|
||||
|
||||
class _WidgetSizeState extends State<MeasurableWidget> {
|
||||
var widgetKey = GlobalKey();
|
||||
Offset? oldPosition;
|
||||
@override
|
||||
void initState() {
|
||||
// TODO: implement initState
|
||||
super.initState();
|
||||
}
|
||||
|
||||
void postFrameCallback(_) {
|
||||
final context = widgetKey.currentContext;
|
||||
if (context == null) return;
|
||||
|
||||
final RenderBox? box =
|
||||
widgetKey.currentContext?.findRenderObject() as RenderBox?;
|
||||
|
||||
if (box != null && box.hasSize) {
|
||||
final Offset position = box.localToGlobal(Offset.zero);
|
||||
|
||||
if (oldPosition != null) {
|
||||
if (oldPosition!.dx == position.dx && oldPosition!.dy == position.dy) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
oldPosition = position;
|
||||
|
||||
final newSize = context.size;
|
||||
widget.onChange(newSize, position);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
SchedulerBinding.instance.addPostFrameCallback(postFrameCallback);
|
||||
return Container(
|
||||
key: widgetKey,
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,94 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:fluffychat/pages/chat/chat.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/widgets/has_error_button.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/widgets/language_permissions_warning_buttons.dart';
|
||||
import 'package:fluffychat/pangea/models/space_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ChatFloatingActionButton extends StatefulWidget {
|
||||
final ChatController controller;
|
||||
const ChatFloatingActionButton({
|
||||
super.key,
|
||||
required this.controller,
|
||||
});
|
||||
|
||||
@override
|
||||
ChatFloatingActionButtonState createState() =>
|
||||
ChatFloatingActionButtonState();
|
||||
}
|
||||
|
||||
class ChatFloatingActionButtonState extends State<ChatFloatingActionButton> {
|
||||
bool showPermissionsError = false;
|
||||
StreamSubscription? _choreoSub;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final permissionsController =
|
||||
widget.controller.pangeaController.permissionsController;
|
||||
final itEnabled = permissionsController.isToolEnabled(
|
||||
ToolSetting.interactiveTranslator,
|
||||
widget.controller.room,
|
||||
);
|
||||
final igcEnabled = permissionsController.isToolEnabled(
|
||||
ToolSetting.interactiveGrammar,
|
||||
widget.controller.room,
|
||||
);
|
||||
showPermissionsError = !itEnabled || !igcEnabled;
|
||||
debugPrint("showPermissionsError: $showPermissionsError");
|
||||
|
||||
if (showPermissionsError) {
|
||||
Future.delayed(
|
||||
const Duration(seconds: 5),
|
||||
() {
|
||||
if (mounted) setState(() => showPermissionsError = false);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Rebuild the widget each time there's an update from choreo (i.e., an error).
|
||||
_choreoSub =
|
||||
widget.controller.choreographer.stateListener.stream.listen((_) {
|
||||
setState(() {});
|
||||
});
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_choreoSub?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (widget.controller.selectedEvents.isNotEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
if (widget.controller.showScrollDownButton) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 56.0),
|
||||
child: FloatingActionButton(
|
||||
onPressed: widget.controller.scrollDown,
|
||||
heroTag: null,
|
||||
mini: true,
|
||||
child: const Icon(Icons.arrow_downward_outlined),
|
||||
),
|
||||
);
|
||||
}
|
||||
if (widget.controller.choreographer.errorService.error != null) {
|
||||
return ChoreographerHasErrorButton(
|
||||
widget.controller.pangeaController,
|
||||
widget.controller.choreographer.errorService.error!,
|
||||
);
|
||||
}
|
||||
|
||||
return showPermissionsError
|
||||
? LanguagePermissionsButtons(
|
||||
choreographer: widget.controller.choreographer,
|
||||
roomID: widget.controller.roomId,
|
||||
)
|
||||
: const SizedBox.shrink();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,82 @@
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:fluffychat/pages/chat/input_bar.dart';
|
||||
import 'package:fluffychat/pangea/widgets/igc/pangea_text_controller.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
class InputBarWrapper extends StatefulWidget {
|
||||
final Room room;
|
||||
final int? minLines;
|
||||
final int? maxLines;
|
||||
final TextInputType? keyboardType;
|
||||
final TextInputAction? textInputAction;
|
||||
final ValueChanged<String>? onSubmitted;
|
||||
final ValueChanged<Uint8List?>? onSubmitImage;
|
||||
final FocusNode? focusNode;
|
||||
final PangeaTextController? controller;
|
||||
final InputDecoration? decoration;
|
||||
final ValueChanged<String>? onChanged;
|
||||
final bool? autofocus;
|
||||
final bool readOnly;
|
||||
|
||||
const InputBarWrapper({
|
||||
required this.room,
|
||||
this.minLines,
|
||||
this.maxLines,
|
||||
this.keyboardType,
|
||||
this.onSubmitted,
|
||||
this.onSubmitImage,
|
||||
this.focusNode,
|
||||
this.controller,
|
||||
this.decoration,
|
||||
this.onChanged,
|
||||
this.autofocus,
|
||||
this.textInputAction,
|
||||
this.readOnly = false,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<InputBarWrapper> createState() => InputBarWrapperState();
|
||||
}
|
||||
|
||||
class InputBarWrapperState extends State<InputBarWrapper> {
|
||||
StreamSubscription? _choreoSub;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
// Rebuild the widget each time there's an update from choreo
|
||||
_choreoSub =
|
||||
widget.controller?.choreographer.stateListener.stream.listen((_) {
|
||||
setState(() {});
|
||||
});
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_choreoSub?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InputBar(
|
||||
room: widget.room,
|
||||
minLines: widget.minLines,
|
||||
maxLines: widget.maxLines,
|
||||
keyboardType: widget.keyboardType,
|
||||
onSubmitted: widget.onSubmitted,
|
||||
onSubmitImage: widget.onSubmitImage,
|
||||
focusNode: widget.focusNode,
|
||||
controller: widget.controller,
|
||||
decoration: widget.decoration,
|
||||
onChanged: widget.onChanged,
|
||||
autofocus: widget.autofocus,
|
||||
textInputAction: widget.textInputAction,
|
||||
readOnly: widget.readOnly,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,29 +0,0 @@
|
||||
import 'package:fluffychat/pages/chat/chat.dart';
|
||||
import 'package:fluffychat/pangea/widgets/chat/text_to_speech_button.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class PangeaMessageActions extends StatelessWidget {
|
||||
final ChatController chatController;
|
||||
|
||||
const PangeaMessageActions({super.key, required this.chatController});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return chatController.selectedEvents.length == 1
|
||||
? Row(
|
||||
children: <Widget>[
|
||||
// LanguageToggleSwitch(controller: chatController),
|
||||
TextToSpeechButton(
|
||||
controller: chatController,
|
||||
selectedEvent: chatController.selectedEvents.first,
|
||||
),
|
||||
// IconButton(
|
||||
// icon: Icon(Icons.mic),
|
||||
// onPressed: chatController.onMicTap,
|
||||
// ),
|
||||
// Add more IconButton widgets here
|
||||
],
|
||||
)
|
||||
: const SizedBox();
|
||||
}
|
||||
}
|
||||
@ -1,138 +0,0 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/pages/chat/chat.dart';
|
||||
import 'package:fluffychat/pages/chat/events/audio_player.dart';
|
||||
import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart';
|
||||
import 'package:fluffychat/pangea/utils/error_handler.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:just_audio/just_audio.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
class TextToSpeechButton extends StatefulWidget {
|
||||
final ChatController controller;
|
||||
final Event selectedEvent;
|
||||
|
||||
const TextToSpeechButton({
|
||||
super.key,
|
||||
required this.controller,
|
||||
required this.selectedEvent,
|
||||
});
|
||||
|
||||
@override
|
||||
_TextToSpeechButtonState createState() => _TextToSpeechButtonState();
|
||||
}
|
||||
|
||||
class _TextToSpeechButtonState extends State<TextToSpeechButton> {
|
||||
final AudioPlayer _audioPlayer = AudioPlayer();
|
||||
late PangeaMessageEvent _pangeaMessageEvent;
|
||||
bool _isLoading = false;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_audioPlayer.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_pangeaMessageEvent = PangeaMessageEvent(
|
||||
event: widget.selectedEvent,
|
||||
timeline: widget.controller.timeline!,
|
||||
ownMessage:
|
||||
widget.selectedEvent.senderId == Matrix.of(context).client.userID,
|
||||
);
|
||||
}
|
||||
|
||||
Event? get localAudioEvent =>
|
||||
langCode != null && text != null && text!.isNotEmpty
|
||||
? _pangeaMessageEvent.getTextToSpeechLocal(langCode!, text!)
|
||||
: null;
|
||||
|
||||
String? get langCode =>
|
||||
widget.controller.choreographer.messageOptions.selectedDisplayLang
|
||||
?.langCode ??
|
||||
widget.controller.choreographer.l2LangCode;
|
||||
|
||||
String? get text => langCode != null
|
||||
? _pangeaMessageEvent.representationByLanguage(langCode!)?.text
|
||||
: null;
|
||||
|
||||
Future<void> _getAudio() async {
|
||||
try {
|
||||
if (!mounted) return;
|
||||
if (text == null || text!.isEmpty) return;
|
||||
if (langCode == null || langCode!.isEmpty) return;
|
||||
|
||||
setState(() => _isLoading = true);
|
||||
await _pangeaMessageEvent.getTextToSpeechGlobal(langCode!);
|
||||
setState(() => _isLoading = false);
|
||||
} catch (e) {
|
||||
setState(() => _isLoading = false);
|
||||
debugger(when: kDebugMode);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(L10n.of(context)!.errorGettingAudio),
|
||||
),
|
||||
);
|
||||
ErrorHandler.logError(
|
||||
e: Exception(),
|
||||
s: StackTrace.current,
|
||||
m: 'text is null or empty in text_to_speech_button.dart',
|
||||
data: {'selectedEvent': widget.selectedEvent, 'langCode': langCode},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_isLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
final playButton = InkWell(
|
||||
borderRadius: BorderRadius.circular(64),
|
||||
onTap: text == null || text!.isEmpty ? null : _getAudio,
|
||||
child: Material(
|
||||
color: AppConfig.primaryColor.withAlpha(64),
|
||||
borderRadius: BorderRadius.circular(64),
|
||||
child: const Icon(
|
||||
// Change the icon based on some condition. If you have an audio player state, use it here.
|
||||
Icons.play_arrow_outlined,
|
||||
color: AppConfig.primaryColor,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return localAudioEvent == null
|
||||
? Opacity(
|
||||
opacity: text == null || text!.isEmpty ? 0.5 : 1,
|
||||
child: SizedBox(
|
||||
width: 44, // Match the size of the button in AudioPlayerState
|
||||
height: 36,
|
||||
child: Padding(
|
||||
//only left side of the button is padded to match the padding of the AudioPlayerState
|
||||
padding: const EdgeInsets.only(left: 8),
|
||||
child: playButton,
|
||||
),
|
||||
),
|
||||
)
|
||||
: Container(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 250,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
AudioPlayerWidget(
|
||||
localAudioEvent!,
|
||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue