removed excessive calls to setState in chat.dart and replaced them with smaller, stateful widgets

pull/1384/head
ggurdin 1 year ago
parent efa325fa43
commit 280915fc96

@ -20,7 +20,6 @@ import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_e
import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart';
import 'package:fluffychat/pangea/models/choreo_record.dart';
import 'package:fluffychat/pangea/models/representation_content_model.dart';
import 'package:fluffychat/pangea/models/space_model.dart';
import 'package:fluffychat/pangea/models/tokens_event_content_model.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:fluffychat/pangea/utils/firebase_analytics.dart';
@ -294,10 +293,6 @@ class ChatController extends State<ChatPageWithRoom>
}
}
// #Pangea
bool showPermissionsError = false;
// #Pangea
@override
void initState() {
scrollController.addListener(_updateScrollController);
@ -327,31 +322,12 @@ class ChatController extends State<ChatPageWithRoom>
context,
() => Future.delayed(
Duration.zero,
() => setState(
() {},
),
() => setState(() {}),
),
);
}
await Matrix.of(context).client.roomsLoading;
choreographer.setRoomId(roomId);
choreographer.messageOptions.resetSelectedDisplayLang();
choreographer.stateListener.stream.listen((event) {
debugPrint("chat.dart choreo event $event");
setState(() {});
});
showPermissionsError = !pangeaController.permissionsController
.isToolEnabled(ToolSetting.interactiveTranslator, room) ||
!pangeaController.permissionsController
.isToolEnabled(ToolSetting.interactiveGrammar, room);
});
Future.delayed(
const Duration(seconds: 5),
() {
if (mounted) setState(() => showPermissionsError = false);
},
);
// Pangea#
_tryLoadTimeline();
if (kIsWeb) {
@ -602,10 +578,9 @@ class ChatController extends State<ChatPageWithRoom>
});
// #Pangea
final List<String> edittingEvents = [];
void clearEdittingEvent(String eventId) {
edittingEvents.remove(eventId);
setState(() {});
Event? pangeaEditingEvent;
void clearEditingEvent() {
pangeaEditingEvent = null;
}
// Future<void> send() async {
@ -665,11 +640,9 @@ class ChatController extends State<ChatPageWithRoom>
.then(
(String? msgEventId) async {
// #Pangea
setState(() {
if (previousEdit != null) {
edittingEvents.add(previousEdit.eventId);
pangeaEditingEvent = previousEdit;
}
});
GoogleAnalytics.sendMessage(
room.id,
@ -1262,9 +1235,6 @@ class ChatController extends State<ChatPageWithRoom>
void clearSelectedEvents() => setState(() {
selectedEvents.clear();
showEmojiPicker = false;
//#Pangea
choreographer.messageOptions.resetSelectedDisplayLang();
//Pangea#
});
void clearSingleSelectedEvent() {
@ -1336,19 +1306,19 @@ class ChatController extends State<ChatPageWithRoom>
// Pangea#
if (!event.redacted) {
// #Pangea
// If previous selectedEvent has same eventId, delete previous selectedEvent
final matches =
selectedEvents.where((e) => e.eventId == event.eventId).toList();
if (matches.isNotEmpty) {
// if (selectedEvents.contains(event)) {
// Pangea#
setState(
// #Pangea
() => selectedEvents.remove(matches.first),
// setState(
// () => selectedEvents.remove(event),
// );
// }
// If delete first selected event with the selected eventID
final matches = selectedEvents.where((e) => e.eventId == event.eventId);
if (matches.isNotEmpty) {
setState(() => selectedEvents.remove(matches.first));
}
// Pangea#
);
} else {
else {
setState(
() => selectedEvents.add(event),
);
@ -1557,35 +1527,6 @@ class ChatController extends State<ChatPageWithRoom>
});
// #Pangea
double? availableSpace;
double? inputRowSize;
bool? lastState;
bool get isRowScrollable {
if (availableSpace == null || inputRowSize == null) {
if (lastState == null) {
lastState = false;
Future.delayed(Duration.zero, () {
setState(() {});
});
}
return false;
}
const double offSetValue = 10;
final bool currentState = inputRowSize! > (availableSpace! - offSetValue);
if (!lastState! && currentState) {
Future.delayed(Duration.zero, () {
setState(() {});
});
}
if (lastState! && !currentState) {
Future.delayed(Duration.zero, () {
setState(() {});
});
}
lastState = currentState;
return currentState;
}
final Map<String, PangeaMessageEvent> _pangeaMessageEvents = {};
final Map<String, ToolbarDisplayController> _toolbarDisplayControllers = {};

@ -170,8 +170,6 @@ class ChatEventList extends StatelessWidget {
controller.scrollToEventId(eventId),
longPressSelect: controller.selectedEvents.isNotEmpty,
// #Pangea
selectedDisplayLang:
controller.choreographer.messageOptions.selectedDisplayLang,
immersionMode: controller.choreographer.immersionMode,
definitions: controller.choreographer.definitionsEnabled,
controller: controller,

@ -3,6 +3,7 @@ import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pangea/choreographer/controllers/choreographer.dart';
import 'package:fluffychat/pangea/choreographer/widgets/send_button.dart';
import 'package:fluffychat/pangea/constants/language_constants.dart';
import 'package:fluffychat/pangea/widgets/chat/input_bar_wrapper.dart';
import 'package:fluffychat/utils/platform_infos.dart';
import 'package:fluffychat/widgets/avatar.dart';
import 'package:fluffychat/widgets/matrix.dart';
@ -12,7 +13,6 @@ import 'package:matrix/matrix.dart';
import '../../config/themes.dart';
import 'chat.dart';
import 'input_bar.dart';
class ChatInputRow extends StatelessWidget {
final ChatController controller;
@ -322,7 +322,10 @@ class ChatInputRow extends StatelessWidget {
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 0.0),
child: InputBar(
// #Pangea
// child: InputBar(
child: InputBarWrapper(
// Pangea#
room: controller.room,
minLines: 1,
maxLines: 8,

@ -7,11 +7,9 @@ import 'package:fluffychat/pages/chat/chat_event_list.dart';
import 'package:fluffychat/pages/chat/pinned_events.dart';
import 'package:fluffychat/pages/chat/reactions_picker.dart';
import 'package:fluffychat/pages/chat/reply_display.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/choreographer/widgets/start_igc_button.dart';
import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart';
import 'package:fluffychat/pangea/pages/class_analytics/measure_able.dart';
import 'package:fluffychat/pangea/widgets/chat/chat_floating_action_button.dart';
import 'package:fluffychat/utils/account_config.dart';
import 'package:fluffychat/widgets/chat_settings_popup_menu.dart';
import 'package:fluffychat/widgets/connection_status_header.dart';
@ -266,32 +264,20 @@ class ChatView extends StatelessWidget {
// #Pangea
// floatingActionButton: controller.showScrollDownButton &&
// controller.selectedEvents.isEmpty
floatingActionButton: controller.selectedEvents.isEmpty
? (controller.showScrollDownButton
// Pangea#
? Padding(
padding: const EdgeInsets.only(bottom: 56.0),
child: FloatingActionButton(
onPressed: controller.scrollDown,
heroTag: null,
mini: true,
child: const Icon(Icons.arrow_downward_outlined),
// ? Padding(
// padding: const EdgeInsets.only(bottom: 56.0),
// child: FloatingActionButton(
// onPressed: controller.scrollDown,
// heroTag: null,
// mini: true,
// child: const Icon(Icons.arrow_downward_outlined),
// ),
// )
// : null,
floatingActionButton: ChatFloatingActionButton(
controller: controller,
),
)
// #Pangea
: controller.choreographer.errorService.error != null
? ChoreographerHasErrorButton(
controller.pangeaController,
controller.choreographer.errorService.error!,
)
: controller.showPermissionsError
? LanguagePermissionsButtons(
choreographer: controller.choreographer,
roomID: controller.roomId,
)
: null)
// #Pangea
: null,
// Pangea#
body:
// #Pangea
// DropTarget(
@ -338,18 +324,7 @@ class ChatView extends StatelessWidget {
),
if (controller.room.canSendDefaultMessages &&
controller.room.membership == Membership.join)
// #Pangea
// Container(
ConditionalFlexible(
isScroll: controller.isRowScrollable,
child: ConditionalScroll(
isScroll: controller.isRowScrollable,
child: MeasurableWidget(
onChange: (size, position) {
controller.inputRowSize = size!.height;
},
child: Container(
// Pangea#
Container(
margin: EdgeInsets.only(
bottom: bottomSheetPadding,
left: bottomSheetPadding,
@ -368,8 +343,7 @@ class ChatView extends StatelessWidget {
borderRadius: const BorderRadius.all(
Radius.circular(24),
),
child: controller.room.isAbandonedDMRoom ==
true
child: controller.room.isAbandonedDMRoom == true
? Row(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
@ -378,20 +352,17 @@ class ChatView extends StatelessWidget {
if (controller.room.isRoomAdmin)
TextButton.icon(
style: TextButton.styleFrom(
padding:
const EdgeInsets.all(
padding: const EdgeInsets.all(
16,
),
foregroundColor:
Theme.of(context)
foregroundColor: Theme.of(context)
.colorScheme
.error,
),
icon: const Icon(
Icons.archive_outlined,
),
onPressed:
controller.archiveChat,
onPressed: controller.archiveChat,
label: Text(
L10n.of(context)!.archive,
),
@ -402,8 +373,7 @@ class ChatView extends StatelessWidget {
padding: const EdgeInsets.all(
16,
),
foregroundColor:
Theme.of(context)
foregroundColor: Theme.of(context)
.colorScheme
.error,
),
@ -427,8 +397,7 @@ class ChatView extends StatelessWidget {
icon: const Icon(
Icons.forum_outlined,
),
onPressed:
controller.recreateChat,
onPressed: controller.recreateChat,
label: Text(
L10n.of(context)!.reopenChat,
),
@ -451,9 +420,6 @@ class ChatView extends StatelessWidget {
),
),
),
),
),
),
],
),
),
@ -484,35 +450,3 @@ class ChatView extends StatelessWidget {
);
}
}
// #Pangea
Widget ConditionalFlexible({required bool isScroll, required Widget child}) {
if (isScroll) {
return Flexible(
flex: 9999999,
child: child,
);
}
return child;
}
class ConditionalScroll extends StatelessWidget {
final bool isScroll;
final Widget child;
const ConditionalScroll({
super.key,
required this.isScroll,
required this.child,
});
@override
Widget build(BuildContext context) {
if (isScroll) {
return SingleChildScrollView(
child: child,
);
}
return child;
}
}
// Pangea#

@ -2,7 +2,6 @@ import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/pages/chat/chat.dart';
import 'package:fluffychat/pangea/enum/use_type.dart';
import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart';
import 'package:fluffychat/pangea/models/language_model.dart';
import 'package:fluffychat/pangea/widgets/chat/message_buttons.dart';
import 'package:fluffychat/pangea/widgets/chat/message_toolbar.dart';
import 'package:fluffychat/utils/date_time_extension.dart';
@ -39,7 +38,6 @@ class Message extends StatelessWidget {
final bool animateIn;
final void Function()? resetAnimateIn;
// #Pangea
final LanguageModel? selectedDisplayLang;
final bool immersionMode;
final bool definitions;
final ChatController controller;
@ -64,7 +62,6 @@ class Message extends StatelessWidget {
this.resetAnimateIn,
this.avatarPresenceBackgroundColor,
// #Pangea
required this.selectedDisplayLang,
required this.immersionMode,
required this.definitions,
required this.controller,
@ -82,9 +79,9 @@ class Message extends StatelessWidget {
// #Pangea
debugPrint('Message.build()');
WidgetsBinding.instance.addPostFrameCallback((_) {
if (controller.edittingEvents.contains(event.eventId)) {
if (controller.pangeaEditingEvent?.eventId == event.eventId) {
pangeaMessageEvent?.updateLatestEdit();
controller.clearEdittingEvent(event.eventId);
controller.clearEditingEvent();
}
});
// Pangea#

@ -4,7 +4,6 @@ import 'dart:developer';
import 'package:fluffychat/pages/chat/chat.dart';
import 'package:fluffychat/pangea/choreographer/controllers/alternative_translator.dart';
import 'package:fluffychat/pangea/choreographer/controllers/igc_controller.dart';
import 'package:fluffychat/pangea/choreographer/controllers/message_options.dart';
import 'package:fluffychat/pangea/constants/language_constants.dart';
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
import 'package:fluffychat/pangea/controllers/subscription_controller.dart';
@ -38,7 +37,6 @@ class Choreographer {
late PangeaTextController _textController;
late ITController itController;
late IgcController igc;
late MessageOptions messageOptions;
late AlternativeTranslator altTranslator;
late ErrorService errorService;
@ -59,7 +57,6 @@ class Choreographer {
_textController = PangeaTextController(choreographer: this);
itController = ITController(this);
igc = IgcController(this);
messageOptions = MessageOptions(this);
errorService = ErrorService(this);
altTranslator = AlternativeTranslator(this);
_textController.addListener(_onChangeListener);

@ -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,3 +1,4 @@
import 'dart:async';
import 'dart:developer';
import 'package:fluffychat/pangea/choreographer/controllers/choreographer.dart';
@ -7,7 +8,6 @@ import 'package:fluffychat/pangea/choreographer/widgets/it_feedback_card.dart';
import 'package:fluffychat/pangea/choreographer/widgets/translation_finished_flow.dart';
import 'package:fluffychat/pangea/constants/choreo_constants.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
@ -18,15 +18,35 @@ import '../../utils/overlay.dart';
import '../../widgets/igc/word_data_card.dart';
import 'choice_array.dart';
class ITBar extends StatelessWidget {
class ITBar extends StatefulWidget {
final Choreographer choreographer;
const ITBar({super.key, required this.choreographer});
ITController get itController => choreographer.itController;
@override
ITBarState createState() => ITBarState();
}
class ITBarState extends State<ITBar> {
ITController get itController => widget.choreographer.itController;
StreamSubscription? _choreoSub;
@override
Widget build(BuildContext context) {
void initState() {
// Rebuild the widget each time there's an update from choreo.
_choreoSub = widget.choreographer.stateListener.stream.listen((_) {
setState(() {});
});
super.initState();
}
@override
void dispose() {
_choreoSub?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedSize(
duration: itController.willOpen
? const Duration(milliseconds: 2000)
@ -36,14 +56,14 @@ class ITBar extends StatelessWidget {
child: !itController.willOpen
? const SizedBox()
: CompositedTransformTarget(
link: choreographer.itBarLinkAndKey.link,
link: widget.choreographer.itBarLinkAndKey.link,
child: AnimatedOpacity(
duration: itController.willOpen
? const Duration(milliseconds: 2000)
: const Duration(milliseconds: 500),
opacity: itController.willOpen ? 1.0 : 0.0,
child: Container(
key: choreographer.itBarLinkAndKey.key,
key: widget.choreographer.itBarLinkAndKey.key,
decoration: BoxDecoration(
color: Theme.of(context).brightness == Brightness.light
? Colors.white
@ -91,23 +111,30 @@ class ITBar extends StatelessWidget {
const SizedBox(height: 7.0),
IntrinsicHeight(
child: Container(
constraints: const BoxConstraints(minHeight: 80),
constraints:
const BoxConstraints(minHeight: 80),
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 4.0),
padding:
const EdgeInsets.symmetric(horizontal: 4.0),
child: Center(
child: itController.choreographer.errorService.isError
child: itController
.choreographer.errorService.isError
? ITError(
error: itController
.choreographer.errorService.error!,
error: itController.choreographer
.errorService.error!,
controller: itController,
)
: itController.showChoiceFeedback
? ChoiceFeedbackText(controller: itController)
? ChoiceFeedbackText(
controller: itController,
)
: itController.isTranslationDone
? TranslationFeedback(
controller: itController,
)
: ITChoices(controller: itController),
: ITChoices(
controller: itController,
),
),
),
),
@ -117,7 +144,8 @@ class ITBar extends StatelessWidget {
Positioned(
top: 0.0,
right: 0.0,
child: ITCloseButton(choreographer: choreographer),
child:
ITCloseButton(choreographer: widget.choreographer),
),
],
),
@ -199,18 +227,14 @@ class OriginalText extends StatelessWidget {
),
),
),
if (
!controller.isEditingSourceText
&& controller.sourceText != null
)
if (!controller.isEditingSourceText && controller.sourceText != null)
AnimatedOpacity(
duration: const Duration(milliseconds: 500),
opacity: controller.nextITStep != null
? 1.0
: 0.0,
opacity: controller.nextITStep != null ? 1.0 : 0.0,
child: IconButton(
onPressed: () => {
if (controller.nextITStep != null) {
if (controller.nextITStep != null)
{
controller.setIsEditingSourceText(true),
},
},

@ -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,22 +1,47 @@
import 'dart:async';
import 'package:fluffychat/pangea/enum/assistance_state_enum.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import '../../../pages/chat/chat.dart';
class ChoreographerSendButton extends StatelessWidget {
class ChoreographerSendButton extends StatefulWidget {
const ChoreographerSendButton({
super.key,
required this.controller,
});
final ChatController controller;
@override
State<ChoreographerSendButton> createState() =>
ChoreographerSendButtonState();
}
class ChoreographerSendButtonState extends State<ChoreographerSendButton> {
StreamSubscription? _choreoSub;
@override
void initState() {
// Rebuild the widget each time there's an update from
// choreo. This keeps the spin up-to-date.
_choreoSub =
widget.controller.choreographer.stateListener.stream.listen((_) {
setState(() {});
});
super.initState();
}
@override
void dispose() {
_choreoSub?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
// commit for cicd
return controller.choreographer.isFetching &&
controller.choreographer.isAutoIGCEnabled
return widget.controller.choreographer.isFetching &&
widget.controller.choreographer.isAutoIGCEnabled
? Container(
height: 56,
width: 56,
@ -28,10 +53,10 @@ class ChoreographerSendButton extends StatelessWidget {
alignment: Alignment.center,
child: IconButton(
icon: const Icon(Icons.send_outlined),
color:
controller.choreographer.assistanceState.stateColor(context),
color: widget.controller.choreographer.assistanceState
.stateColor(context),
onPressed: () {
controller.choreographer.send(context);
widget.controller.choreographer.send(context);
},
tooltip: L10n.of(context)!.send,
),

@ -82,10 +82,9 @@ class PangeaMessageEvent {
.firstOrNull ??
_event;
Event updateLatestEdit() {
void updateLatestEdit() {
_latestEditCache = null;
_representations = null;
return _latestEdit;
}
Future<PangeaAudioFile> getMatrixAudioFile(

@ -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…
Cancel
Save