import 'package:flutter/cupertino.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:provider/provider.dart'; import 'package:yumi/shared/tools/sc_cp_relation_pair_utils.dart'; import 'package:yumi/services/audio/rtc_manager.dart'; import 'package:yumi/ui_kit/widgets/room/seat/room_mic_relation_effect.dart'; import '../../../../modules/room/seat/sc_seat_item.dart'; class RoomSeatWidget extends StatefulWidget { const RoomSeatWidget({super.key}); @override State createState() => _RoomSeatWidgetState(); } class _RoomSeatWidgetState extends State { static const int _defaultVoiceRoomSeatCount = 10; static const int _seatColumnCount = 5; static const double _relationEffectWidth = 108; static const double _relationEffectHeight = 54; static const double _relationEffectTop = -2; int _lastSeatCount = 0; int _normalizeSeatCount(int? seatCount) { switch (seatCount) { case 5: case 10: case 15: case 20: return seatCount ?? 0; default: return 0; } } int _resolvedSeatCount(_RoomSeatLayoutSnapshot snapshot) { final int liveSeatCount = _normalizeSeatCount(snapshot.seatCount); final int configuredSeatCount = _normalizeSeatCount( snapshot.configuredSeatCount, ); final int seatCount = configuredSeatCount > 0 ? configuredSeatCount : liveSeatCount; if (!snapshot.isExitingCurrentVoiceRoomSession && seatCount > 0) { _lastSeatCount = seatCount; } if (snapshot.isExitingCurrentVoiceRoomSession && _lastSeatCount > 0) { return _lastSeatCount; } if (snapshot.hasCurrentRoom && seatCount == 0) { return _lastSeatCount > 0 ? _lastSeatCount : _defaultVoiceRoomSeatCount; } return seatCount; } int _seatCountFromMicIndexes(Iterable micIndexes) { var maxIndex = -1; var count = 0; for (final micIndex in micIndexes) { count += 1; final resolvedIndex = micIndex.toInt(); if (resolvedIndex > maxIndex) { maxIndex = resolvedIndex; } } final indexedSeatCount = _normalizeSeatCount(maxIndex + 1); if (indexedSeatCount > 0) { return indexedSeatCount; } return _normalizeSeatCount(count); } int _seatContentVersion(RtcProvider provider) { var version = provider.roomWheatMap.length; final micIndexes = provider.roomWheatMap.keys.toList()..sort((a, b) => a.compareTo(b)); for (final micIndex in micIndexes) { final seat = provider.micAtIndexForDisplay(micIndex); final user = seat?.user; version = Object.hash( version, micIndex, seat?.micLock ?? false, seat?.micMute ?? false, user?.id ?? "", user?.userAvatar ?? "", user?.userNickname ?? "", user?.roles ?? "", user?.getHeaddress()?.sourceUrl ?? "", user?.getHeaddress()?.cover ?? "", user?.getVIP()?.name ?? "", scCpProfileRelationVersion(user), ); } return version; } @override Widget build(BuildContext context) { return Selector( selector: (context, provider) => _RoomSeatLayoutSnapshot( seatCount: _seatCountFromMicIndexes(provider.roomWheatMap.keys), seatContentVersion: _seatContentVersion(provider), showRoomVisualEffects: provider.shouldShowRoomVisualEffects, configuredSeatCount: provider.currenRoom?.roomProfile?.roomSetting?.mikeSize ?.toInt() ?? provider.previewRoomSeatCount ?? 0, hasCurrentRoom: provider.currenRoom != null, isExitingCurrentVoiceRoomSession: provider.isExitingCurrentVoiceRoomSession, ), builder: (context, snapshot, child) { final int seatCount = _resolvedSeatCount(snapshot); return seatCount == 5 ? _buildSeat5() : (seatCount == 10 ? _buildSeat10() : (seatCount == 15 ? _buildSeat15() : (seatCount == 20 ? _buildSeat20() : Container(height: 180.w)))); }, ); } _buildSeat5() { return Container( margin: EdgeInsets.only(top: 3.w, left: 10.w, right: 10.w), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ _buildSeatRow(const [0, 1, 2, 3, 4]), ], ), ); } _buildSeat10() { return Container( margin: EdgeInsets.only(top: 3.w, left: 10.w, right: 10.w), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ _buildSeatRow(const [0, 1, 2, 3, 4]), _buildSeatRow(const [5, 6, 7, 8, 9]), ], ), ); } _buildSeat15() { return Container( margin: EdgeInsets.only(top: 3.w, left: 10.w, right: 10.w), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ _buildSeatRow(const [0, 1, 2, 3, 4]), _buildSeatRow(const [5, 6, 7, 8, 9]), _buildSeatRow(const [10, 11, 12, 13, 14]), ], ), ); } _buildSeat20() { return Container( margin: EdgeInsets.only(top: 3.w, left: 10.w, right: 10.w), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ _buildSeatRow(const [0, 1, 2, 3, 4]), _buildSeatRow(const [5, 6, 7, 8, 9]), _buildSeatRow(const [10, 11, 12, 13, 14]), _buildSeatRow(const [15, 16, 17, 18, 19]), ], ), ); } Widget _buildSeatRow(List indexes) { return LayoutBuilder( builder: (context, constraints) { final rowWidth = constraints.maxWidth; final columnWidth = rowWidth / _seatColumnCount; return Stack( clipBehavior: Clip.none, children: [ Row( children: [ for (final index in indexes) Expanded(flex: 1, child: SCSeatItem(index: index)), ], ), if (rowWidth.isFinite) ..._buildRelationEffectsForRow( context, indexes: indexes, columnWidth: columnWidth, ), ], ); }, ); } List _buildRelationEffectsForRow( BuildContext context, { required List indexes, required double columnWidth, }) { final provider = context.read(); if (!provider.shouldShowRoomVisualEffects) { return const []; } final effects = []; for (var slot = 0; slot < indexes.length - 1; slot += 1) { final relationInfo = _relationInfoBetweenSeats( provider, indexes[slot], indexes[slot + 1], ); if (relationInfo == null) { continue; } final left = ((slot + 1) * columnWidth - (_relationEffectWidth.w / 2)) .clamp(0.0, double.infinity); effects.add( Positioned( left: left, top: _relationEffectTop.w, width: _relationEffectWidth.w, height: _relationEffectHeight.w, child: IgnorePointer( child: RoomMicRelationEffect( relationType: relationInfo.relationType, cpLevel: relationInfo.cpLevel, ), ), ), ); } return effects; } SCCpRelationPairInfo? _relationInfoBetweenSeats( RtcProvider provider, int leftIndex, int rightIndex, ) { final leftUser = provider.micAtIndexForDisplay(leftIndex)?.user; final rightUser = provider.micAtIndexForDisplay(rightIndex)?.user; return scCpRelationInfoBetweenProfiles(leftUser, rightUser); } } class _RoomSeatLayoutSnapshot { const _RoomSeatLayoutSnapshot({ required this.seatCount, required this.seatContentVersion, required this.showRoomVisualEffects, required this.configuredSeatCount, required this.hasCurrentRoom, required this.isExitingCurrentVoiceRoomSession, }); final int seatCount; final int seatContentVersion; final bool showRoomVisualEffects; final int configuredSeatCount; final bool hasCurrentRoom; final bool isExitingCurrentVoiceRoomSession; @override bool operator ==(Object other) { if (identical(this, other)) { return true; } return other is _RoomSeatLayoutSnapshot && other.seatCount == seatCount && other.seatContentVersion == seatContentVersion && other.showRoomVisualEffects == showRoomVisualEffects && other.configuredSeatCount == configuredSeatCount && other.hasCurrentRoom == hasCurrentRoom && other.isExitingCurrentVoiceRoomSession == isExitingCurrentVoiceRoomSession; } @override int get hashCode => Object.hash( seatCount, seatContentVersion, showRoomVisualEffects, configuredSeatCount, hasCurrentRoom, isExitingCurrentVoiceRoomSession, ); }