import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:yumi/ui_kit/components/sc_debounce_widget.dart'; import 'package:yumi/ui_kit/components/sc_compontent.dart'; import 'package:yumi/ui_kit/components/sc_tts.dart'; import 'package:yumi/shared/tools/sc_loading_manager.dart'; import 'package:yumi/shared/data_sources/sources/repositories/sc_room_repository_imp.dart'; import 'package:yumi/shared/data_sources/sources/repositories/sc_user_repository_impl.dart'; import 'package:yumi/services/audio/rtc_manager.dart'; import 'package:provider/provider.dart'; import 'package:yumi/app_localizations.dart'; import 'package:yumi/ui_kit/components/dialog/dialog_base.dart'; import 'package:yumi/app/routes/sc_fluro_navigator.dart'; import 'package:yumi/app/constants/sc_room_msg_type.dart'; import 'package:yumi/shared/tools/sc_pick_utils.dart'; import 'package:yumi/services/room/rc_room_manager.dart'; import 'package:yumi/services/audio/rtm_manager.dart'; import 'package:yumi/ui_kit/theme/socialchat_theme.dart'; import 'package:yumi/ui_kit/widgets/room/room_msg_item.dart'; import '../../../shared/tools/sc_lk_dialog_util.dart'; import '../../../shared/data_sources/models/enum/sc_room_info_event_type.dart'; import '../../../shared/business_logic/usecases/sc_accurate_length_limiting_textInput_formatter.dart'; import '../../../ui_kit/widgets/room/switch_model/room_mic_switch_page.dart'; import '../block/blocked_list_page.dart'; import '../voice_room_route.dart'; ///编辑房间信息 class RoomEditPage extends StatefulWidget { final String needRestCurrentRoomInfo; final RoomEntryPreviewData? setupPreviewData; const RoomEditPage({ super.key, this.needRestCurrentRoomInfo = "false", this.setupPreviewData, }); @override State createState() => _RoomEditPageState(); } class _RoomEditPageState extends State { static const Set _roomGroupReadyCodes = {0, 10021, 10025}; static const Color _backgroundColor = Color(0xff071E1D); static const Color _fieldColor = Color(0xff08251E); static const Color _fieldBorderColor = Color(0xffB2FBCC); static const Color _dangerColor = Color(0xffff0000); RtcProvider? rtcProvider; bool isEdit = false; bool _isBasicInfoEditing = false; final TextEditingController _roomNameController = TextEditingController(); final TextEditingController _roomAnnouncementController = TextEditingController(); final TextEditingController _roomLockPasswordController = TextEditingController(); final int _roomNameMaxLength = 24; int _roomNameCurrentLength = 0; String roomCover = ""; final int _roomAnnouncementMaxLength = 200; int _roomAnnouncementCurrentLength = 0; bool _initialRoomLocked = false; bool _isRoomPrivate = false; @override void initState() { super.initState(); rtcProvider = Provider.of(context, listen: false); _roomNameController.addListener(() { setState(() { _roomNameCurrentLength = _getActualCharacterCount( _roomNameController.text, ); }); }); _roomAnnouncementController.addListener(() { setState(() { _roomAnnouncementCurrentLength = _getActualCharacterCount( _roomAnnouncementController.text, ); }); }); final setupPreviewData = widget.setupPreviewData; _roomNameController.text = setupPreviewData?.roomName ?? rtcProvider?.currenRoom?.roomProfile?.roomProfile?.roomName ?? ""; _roomNameCurrentLength = _getActualCharacterCount(_roomNameController.text); _roomAnnouncementController.text = setupPreviewData?.roomDesc ?? rtcProvider?.currenRoom?.roomProfile?.roomProfile?.roomDesc ?? ""; _roomAnnouncementCurrentLength = _getActualCharacterCount( _roomAnnouncementController.text, ); roomCover = setupPreviewData?.roomCover ?? rtcProvider?.currenRoom?.roomProfile?.roomProfile?.roomCover ?? ""; final roomPassword = setupPreviewData?.roomSetting?.password?.trim() ?? rtcProvider?.currenRoom?.roomProfile?.roomSetting?.password?.trim() ?? ""; _initialRoomLocked = roomPassword.isNotEmpty; _isRoomPrivate = _initialRoomLocked; } @override void dispose() { _roomNameController.dispose(); _roomAnnouncementController.dispose(); _roomLockPasswordController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return PopScope( canPop: false, onPopInvokedWithResult: (didPop, result) { if (didPop) { return; } _handleSystemBack(); }, child: AnnotatedRegion( value: SystemUiOverlayStyle.light, child: Scaffold( resizeToAvoidBottomInset: false, backgroundColor: _backgroundColor, body: _buildBody(context), ), ), ); } Widget _buildBody(BuildContext context) { final mediaQuery = MediaQuery.of(context); final bottomPadding = mediaQuery.viewPadding.bottom > 34.w ? 34.w : mediaQuery.viewPadding.bottom; return Column( children: [ SizedBox(height: mediaQuery.viewPadding.top), _buildNavigationBar(context), Expanded( child: SingleChildScrollView( keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag, padding: EdgeInsets.only(bottom: bottomPadding + 24.w), child: _buildForm(context), ), ), ], ); } Widget _buildNavigationBar(BuildContext context) { return SizedBox( width: double.infinity, height: 44.w, child: Stack( alignment: Alignment.center, children: [ Positioned( left: 0, top: 0, bottom: 0, child: GestureDetector( behavior: HitTestBehavior.opaque, onTap: _handleBack, child: SizedBox( width: 56.w, height: 44.w, child: Padding( padding: EdgeInsets.only(left: 16.w), child: Align( alignment: Alignment.centerLeft, child: Icon( Icons.keyboard_arrow_left, size: 24.w, color: Colors.white, ), ), ), ), ), ), Text( "Room Setup", textAlign: TextAlign.center, style: TextStyle( color: Colors.white, fontSize: 18.sp, fontWeight: FontWeight.w500, height: 1, ), ), PositionedDirectional( end: 16.w, top: 0, bottom: 0, child: _buildNavigationAction(context), ), ], ), ); } Widget _buildNavigationAction(BuildContext context) { final label = _isBasicInfoEditing || isEdit ? SCAppLocalizations.of(context)!.save : SCAppLocalizations.of(context)!.edit; return SCDebounceWidget( onTap: () { if (!_isBasicInfoEditing && !isEdit) { setState(() { _isBasicInfoEditing = true; }); return; } submit(context); }, child: SizedBox( height: 44.w, child: Center( child: Text( label, textAlign: TextAlign.center, style: TextStyle( color: SocialChatTheme.primaryColor, fontSize: 14.sp, fontWeight: FontWeight.w600, height: 1, ), ), ), ), ); } Widget _buildForm(BuildContext context) { return Column( children: [ SizedBox(height: 16.w), _buildRoomAvatar(), SizedBox(height: 20.w), _buildTextInput( controller: _roomNameController, height: 40.w, hintText: "Please enter a name", counterText: '$_roomNameCurrentLength/$_roomNameMaxLength', readOnly: !_isBasicInfoEditing, inputFormatters: [ SCAccurateLengthLimitingTextInputFormatter(_roomNameMaxLength), ], onChanged: (_) { _markEdited(); }, ), SizedBox(height: 16.w), _buildSectionHeader( title: "Notice", counterText: '$_roomAnnouncementCurrentLength/$_roomAnnouncementMaxLength', ), SizedBox(height: 12.w), _buildTextInput( controller: _roomAnnouncementController, height: 135.w, hintText: "Please enter a name", maxLines: null, expands: true, readOnly: !_isBasicInfoEditing, inputFormatters: [ SCAccurateLengthLimitingTextInputFormatter( _roomAnnouncementMaxLength, ), ], onChanged: (_) { _markEdited(); }, ), SizedBox(height: 16.w), _buildLockHeader(), Visibility( visible: _isRoomPrivate, child: Column( children: [ SizedBox(height: 8.w), _buildTextInput( controller: _roomLockPasswordController, height: 40.w, hintText: "Please enter password", keyboardType: TextInputType.visiblePassword, obscureText: true, onChanged: (_) { _markEdited(); }, ), SizedBox(height: 8.w), _buildLockTips(), ], ), ), if (widget.needRestCurrentRoomInfo != "true") ...[ SizedBox(height: 16.w), _buildSettingRow( icon: Icons.dashboard_rounded, title: SCAppLocalizations.of(context)!.roomTheme2, onTap: () { VoiceRoomRoute.openRoomTheme(context); }, ), SizedBox(height: 8.w), _buildSettingRow( icon: Icons.mic_none_rounded, title: "Microphone Settings", onTap: () { SmartDialog.show( tag: "showRoomMicSwitch", alignment: Alignment.bottomCenter, animationType: SmartAnimationType.fade, debounce: true, builder: (_) { return RoomMicSwitchPage(); }, ); }, ), SizedBox(height: 8.w), _buildSettingRow( icon: Icons.person_off_rounded, title: "Block List", onTap: () { showBottomInBottomDialog( context, BlockedListPage( roomId: Provider.of( context, listen: false, ).currenRoom?.roomProfile?.roomProfile?.id ?? "", ), ); }, ), ], ], ); } Widget _buildRoomAvatar() { return GestureDetector( behavior: HitTestBehavior.opaque, onTap: () { SCPickUtils.pickImage(context, (bool success, String url) { if (success) { setState(() { roomCover = url; isEdit = true; }); } }); }, child: ClipOval( child: netImage( url: roomCover, defaultImg: kRoomCoverDefaultImg, width: 90.w, height: 90.w, fit: BoxFit.cover, shape: BoxShape.circle, ), ), ); } Widget _buildTextInput({ required TextEditingController controller, required double height, required String hintText, String? counterText, int? maxLines = 1, bool expands = false, bool obscureText = false, TextInputType? keyboardType, List? inputFormatters, ValueChanged? onChanged, bool readOnly = false, }) { final isMultiline = maxLines == null || expands; return Container( height: height, margin: EdgeInsets.symmetric(horizontal: 10.w), decoration: BoxDecoration( color: _fieldColor, borderRadius: BorderRadius.circular(8.w), border: Border.all(color: _fieldBorderColor, width: 1), ), child: Stack( alignment: isMultiline ? Alignment.topLeft : Alignment.centerLeft, children: [ Padding( padding: EdgeInsets.only( left: 12.w, right: counterText == null ? 12.w : 56.w, top: isMultiline ? 10.w : 0, bottom: isMultiline ? 10.w : 0, ), child: TextField( controller: controller, cursorColor: SocialChatTheme.primaryColor, keyboardType: keyboardType, obscureText: obscureText, readOnly: readOnly, enableInteractiveSelection: !readOnly, showCursor: !readOnly, inputFormatters: inputFormatters, maxLines: maxLines, expands: expands, onChanged: onChanged, textAlignVertical: isMultiline ? TextAlignVertical.top : TextAlignVertical.center, decoration: InputDecoration( hintText: hintText, hintStyle: TextStyle( color: Colors.white, fontSize: 14.sp, height: 16 / 14, ), filled: true, fillColor: Colors.transparent, border: InputBorder.none, enabledBorder: InputBorder.none, focusedBorder: InputBorder.none, disabledBorder: InputBorder.none, errorBorder: InputBorder.none, focusedErrorBorder: InputBorder.none, counterText: '', isDense: true, isCollapsed: !isMultiline, contentPadding: EdgeInsets.zero, ), style: TextStyle( color: Colors.white, backgroundColor: Colors.transparent, fontSize: 14.sp, fontWeight: FontWeight.w400, height: 16 / 14, ), ), ), if (counterText != null) PositionedDirectional( end: 12.w, top: 0, bottom: 0, child: Center( child: Text( counterText, textAlign: TextAlign.right, style: TextStyle( color: Colors.white.withValues(alpha: 0.6), fontSize: 14.sp, fontWeight: FontWeight.w400, height: 16 / 14, ), ), ), ), ], ), ); } void _markEdited() { if (isEdit) { return; } setState(() { isEdit = true; }); } Widget _buildSectionHeader({required String title, String? counterText}) { return Padding( padding: EdgeInsets.symmetric(horizontal: 10.w), child: SizedBox( height: 16.w, child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ Text( title, style: TextStyle( color: Colors.white, fontSize: 16.sp, fontWeight: FontWeight.w500, height: 1, ), ), const Spacer(), if (counterText != null) Text( counterText, style: TextStyle( color: Colors.white.withValues(alpha: 0.6), fontSize: 12.sp, fontWeight: FontWeight.w400, height: 1, ), ), ], ), ), ); } Widget _buildLockHeader() { return Padding( padding: EdgeInsets.symmetric(horizontal: 10.w), child: SizedBox( height: 20.w, child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ Text( "Room lock", style: TextStyle( color: Colors.white, fontSize: 16.sp, fontWeight: FontWeight.w500, height: 1, ), ), const Spacer(), _buildLockOption( selected: !_isRoomPrivate, label: "Public", onTap: () { setState(() { if (_isRoomPrivate) { isEdit = true; } _isRoomPrivate = false; }); }, ), SizedBox(width: 8.w), _buildLockOption( selected: _isRoomPrivate, label: "Private", onTap: () { setState(() { if (!_isRoomPrivate) { isEdit = true; } _isRoomPrivate = true; }); }, ), ], ), ), ); } Widget _buildLockOption({ required bool selected, required String label, required VoidCallback onTap, }) { return GestureDetector( behavior: HitTestBehavior.opaque, onTap: onTap, child: SizedBox( height: 20.w, child: Row( mainAxisSize: MainAxisSize.min, children: [ SizedBox( width: 20.w, height: 20.w, child: Center( child: Container( width: 14.w, height: 14.w, decoration: BoxDecoration( color: selected ? SocialChatTheme.primaryColor : Colors.transparent, borderRadius: BorderRadius.circular(2.w), border: Border.all( color: SocialChatTheme.primaryColor, width: 1.w, ), ), child: selected ? Icon(Icons.check, color: Colors.white, size: 11.w) : null, ), ), ), SizedBox(width: 4.w), Text( label, style: TextStyle( color: selected ? Colors.white : Colors.white.withValues(alpha: 0.6), fontSize: 12.sp, fontWeight: FontWeight.w400, height: 1, ), ), ], ), ), ); } Widget _buildLockTips() { return Padding( padding: EdgeInsets.symmetric(horizontal: 10.w), child: SizedBox( width: double.infinity, child: Text( "After locking the door, enter the correct password to enter the room.", maxLines: 2, style: TextStyle( color: _dangerColor, fontSize: 14.sp, fontWeight: FontWeight.w400, height: 16 / 14, ), ), ), ); } Widget _buildSettingRow({ required IconData icon, required String title, required VoidCallback onTap, }) { return Padding( padding: EdgeInsets.symmetric(horizontal: 10.w), child: SCDebounceWidget( onTap: onTap, child: Container( height: 40.w, decoration: BoxDecoration( color: _fieldColor, borderRadius: BorderRadius.circular(8.w), border: Border.all(color: _fieldBorderColor, width: 1), ), child: Row( children: [ SizedBox(width: 8.w), Icon(icon, color: Colors.white, size: 24.w), SizedBox(width: 4.w), Expanded( child: Text( title, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( color: Colors.white, fontSize: 14.sp, fontWeight: FontWeight.w400, height: 16 / 14, ), ), ), Icon(Icons.keyboard_arrow_right, color: Colors.white, size: 24.w), SizedBox(width: 8.w), ], ), ), ), ); } void _handleBack() { if (widget.needRestCurrentRoomInfo == "true" && widget.setupPreviewData == null) { Provider.of(context, listen: false).currenRoom = null; } Navigator.pop(context); } void _handleSystemBack() { if (!isEdit) { SCNavigatorUtils.goBack(context); return; } SmartDialog.show( tag: "showConfirmDialog", alignment: Alignment.center, debounce: true, animationType: SmartAnimationType.fade, builder: (_) { return MsgDialog( title: SCAppLocalizations.of(context)!.tips, msg: SCAppLocalizations.of(context)!.theModificationsMade, onEnsure: () { SCNavigatorUtils.goBack(context); }, ); }, ); } int _getActualCharacterCount(String text) { return text.characters.length; } String _preferNonEmpty(String? primary, String fallback) { if ((primary ?? "").trim().isNotEmpty) { return primary!; } return fallback; } void submit(BuildContext context) async { final roomManager = Provider.of( context, listen: false, ); final currentRtcProvider = Provider.of(context, listen: false); final currentRtmProvider = Provider.of(context, listen: false); final setupPreviewData = widget.setupPreviewData; final currentRoomProfile = rtcProvider?.currenRoom?.roomProfile?.roomProfile; final currentRoom = rtcProvider?.currenRoom?.roomProfile; final submittedRoomName = _roomNameController.text.trim(); final submittedRoomDesc = _roomAnnouncementController.text.trim(); final submittedRoomCover = roomCover.trim(); final submittedRoomPassword = _roomLockPasswordController.text.trim(); final roomId = setupPreviewData?.roomId ?? currentRoomProfile?.id ?? ""; final roomAccount = setupPreviewData?.roomAccount ?? currentRoomProfile?.roomAccount ?? ""; final roomUserId = setupPreviewData?.userId ?? currentRoomProfile?.userId; final roomSetting = setupPreviewData?.roomSetting ?? currentRoom?.roomSetting; final ownerProfile = setupPreviewData?.ownerProfile ?? currentRoom?.userProfile; final roomAdminCount = setupPreviewData?.roomAdminCount ?? currentRoom?.roomCounter?.adminCount; final roomMemberCount = setupPreviewData?.roomMemberCount ?? currentRoom?.roomCounter?.memberCount; final entrantsRole = setupPreviewData?.entrantsRole ?? rtcProvider?.currenRoom?.entrants?.roles; if (roomId.trim().isEmpty || roomAccount.trim().isEmpty) { SCTts.show("Room data is not ready"); return; } if (submittedRoomName.isEmpty) { SCTts.show("Room name not empty!"); return; } if (submittedRoomDesc.isEmpty) { SCTts.show("Room announcement not empty!"); return; } if (_isRoomPrivate && (!_initialRoomLocked || submittedRoomPassword.isNotEmpty) && submittedRoomPassword.isEmpty) { SCTts.show("Please enter password"); return; } SCLoadingManager.show(context: context); var roomInfo = await SCAccountRepository().editRoomInfo( roomId, submittedRoomCover, submittedRoomName, submittedRoomDesc, SCRoomInfoEventType.AVAILABLE.name, ); await _submitRoomLockSetting(roomId, submittedRoomPassword); if (!context.mounted) { SCLoadingManager.hide(); return; } final mergedRoomInfo = roomInfo.copyWith( id: _preferNonEmpty(roomInfo.id, roomId), roomAccount: _preferNonEmpty(roomInfo.roomAccount, roomAccount), userId: roomInfo.userId ?? roomUserId, roomCover: _preferNonEmpty(roomInfo.roomCover, submittedRoomCover), roomName: _preferNonEmpty(roomInfo.roomName, submittedRoomName), roomDesc: _preferNonEmpty(roomInfo.roomDesc, submittedRoomDesc), ); currentRtcProvider.updateCurrentRoomBasicInfo( roomCover: mergedRoomInfo.roomCover, roomName: mergedRoomInfo.roomName, roomDesc: mergedRoomInfo.roomDesc, ); roomManager.updateMyRoomInfo(mergedRoomInfo); if (widget.needRestCurrentRoomInfo != "true") { currentRtmProvider.dispatchMessage( Msg( groupId: rtcProvider?.currenRoom?.roomProfile?.roomProfile?.roomAccount ?? "", msg: mergedRoomInfo.id ?? rtcProvider?.currenRoom?.roomProfile?.roomProfile?.id ?? "", type: SCRoomMsgType.roomSettingUpdate, ), addLocal: false, ); } if (widget.needRestCurrentRoomInfo == "true") { ///需要创建群组 var c = await currentRtmProvider.createRoomGroup( roomAccount, mergedRoomInfo.roomName ?? submittedRoomName, ); if (!context.mounted) { SCLoadingManager.hide(); return; } if (_roomGroupReadyCodes.contains(c.code)) { SCLoadingManager.hide(); SCTts.show(SCAppLocalizations.of(context)!.createRoomSuccsess); SCNavigatorUtils.goBack(context); currentRtcProvider.joinVoiceRoomSession( context, mergedRoomInfo.id ?? roomId, clearRoomData: setupPreviewData == null, previewData: RoomEntryPreviewData( roomId: mergedRoomInfo.id ?? roomId, roomAccount: mergedRoomInfo.roomAccount ?? roomAccount, userId: mergedRoomInfo.userId ?? roomUserId, roomCover: mergedRoomInfo.roomCover, roomName: mergedRoomInfo.roomName ?? submittedRoomName, roomDesc: mergedRoomInfo.roomDesc ?? submittedRoomDesc, roomSetting: roomSetting, existsPassword: _isRoomPrivate, ownerProfile: ownerProfile, roomAdminCount: roomAdminCount, roomMemberCount: roomMemberCount, entrantsRole: entrantsRole, ), ); } else { SCLoadingManager.hide(); SCTts.show("${c.code}"); } } else { currentRtcProvider.loadRoomInfo( mergedRoomInfo.id ?? rtcProvider?.currenRoom?.roomProfile?.roomProfile?.id ?? "", ); SCLoadingManager.hide(); SCNavigatorUtils.goBack(context); } } Future _submitRoomLockSetting(String roomId, String password) async { final normalizedRoomId = roomId.trim(); if (normalizedRoomId.isEmpty) { return; } if (_isRoomPrivate) { if (!_initialRoomLocked || password.isNotEmpty) { await SCChatRoomRepository().roomLocked(normalizedRoomId, password); } } else if (_initialRoomLocked) { await SCChatRoomRepository().roomUnlocked(normalizedRoomId); } _initialRoomLocked = _isRoomPrivate; _roomLockPasswordController.clear(); } }