yumi-flutter/lib/modules/room/edit/room_edit_page.dart
2026-05-21 18:55:14 +08:00

891 lines
27 KiB
Dart

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<RoomEditPage> createState() => _RoomEditPageState();
}
class _RoomEditPageState extends State<RoomEditPage> {
static const Set<int> _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<RtcProvider>(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<SystemUiOverlayStyle>(
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<RtcProvider>(
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<TextInputFormatter>? inputFormatters,
ValueChanged<String>? 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<RtcProvider>(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<SocialChatRoomManager>(
context,
listen: false,
);
final currentRtcProvider = Provider.of<RtcProvider>(context, listen: false);
final currentRtmProvider = Provider.of<RtmProvider>(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<void> _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();
}
}