import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:provider/provider.dart'; import 'package:yumi/modules/gift/gift_page.dart'; import 'package:yumi/services/gift/room_gift_combo_send_controller.dart'; import 'package:yumi/ui_kit/widgets/room/bottom/room_bottom_chat_entry.dart'; import 'package:yumi/ui_kit/widgets/room/bottom/room_bottom_circle_action.dart'; import 'package:yumi/ui_kit/widgets/room/bottom/room_bottom_gift_button.dart'; import 'package:yumi/ui_kit/widgets/room/bottom/room_gift_combo_floating_button.dart'; import 'package:yumi/ui_kit/widgets/room/room_menu_dialog.dart'; import 'package:yumi/ui_kit/widgets/room/room_msg_input.dart'; import 'package:yumi/ui_kit/components/text/sc_text.dart'; import 'package:yumi/shared/tools/sc_room_utils.dart'; import 'package:yumi/services/audio/rtc_manager.dart'; import '../../../app/routes/sc_fluro_navigator.dart'; import '../../../modules/index/main_route.dart'; import '../../../services/audio/rtm_manager.dart'; import '../../components/sc_debounce_widget.dart'; class RoomBottomWidget extends StatefulWidget { const RoomBottomWidget({super.key, this.showGiftComboButton = true}); static const double floatingButtonHostHeight = _floatingButtonHostHeight; final bool showGiftComboButton; @override State createState() => _RoomBottomWidgetState(); } class RoomGiftComboFloatingLayer extends StatelessWidget { const RoomGiftComboFloatingLayer({super.key}); @override Widget build(BuildContext context) { return Selector( selector: (context, provider) => _shouldShowRoomBottomMic(provider), builder: (context, showMic, child) { return LayoutBuilder( builder: (context, constraints) { final inputWidth = _resolveRoomBottomInputWidth( maxWidth: constraints.maxWidth, showMic: showMic, ); final giftCenterX = _resolveRoomGiftCenterX( maxWidth: constraints.maxWidth, inputWidth: inputWidth, showMic: showMic, ); return Stack( clipBehavior: Clip.none, children: [ Positioned( left: giftCenterX - (_floatingButtonWidth.w / 2), bottom: _floatingButtonBottomOffset.w, child: const RoomGiftComboFloatingButton(), ), ], ); }, ); }, ); } } class _RoomBottomWidgetState extends State { int roomMenuStime1 = 0; @override void dispose() { RoomGiftComboSendController().hide(); super.dispose(); } @override Widget build(BuildContext context) { return SizedBox( height: _floatingButtonHostHeight.w, child: Selector( selector: (context, provider) => _RoomBottomSnapshot( showMic: _shouldShowRoomBottomMic(provider), isMic: provider.isMic, ), builder: (context, bottomSnapshot, child) { return LayoutBuilder( builder: (context, constraints) { final inputWidth = _resolveRoomBottomInputWidth( maxWidth: constraints.maxWidth, showMic: bottomSnapshot.showMic, ); final giftCenterX = _resolveRoomGiftCenterX( maxWidth: constraints.maxWidth, inputWidth: inputWidth, showMic: bottomSnapshot.showMic, ); return Stack( clipBehavior: Clip.none, children: [ if (widget.showGiftComboButton) Positioned( left: giftCenterX - (_floatingButtonWidth.w / 2), bottom: _floatingButtonBottomOffset.w, child: const RoomGiftComboFloatingButton(), ), Positioned( left: 0, right: 0, bottom: 0, child: SizedBox( height: _bottomBarHeight.w, child: _buildBottomBar( inputWidth: inputWidth, showMic: bottomSnapshot.showMic, isMic: bottomSnapshot.isMic, ), ), ), ], ); }, ); }, ), ); } Widget _buildChatEntry(double inputWidth) { return RoomBottomChatEntry( width: inputWidth, onTap: () { if (SCRoomUtils.touristCanMsg(context)) { Navigator.push(context, PopRoute(child: RoomMsgInput())); } }, ); } Widget _buildChatControls(double inputWidth) { return Row( mainAxisSize: MainAxisSize.min, children: [ _buildChatEntry(inputWidth), SizedBox(width: _chatEmojiGap.w), _buildEmojiAction(), ], ); } Widget _buildBottomBar({ required double inputWidth, required bool showMic, required bool isMic, }) { final giftAction = _buildGiftAction(); final messageAction = _buildMessageAction(); final menuAction = _buildMenuAction(); return Padding( padding: EdgeInsetsDirectional.only( start: _horizontalPadding.w, end: _horizontalPadding.w, ), child: showMic ? Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.center, children: [ _buildChatControls(inputWidth), giftAction, _buildMicAction(isMic: isMic), menuAction, messageAction, ], ) : Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ _buildChatControls(inputWidth), const Spacer(), giftAction, SizedBox(width: _compactGap.w), menuAction, SizedBox(width: _compactGap.w), messageAction, ], ), ); } Widget _buildGiftAction() { return SizedBox( width: _giftActionWidth.w, height: _giftActionWidth.w, child: RoomBottomGiftButton(onTap: _showGiftPanel), ); } Widget _buildEmojiAction() { return SizedBox( width: _emojiActionWidth.w, height: 48.w, child: SCDebounceWidget( onTap: () { if (SCRoomUtils.touristCanMsg(context)) { Navigator.push( context, PopRoute(child: const RoomMsgInput(initialShowEmoji: true)), ); } }, child: Center( child: Image.asset( "sc_images/room/sc_icon_room_bottom_emoji.png", width: 34.w, height: 34.w, fit: BoxFit.contain, gaplessPlayback: true, ), ), ), ); } void _showGiftPanel() { SmartDialog.show( tag: "showGiftControl", alignment: Alignment.bottomCenter, maskColor: Colors.transparent, animationType: SmartAnimationType.fade, clickMaskDismiss: true, builder: (_) { return GiftPage(); }, ); } Widget _buildMenuAction() { return SCDebounceWidget( onTap: () { SmartDialog.show( tag: "showRoomMenuDialog", alignment: Alignment.bottomCenter, debounce: true, animationType: SmartAnimationType.fade, maskColor: Colors.transparent, clickMaskDismiss: true, builder: (_) { return RoomMenuDialog(roomMenuStime1, (eTime) { roomMenuStime1 = eTime; }); }, ); }, child: RoomBottomCircleAction( child: Image.asset( "sc_images/room/sc_icon_botton_menu.png", width: 20.w, height: 20.w, fit: BoxFit.contain, ), ), ); } Widget _buildMessageAction() { return SCDebounceWidget( onTap: () { SCNavigatorUtils.push( context, "${SCMainRoute.message}?isFromRoom=true", ); }, child: Selector( selector: (c, p) => p.allUnReadCount, shouldRebuild: (prev, next) => prev != next, builder: (_, allUnReadCount, __) { final action = RoomBottomCircleAction( child: Image.asset( "sc_images/room/sc_icon_botton_message_custom.png", width: 22.w, height: 22.w, fit: BoxFit.contain, ), ); if (allUnReadCount <= 0) { return action; } return Badge( backgroundColor: Colors.red, label: text( "${allUnReadCount > 99 ? "99+" : allUnReadCount}", fontSize: 9.sp, textColor: Colors.white, fontWeight: FontWeight.w600, ), alignment: AlignmentDirectional.topEnd, child: action, ); }, ), ); } Widget _buildMicAction({required bool isMic}) { return GestureDetector( behavior: HitTestBehavior.opaque, onTap: () { final provider = context.read(); setState(() { provider.isMic = !provider.isMic; }); unawaited(provider.setSelfMicMutedFromBottom(provider.isMic)); }, child: RoomBottomCircleAction( child: Image.asset( "sc_images/room/${isMic ? 'sc_icon_botton_mic_close' : 'sc_icon_botton_mic_open'}.png", width: 30.w, height: 30.w, fit: BoxFit.contain, gaplessPlayback: true, ), ), ); } } const double _bottomBarHeight = 72; const double _floatingButtonHostHeight = 122; const double _floatingButtonWidth = RoomGiftComboFloatingButton.hostSize; const double _floatingButtonBottomOffset = 54; const double _giftActionWidth = 52; const double _circleActionWidth = 46; const double _emojiActionWidth = 38; const double _chatEmojiGap = 8; const double _compactGap = 14; const double _horizontalPadding = 16; double _resolveRoomBottomInputWidth({ required double maxWidth, required bool showMic, }) { final contentWidth = maxWidth - (_horizontalPadding.w * 2); final actionWidth = _emojiActionWidth.w + _chatEmojiGap.w + _giftActionWidth.w + _circleActionWidth.w * (showMic ? 3 : 2) + (showMic ? 16.w : _compactGap.w * 2); final availableWidth = contentWidth - actionWidth; return availableWidth.clamp(82.w, 112.w).toDouble(); } double _resolveRoomGiftCenterX({ required double maxWidth, required double inputWidth, required bool showMic, }) { final contentWidth = maxWidth - (_horizontalPadding.w * 2); if (showMic) { final occupiedWidth = inputWidth + _chatEmojiGap.w + _emojiActionWidth.w + _giftActionWidth.w + _circleActionWidth.w * 3; final gapCount = 4; final gap = ((contentWidth - occupiedWidth) / gapCount).clamp( 0.0, double.infinity, ); return _horizontalPadding.w + inputWidth + _chatEmojiGap.w + _emojiActionWidth.w + gap + (_giftActionWidth.w / 2); } final fixedWidth = inputWidth + _chatEmojiGap.w + _emojiActionWidth.w + _giftActionWidth.w + _circleActionWidth.w * 2 + _compactGap.w * 2; final spacerWidth = (contentWidth - fixedWidth).clamp(0.0, double.infinity); return _horizontalPadding.w + inputWidth + spacerWidth + (_giftActionWidth.w / 2); } bool _shouldShowRoomBottomMic(RtcProvider provider) { return provider.isOnMai(); } class _RoomBottomSnapshot { const _RoomBottomSnapshot({required this.showMic, required this.isMic}); final bool showMic; final bool isMic; @override bool operator ==(Object other) { if (identical(this, other)) { return true; } return other is _RoomBottomSnapshot && other.showMic == showMic && other.isMic == isMic; } @override int get hashCode => Object.hash(showMic, isMic); }