693 lines
22 KiB
Dart
693 lines
22 KiB
Dart
import 'dart:async';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
|
import 'package:provider/provider.dart';
|
|
import 'package:yumi/app/constants/sc_room_msg_type.dart';
|
|
import 'package:yumi/services/audio/rtc_manager.dart';
|
|
import 'package:yumi/services/audio/rtm_manager.dart';
|
|
import 'package:yumi/services/general/sc_app_general_manager.dart';
|
|
import 'package:yumi/services/music/room_music_manager.dart';
|
|
import 'package:yumi/ui_kit/components/sc_debounce_widget.dart';
|
|
import 'package:yumi/ui_kit/widgets/room/music/room_music_floating_entry.dart';
|
|
import 'package:yumi/ui_kit/widgets/room/red_packet/room_red_packet_models.dart';
|
|
import 'package:yumi/ui_kit/widgets/room/red_packet/room_red_packet_open_dialog.dart';
|
|
import 'package:yumi/ui_kit/widgets/room/red_packet/room_red_packet_pending_cache.dart';
|
|
import 'package:yumi/ui_kit/widgets/room/room_banner_view.dart';
|
|
import 'package:yumi/ui_kit/widgets/room/room_game_bottom_sheet.dart';
|
|
import 'package:yumi/ui_kit/widgets/room/rocket/room_rocket_floating_entry.dart';
|
|
|
|
class RoomPlayWidget extends StatefulWidget {
|
|
const RoomPlayWidget({super.key});
|
|
|
|
@override
|
|
State<RoomPlayWidget> createState() => _RoomPlayWidgetState();
|
|
}
|
|
|
|
class _RoomPlayWidgetState extends State<RoomPlayWidget> {
|
|
static const double _railEnd = 8;
|
|
static const double _railBottom = 30;
|
|
static const double _railWidth = 58;
|
|
static const double _railVerticalPadding = 8;
|
|
static const double _actionExtent = 56;
|
|
static const double _actionGap = 5;
|
|
static const double _toggleExtent = 20;
|
|
static const int _compactActionCount = 2;
|
|
|
|
bool _rightActionsExpanded = false;
|
|
bool _redPacketEntryVisible = false;
|
|
|
|
void _handleRedPacketEntryVisibilityChanged(bool visible) {
|
|
if (_redPacketEntryVisible == visible || !mounted) {
|
|
return;
|
|
}
|
|
setState(() {
|
|
_redPacketEntryVisible = visible;
|
|
});
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return LayoutBuilder(
|
|
builder: (context, constraints) {
|
|
final hasCurrentMusic = context.select<RoomMusicManager, bool>(
|
|
(manager) => manager.current != null,
|
|
);
|
|
final hasRocket = context.select<RtcProvider, bool>(
|
|
(provider) => _hasRoomRocketEntry(provider),
|
|
);
|
|
final hasRoomBanner = context.select<SCAppGeneralManager, bool>(
|
|
(manager) => manager.roomBanners.isNotEmpty,
|
|
);
|
|
final actions = <_RoomSideAction>[
|
|
if (_redPacketEntryVisible) _RoomSideAction.redPacket,
|
|
if (hasRocket) _RoomSideAction.rocket,
|
|
if (hasCurrentMusic) _RoomSideAction.music,
|
|
_RoomSideAction.game,
|
|
if (hasRoomBanner) _RoomSideAction.banner,
|
|
];
|
|
final fullHeight = _railHeight(
|
|
actionCount: actions.length,
|
|
showToggle: false,
|
|
showBackground: false,
|
|
);
|
|
final availableHeight =
|
|
(constraints.maxHeight - _railBottom.w)
|
|
.clamp(0.0, constraints.maxHeight)
|
|
.toDouble();
|
|
final shouldFold =
|
|
actions.length > _compactActionCount &&
|
|
availableHeight < fullHeight;
|
|
final isExpanded = !shouldFold || _rightActionsExpanded;
|
|
final isCollapsed = shouldFold && !isExpanded;
|
|
final visibleActions =
|
|
isExpanded ? actions : actions.take(_compactActionCount).toList();
|
|
final railHeight = _railHeight(
|
|
actionCount: visibleActions.length,
|
|
showToggle: shouldFold,
|
|
showBackground: isCollapsed,
|
|
);
|
|
|
|
Widget rail = _RoomSideActionRail(
|
|
actions: visibleActions,
|
|
showToggle: shouldFold,
|
|
expanded: isExpanded,
|
|
showBackground: isCollapsed,
|
|
onToggle: () {
|
|
setState(() {
|
|
_rightActionsExpanded = !_rightActionsExpanded;
|
|
});
|
|
},
|
|
actionBuilder: _buildAction,
|
|
);
|
|
if (shouldFold && isExpanded && railHeight > availableHeight) {
|
|
rail = SizedBox(
|
|
width: _railWidth.w,
|
|
height: availableHeight.clamp(0.0, railHeight).toDouble(),
|
|
child: FittedBox(
|
|
alignment: AlignmentDirectional.bottomEnd,
|
|
fit: BoxFit.scaleDown,
|
|
child: rail,
|
|
),
|
|
);
|
|
}
|
|
|
|
return SizedBox.expand(
|
|
child: Stack(
|
|
clipBehavior: Clip.none,
|
|
children: [
|
|
PositionedDirectional(
|
|
end: _railEnd.w,
|
|
bottom: _railBottom.w,
|
|
child: rail,
|
|
),
|
|
if (!_redPacketEntryVisible)
|
|
PositionedDirectional(
|
|
end: 0,
|
|
bottom: 0,
|
|
child: Offstage(
|
|
child: _RoomRedPacketFloatingEntry(
|
|
onVisibilityChanged:
|
|
_handleRedPacketEntryVisibilityChanged,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
bool _hasRoomRocketEntry(RtcProvider provider) {
|
|
final status = provider.roomRocketStatus;
|
|
if (status == null || !status.isEnabled) {
|
|
return false;
|
|
}
|
|
return status.levels?.any((item) => item.rocketIconUrl.trim().isNotEmpty) ??
|
|
false;
|
|
}
|
|
|
|
double _railHeight({
|
|
required int actionCount,
|
|
required bool showToggle,
|
|
required bool showBackground,
|
|
}) {
|
|
final actionGaps = actionCount > 1 ? actionCount - 1 : 0;
|
|
final toggleHeight =
|
|
showToggle ? _toggleExtent + (actionCount > 0 ? _actionGap : 0) : 0.0;
|
|
final verticalPadding = showBackground ? _railVerticalPadding * 2 : 0.0;
|
|
return (verticalPadding +
|
|
toggleHeight +
|
|
actionCount * _actionExtent +
|
|
actionGaps * _actionGap)
|
|
.w;
|
|
}
|
|
|
|
Widget _buildAction(_RoomSideAction action) {
|
|
final child = switch (action) {
|
|
_RoomSideAction.redPacket => _RoomRedPacketFloatingEntry(
|
|
onVisibilityChanged: _handleRedPacketEntryVisibilityChanged,
|
|
),
|
|
_RoomSideAction.rocket => const RoomRocketFloatingEntry(),
|
|
_RoomSideAction.music => const RoomMusicFloatingEntry(),
|
|
_RoomSideAction.game => const RoomGameEntryButton(),
|
|
_RoomSideAction.banner => RoomBannerView(),
|
|
};
|
|
return SizedBox(
|
|
width: 46.w,
|
|
height: _actionExtent.w,
|
|
child: Center(child: child),
|
|
);
|
|
}
|
|
}
|
|
|
|
enum _RoomSideAction { redPacket, rocket, music, game, banner }
|
|
|
|
class _RoomSideActionRail extends StatelessWidget {
|
|
const _RoomSideActionRail({
|
|
required this.actions,
|
|
required this.showToggle,
|
|
required this.expanded,
|
|
required this.showBackground,
|
|
required this.onToggle,
|
|
required this.actionBuilder,
|
|
});
|
|
|
|
final List<_RoomSideAction> actions;
|
|
final bool showToggle;
|
|
final bool expanded;
|
|
final bool showBackground;
|
|
final VoidCallback onToggle;
|
|
final Widget Function(_RoomSideAction action) actionBuilder;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final children = <Widget>[];
|
|
if (showToggle) {
|
|
children.add(
|
|
SCDebounceWidget(
|
|
onTap: onToggle,
|
|
child: SizedBox(
|
|
width: 46.w,
|
|
height: _RoomPlayWidgetState._toggleExtent.w,
|
|
child: Center(child: _RoomSideToggleIcon(expanded: expanded)),
|
|
),
|
|
),
|
|
);
|
|
if (actions.isNotEmpty) {
|
|
children.add(SizedBox(height: _RoomPlayWidgetState._actionGap.w));
|
|
}
|
|
}
|
|
for (var index = 0; index < actions.length; index++) {
|
|
if (index > 0) {
|
|
children.add(SizedBox(height: _RoomPlayWidgetState._actionGap.w));
|
|
}
|
|
children.add(
|
|
KeyedSubtree(
|
|
key: ValueKey(actions[index]),
|
|
child: actionBuilder(actions[index]),
|
|
),
|
|
);
|
|
}
|
|
return Container(
|
|
width: _RoomPlayWidgetState._railWidth.w,
|
|
padding: EdgeInsets.symmetric(
|
|
horizontal: 6.w,
|
|
vertical:
|
|
showBackground ? _RoomPlayWidgetState._railVerticalPadding.w : 0,
|
|
),
|
|
decoration:
|
|
showBackground
|
|
? BoxDecoration(
|
|
color: Colors.black.withValues(alpha: 0.21),
|
|
borderRadius: BorderRadius.circular(8.w),
|
|
)
|
|
: null,
|
|
child: Column(mainAxisSize: MainAxisSize.min, children: children),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _RoomSideToggleIcon extends StatelessWidget {
|
|
const _RoomSideToggleIcon({required this.expanded});
|
|
|
|
final bool expanded;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Transform.rotate(
|
|
angle: expanded ? 3.141592653589793 : 0,
|
|
child: CustomPaint(
|
|
size: Size(16.w, 16.w),
|
|
painter: const _RoomSideTogglePainter(),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _RoomSideTogglePainter extends CustomPainter {
|
|
const _RoomSideTogglePainter();
|
|
|
|
@override
|
|
void paint(Canvas canvas, Size size) {
|
|
final paint =
|
|
Paint()
|
|
..color = Colors.white.withValues(alpha: 0.8)
|
|
..style = PaintingStyle.stroke
|
|
..strokeWidth = 2
|
|
..strokeCap = StrokeCap.round
|
|
..strokeJoin = StrokeJoin.round;
|
|
final width = size.width;
|
|
final left = width * 0.22;
|
|
final center = width * 0.5;
|
|
final right = width * 0.78;
|
|
|
|
void drawChevron(double top, double bottom) {
|
|
final path =
|
|
Path()
|
|
..moveTo(left, top)
|
|
..lineTo(center, bottom)
|
|
..lineTo(right, top);
|
|
canvas.drawPath(path, paint);
|
|
}
|
|
|
|
drawChevron(size.height * 0.30, size.height * 0.52);
|
|
drawChevron(size.height * 0.52, size.height * 0.74);
|
|
}
|
|
|
|
@override
|
|
bool shouldRepaint(covariant _RoomSideTogglePainter oldDelegate) => false;
|
|
}
|
|
|
|
class _RoomRedPacketFloatingEntry extends StatefulWidget {
|
|
const _RoomRedPacketFloatingEntry({this.onVisibilityChanged});
|
|
|
|
final ValueChanged<bool>? onVisibilityChanged;
|
|
|
|
@override
|
|
State<_RoomRedPacketFloatingEntry> createState() =>
|
|
_RoomRedPacketFloatingEntryState();
|
|
}
|
|
|
|
class _RoomRedPacketFloatingEntryState
|
|
extends State<_RoomRedPacketFloatingEntry> {
|
|
static const int _listRefreshIntervalSeconds = 5;
|
|
|
|
Timer? _entryRefreshTimer;
|
|
bool _refreshingList = false;
|
|
int _elapsedSeconds = 0;
|
|
String _observedRoomId = '';
|
|
String _verifiedRoomId = '';
|
|
int _verifiedRoomRefreshCount = 0;
|
|
Set<String> _verifiedKnownPacketIds = const <String>{};
|
|
Set<String> _verifiedVisiblePacketIds = const <String>{};
|
|
bool? _lastNotifiedVisibility;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_entryRefreshTimer = Timer.periodic(
|
|
const Duration(seconds: 1),
|
|
(_) => _onEntryTimerTick(),
|
|
);
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
if (mounted) {
|
|
_refreshRoomRedPacketList();
|
|
}
|
|
});
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_entryRefreshTimer?.cancel();
|
|
super.dispose();
|
|
}
|
|
|
|
void _notifyVisibilityChanged(bool visible) {
|
|
if (_lastNotifiedVisibility == visible) {
|
|
return;
|
|
}
|
|
_lastNotifiedVisibility = visible;
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
if (!mounted) {
|
|
return;
|
|
}
|
|
widget.onVisibilityChanged?.call(visible);
|
|
});
|
|
}
|
|
|
|
void _onEntryTimerTick() {
|
|
RoomRedPacketPendingCache.instance.pruneExpired();
|
|
if (!mounted) {
|
|
return;
|
|
}
|
|
_refreshRoomRedPacketListIfRoomChanged();
|
|
setState(() {});
|
|
_elapsedSeconds++;
|
|
if (_elapsedSeconds % _listRefreshIntervalSeconds == 0) {
|
|
_refreshRoomRedPacketList();
|
|
}
|
|
}
|
|
|
|
void _refreshRoomRedPacketListIfRoomChanged() {
|
|
try {
|
|
final rtcProvider = Provider.of<RtcProvider>(context, listen: false);
|
|
final currentRoomId =
|
|
rtcProvider.currenRoom?.roomProfile?.roomProfile?.id?.trim() ?? '';
|
|
if (currentRoomId.isEmpty || currentRoomId == _observedRoomId) {
|
|
return;
|
|
}
|
|
_observedRoomId = currentRoomId;
|
|
_elapsedSeconds = 0;
|
|
_verifiedRoomId = '';
|
|
_verifiedRoomRefreshCount = 0;
|
|
_verifiedKnownPacketIds = const <String>{};
|
|
_verifiedVisiblePacketIds = const <String>{};
|
|
_refreshRoomRedPacketList();
|
|
} catch (_) {}
|
|
}
|
|
|
|
void _refreshRoomRedPacketList() {
|
|
if (_refreshingList || !mounted) {
|
|
return;
|
|
}
|
|
try {
|
|
final rtcProvider = Provider.of<RtcProvider>(context, listen: false);
|
|
final currentRoomId =
|
|
rtcProvider.currenRoom?.roomProfile?.roomProfile?.id?.trim() ?? '';
|
|
if (currentRoomId.isEmpty) {
|
|
return;
|
|
}
|
|
_refreshingList = true;
|
|
unawaited(
|
|
rtcProvider
|
|
.loadRoomRedPacketList(1)
|
|
.then((result) {
|
|
final activeRoomId =
|
|
rtcProvider.currenRoom?.roomProfile?.roomProfile?.id
|
|
?.trim() ??
|
|
'';
|
|
if (activeRoomId != currentRoomId) {
|
|
return result;
|
|
}
|
|
final serverPackets =
|
|
result.map(RoomRedPacketUiData.fromListRes).toList();
|
|
final serverPacketIds =
|
|
serverPackets
|
|
.map((item) => item.packetId.trim())
|
|
.where((packetId) => packetId.isNotEmpty)
|
|
.toSet();
|
|
final visiblePacketIds =
|
|
serverPackets
|
|
.map(RoomRedPacketOpenDialog.withLocalClaimState)
|
|
.where(_shouldShowRoomRedPacketEntry)
|
|
.map((item) => item.packetId.trim())
|
|
.where((packetId) => packetId.isNotEmpty)
|
|
.toSet();
|
|
if (!mounted) {
|
|
return result;
|
|
}
|
|
setState(() {
|
|
if (_verifiedRoomId == currentRoomId) {
|
|
_verifiedRoomRefreshCount++;
|
|
_verifiedKnownPacketIds = {
|
|
..._verifiedKnownPacketIds,
|
|
...serverPacketIds,
|
|
};
|
|
} else {
|
|
_verifiedRoomId = currentRoomId;
|
|
_verifiedRoomRefreshCount = 1;
|
|
_verifiedKnownPacketIds = serverPacketIds;
|
|
}
|
|
_verifiedVisiblePacketIds = visiblePacketIds;
|
|
});
|
|
return result;
|
|
})
|
|
.catchError((_) => rtcProvider.redPacketList)
|
|
.whenComplete(() {
|
|
_refreshingList = false;
|
|
}),
|
|
);
|
|
} catch (_) {
|
|
_refreshingList = false;
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return AnimatedBuilder(
|
|
animation: Listenable.merge([
|
|
RoomRedPacketPendingCache.instance,
|
|
RoomRedPacketOpenDialog.claimedStateNotifier,
|
|
]),
|
|
builder: (context, child) {
|
|
return Consumer2<RtcProvider, RtmProvider>(
|
|
builder: (context, rtcProvider, rtmProvider, child) {
|
|
final snapshot = _RoomRedPacketEntrySnapshot.fromProviders(
|
|
rtcProvider: rtcProvider,
|
|
rtmProvider: rtmProvider,
|
|
verifiedRoomId: _verifiedRoomId,
|
|
verifiedRoomRefreshCount: _verifiedRoomRefreshCount,
|
|
verifiedKnownPacketIds: _verifiedKnownPacketIds,
|
|
verifiedVisiblePacketIds: _verifiedVisiblePacketIds,
|
|
);
|
|
_notifyVisibilityChanged(snapshot.packets.isNotEmpty);
|
|
if (snapshot.packets.isEmpty) {
|
|
return const SizedBox.shrink();
|
|
}
|
|
return SCDebounceWidget(
|
|
onTap: () {
|
|
final freshSnapshot = _RoomRedPacketEntrySnapshot.fromProviders(
|
|
rtcProvider: Provider.of<RtcProvider>(context, listen: false),
|
|
rtmProvider: Provider.of<RtmProvider>(context, listen: false),
|
|
verifiedRoomId: _verifiedRoomId,
|
|
verifiedRoomRefreshCount: _verifiedRoomRefreshCount,
|
|
verifiedKnownPacketIds: _verifiedKnownPacketIds,
|
|
verifiedVisiblePacketIds: _verifiedVisiblePacketIds,
|
|
);
|
|
if (freshSnapshot.packets.isEmpty) {
|
|
return;
|
|
}
|
|
RoomRedPacketOpenDialog.showList(
|
|
context,
|
|
freshSnapshot.packets,
|
|
);
|
|
},
|
|
child: SizedBox(
|
|
width: 46.w,
|
|
height: 46.w,
|
|
child: Stack(
|
|
clipBehavior: Clip.none,
|
|
alignment: Alignment.center,
|
|
children: [
|
|
Image.asset(
|
|
'sc_images/room/red_packet/small_packet_icon.png',
|
|
width: 44.w,
|
|
height: 44.w,
|
|
fit: BoxFit.contain,
|
|
gaplessPlayback: true,
|
|
),
|
|
if (snapshot.packets.length > 1)
|
|
PositionedDirectional(
|
|
top: -2.w,
|
|
end: -2.w,
|
|
child: Container(
|
|
constraints: BoxConstraints(minWidth: 16.w),
|
|
height: 16.w,
|
|
padding: EdgeInsets.symmetric(horizontal: 4.w),
|
|
alignment: Alignment.center,
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xFFFF3D34),
|
|
borderRadius: BorderRadius.circular(8.w),
|
|
border: Border.all(color: Colors.white, width: 1.w),
|
|
),
|
|
child: Text(
|
|
snapshot.packets.length > 99
|
|
? '99+'
|
|
: snapshot.packets.length.toString(),
|
|
textScaler: TextScaler.noScaling,
|
|
maxLines: 1,
|
|
style: TextStyle(
|
|
color: Colors.white,
|
|
fontSize: 9.sp,
|
|
fontWeight: FontWeight.w800,
|
|
decoration: TextDecoration.none,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
class _RoomRedPacketEntrySnapshot {
|
|
const _RoomRedPacketEntrySnapshot(this.packets, this.signature);
|
|
|
|
factory _RoomRedPacketEntrySnapshot.fromProviders({
|
|
required RtcProvider rtcProvider,
|
|
required RtmProvider rtmProvider,
|
|
required String verifiedRoomId,
|
|
required int verifiedRoomRefreshCount,
|
|
required Set<String> verifiedKnownPacketIds,
|
|
required Set<String> verifiedVisiblePacketIds,
|
|
}) {
|
|
final packets = <RoomRedPacketUiData>[];
|
|
final hiddenPacketIds = <String>{};
|
|
final currentRoomId =
|
|
rtcProvider.currenRoom?.roomProfile?.roomProfile?.id?.trim() ?? '';
|
|
final shouldTrustVerifiedAbsence =
|
|
currentRoomId.isNotEmpty &&
|
|
verifiedRoomId == currentRoomId &&
|
|
verifiedRoomRefreshCount >= 2;
|
|
for (final packet in rtcProvider.redPacketList.map(
|
|
RoomRedPacketUiData.fromListRes,
|
|
)) {
|
|
_addVisiblePacket(
|
|
packets,
|
|
packet,
|
|
hiddenPacketIds: hiddenPacketIds,
|
|
authoritative: true,
|
|
);
|
|
}
|
|
for (final packet in RoomRedPacketPendingCache.instance.packetsForRoom(
|
|
currentRoomId,
|
|
)) {
|
|
_addVisiblePacket(
|
|
packets,
|
|
packet,
|
|
hiddenPacketIds: hiddenPacketIds,
|
|
verifiedKnownPacketIds:
|
|
shouldTrustVerifiedAbsence ? verifiedKnownPacketIds : null,
|
|
verifiedVisiblePacketIds:
|
|
shouldTrustVerifiedAbsence ? verifiedVisiblePacketIds : null,
|
|
);
|
|
}
|
|
for (final msg in rtmProvider.roomChatMsgList) {
|
|
if (msg.type != SCRoomMsgType.roomRedPacket) {
|
|
continue;
|
|
}
|
|
_addVisiblePacket(
|
|
packets,
|
|
RoomRedPacketUiData.fromMessagePayload(
|
|
payload: msg.msg,
|
|
fallbackUser: msg.user,
|
|
),
|
|
hiddenPacketIds: hiddenPacketIds,
|
|
verifiedKnownPacketIds:
|
|
shouldTrustVerifiedAbsence ? verifiedKnownPacketIds : null,
|
|
verifiedVisiblePacketIds:
|
|
shouldTrustVerifiedAbsence ? verifiedVisiblePacketIds : null,
|
|
);
|
|
}
|
|
final signature = packets
|
|
.map(
|
|
(item) =>
|
|
'${item.packetId}|${item.status}|${item.remainCount}|${item.grabbed}|${item.claimedAmount}|${item.claimStartTimeMs}|${item.expireTimeMs}',
|
|
)
|
|
.join(';');
|
|
return _RoomRedPacketEntrySnapshot(packets, signature);
|
|
}
|
|
|
|
final List<RoomRedPacketUiData> packets;
|
|
final String signature;
|
|
|
|
@override
|
|
bool operator ==(Object other) {
|
|
if (identical(this, other)) {
|
|
return true;
|
|
}
|
|
return other is _RoomRedPacketEntrySnapshot && other.signature == signature;
|
|
}
|
|
|
|
@override
|
|
int get hashCode => signature.hashCode;
|
|
}
|
|
|
|
void _addVisiblePacket(
|
|
List<RoomRedPacketUiData> packets,
|
|
RoomRedPacketUiData packet, {
|
|
Set<String>? hiddenPacketIds,
|
|
Set<String>? verifiedKnownPacketIds,
|
|
Set<String>? verifiedVisiblePacketIds,
|
|
bool authoritative = false,
|
|
}) {
|
|
final normalizedPacket = RoomRedPacketOpenDialog.withLocalClaimState(packet);
|
|
final packetId = normalizedPacket.packetId.trim();
|
|
if (packetId.isNotEmpty &&
|
|
RoomRedPacketOpenDialog.isClaimedLocally(packetId)) {
|
|
if (authoritative) {
|
|
hiddenPacketIds?.add(packetId);
|
|
}
|
|
return;
|
|
}
|
|
if (packetId.isNotEmpty &&
|
|
RoomRedPacketOpenDialog.isUnavailableLocally(packetId)) {
|
|
if (authoritative) {
|
|
hiddenPacketIds?.add(packetId);
|
|
}
|
|
return;
|
|
}
|
|
if (!authoritative &&
|
|
packetId.isNotEmpty &&
|
|
(hiddenPacketIds?.contains(packetId) ?? false)) {
|
|
return;
|
|
}
|
|
if (!authoritative &&
|
|
packetId.isNotEmpty &&
|
|
verifiedKnownPacketIds != null &&
|
|
verifiedVisiblePacketIds != null &&
|
|
verifiedKnownPacketIds.contains(packetId) &&
|
|
!verifiedVisiblePacketIds.contains(packetId)) {
|
|
return;
|
|
}
|
|
if (!_shouldShowRoomRedPacketEntry(normalizedPacket)) {
|
|
if (authoritative && packetId.isNotEmpty) {
|
|
hiddenPacketIds?.add(packetId);
|
|
}
|
|
return;
|
|
}
|
|
if (packetId.isNotEmpty &&
|
|
packets.any((item) => item.packetId.trim() == packetId)) {
|
|
final existingIndex = packets.indexWhere(
|
|
(item) => item.packetId.trim() == packetId,
|
|
);
|
|
packets[existingIndex] = packets[existingIndex].withFallbackProfile(
|
|
normalizedPacket,
|
|
);
|
|
return;
|
|
}
|
|
packets.add(normalizedPacket);
|
|
}
|
|
|
|
bool _shouldShowRoomRedPacketEntry(RoomRedPacketUiData item) {
|
|
return !item.grabbed && !item.isExpired && !item.isSoldOut;
|
|
}
|