buf fxi
This commit is contained in:
parent
27f4e75cb8
commit
44e10028b9
@ -21,7 +21,6 @@ import 'package:yumi/shared/data_sources/sources/remote/net/api.dart';
|
|||||||
import 'package:yumi/shared/data_sources/sources/repositories/sc_room_repository_imp.dart';
|
import 'package:yumi/shared/data_sources/sources/repositories/sc_room_repository_imp.dart';
|
||||||
import 'package:yumi/shared/business_logic/models/res/login_res.dart';
|
import 'package:yumi/shared/business_logic/models/res/login_res.dart';
|
||||||
import 'package:yumi/shared/tools/sc_cp_gift_relation_utils.dart';
|
import 'package:yumi/shared/tools/sc_cp_gift_relation_utils.dart';
|
||||||
import 'package:yumi/shared/tools/sc_cp_relation_notice_utils.dart';
|
|
||||||
import 'package:yumi/main.dart';
|
import 'package:yumi/main.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:yumi/app/constants/sc_room_msg_type.dart';
|
import 'package:yumi/app/constants/sc_room_msg_type.dart';
|
||||||
@ -35,7 +34,6 @@ import 'package:yumi/services/audio/rtc_manager.dart';
|
|||||||
import 'package:yumi/services/audio/rtm_manager.dart';
|
import 'package:yumi/services/audio/rtm_manager.dart';
|
||||||
import 'package:yumi/services/auth/user_profile_manager.dart';
|
import 'package:yumi/services/auth/user_profile_manager.dart';
|
||||||
import 'package:yumi/ui_kit/widgets/gift/sc_gift_combo_send_button.dart';
|
import 'package:yumi/ui_kit/widgets/gift/sc_gift_combo_send_button.dart';
|
||||||
import 'package:yumi/ui_kit/widgets/room/cp/room_cp_invite_dialog.dart';
|
|
||||||
import 'package:yumi/ui_kit/widgets/room/room_msg_item.dart';
|
import 'package:yumi/ui_kit/widgets/room/room_msg_item.dart';
|
||||||
import 'package:yumi/modules/gift/gift_tab_page.dart';
|
import 'package:yumi/modules/gift/gift_tab_page.dart';
|
||||||
import 'package:yumi/modules/gift/cp_rights/cp_rights_guide_page.dart';
|
import 'package:yumi/modules/gift/cp_rights/cp_rights_guide_page.dart';
|
||||||
@ -1564,7 +1562,6 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
|||||||
}
|
}
|
||||||
await _refreshGiftBackpack();
|
await _refreshGiftBackpack();
|
||||||
_refreshCpStateAfterGift(request, profileManager);
|
_refreshCpStateAfterGift(request, profileManager);
|
||||||
_showCpGiftWaitingDialog(request);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1629,7 +1626,6 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
|||||||
}
|
}
|
||||||
profileManager?.updateBalance(result);
|
profileManager?.updateBalance(result);
|
||||||
_refreshCpStateAfterGift(request, profileManager);
|
_refreshCpStateAfterGift(request, profileManager);
|
||||||
_showCpGiftWaitingDialog(request);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
final errorMessage =
|
final errorMessage =
|
||||||
request.isBackpackGiftRequest && _isBackpackQuantityError(e)
|
request.isBackpackGiftRequest && _isBackpackQuantityError(e)
|
||||||
@ -1672,81 +1668,6 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
|||||||
unawaited(profileManager?.refreshLoadedCpProfiles());
|
unawaited(profileManager?.refreshLoadedCpProfiles());
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showCpGiftWaitingDialog(_GiftSendRequest request) {
|
|
||||||
if (!_isCpGift(request.gift)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (request.isSelfRecipientRequest) {
|
|
||||||
_giftFxLog(
|
|
||||||
'skip local CP waiting dialog reason=self_recipient '
|
|
||||||
'giftId=${request.gift.id} '
|
|
||||||
'acceptUserIds=${request.acceptUserIds.join(",")}',
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final context = navigatorKey.currentState?.context;
|
|
||||||
final currentProfile = AccountStorage().getCurrentUser()?.userProfile;
|
|
||||||
final receiverProfile =
|
|
||||||
request.acceptUsers.isNotEmpty ? request.acceptUsers.first.user : null;
|
|
||||||
if (context == null || currentProfile == null || receiverProfile == null) {
|
|
||||||
_giftFxLog(
|
|
||||||
'skip local CP waiting dialog reason=missing_profile '
|
|
||||||
'giftId=${request.gift.id} '
|
|
||||||
'acceptUserIds=${request.acceptUserIds.join(",")}',
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final relationType = scNormalizeCpRelationType(request.cpRelationType);
|
|
||||||
_giftFxLog(
|
|
||||||
'show local CP waiting dialog '
|
|
||||||
'giftId=${request.gift.id} '
|
|
||||||
'relationType=$relationType '
|
|
||||||
'receiverId=${receiverProfile.id}',
|
|
||||||
);
|
|
||||||
unawaited(
|
|
||||||
RoomCpInviteDialog.show(
|
|
||||||
context,
|
|
||||||
inviter: RoomCpInviteDialogUser(
|
|
||||||
name: _cpInviteDisplayName(currentProfile),
|
|
||||||
avatarUrl: currentProfile.userAvatar ?? "",
|
|
||||||
headdress: currentProfile.getHeaddress()?.sourceUrl ?? "",
|
|
||||||
headdressCover: currentProfile.getHeaddress()?.cover ?? "",
|
|
||||||
),
|
|
||||||
receiver: RoomCpInviteDialogUser(
|
|
||||||
name: _cpInviteDisplayName(receiverProfile),
|
|
||||||
avatarUrl: receiverProfile.userAvatar ?? "",
|
|
||||||
headdress: receiverProfile.getHeaddress()?.sourceUrl ?? "",
|
|
||||||
headdressCover: receiverProfile.getHeaddress()?.cover ?? "",
|
|
||||||
),
|
|
||||||
style: RoomCpInviteDialogStyle.waiting,
|
|
||||||
countdownSeconds: 86399,
|
|
||||||
descriptionText: _cpInviteWaitingDescriptionText(relationType),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
String _cpInviteDisplayName(SocialChatUserProfile profile) {
|
|
||||||
for (final value in [profile.userNickname, profile.account, profile.id]) {
|
|
||||||
final text = value?.trim() ?? "";
|
|
||||||
if (text.isNotEmpty) {
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "User";
|
|
||||||
}
|
|
||||||
|
|
||||||
String _cpInviteWaitingDescriptionText(String relationType) {
|
|
||||||
switch (scNormalizeCpRelationType(relationType)) {
|
|
||||||
case "BROTHER":
|
|
||||||
return "We invite you to become Brother";
|
|
||||||
case "SISTERS":
|
|
||||||
return "We invite you to become Sister";
|
|
||||||
case "CP":
|
|
||||||
default:
|
|
||||||
return "We invite you to become Couple";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<MicRes>> _giveBackpackGiftToRecipients(
|
Future<List<MicRes>> _giveBackpackGiftToRecipients(
|
||||||
SCChatRoomRepository repository,
|
SCChatRoomRepository repository,
|
||||||
_GiftSendRequest request,
|
_GiftSendRequest request,
|
||||||
|
|||||||
@ -1383,14 +1383,20 @@ class _PersonDetailPageState extends State<PersonDetailPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<CPRes> _profileCloseFriendRelations(SocialChatUserProfile? profile) {
|
List<CPRes> _profileCloseFriendRelations(SocialChatUserProfile? profile) {
|
||||||
return (profile?.closeFriendList ?? const <CPRes>[])
|
final brothers = <CPRes>[];
|
||||||
.where((relation) {
|
final sisters = <CPRes>[];
|
||||||
final type = _normalizeProfileRelationType(relation.relationType);
|
for (final relation in profile?.closeFriendList ?? const <CPRes>[]) {
|
||||||
return (type == "BROTHER" || type == "SISTERS") &&
|
if (!_profileRelationHasPartner(profile, relation)) {
|
||||||
_profileRelationHasPartner(profile, relation);
|
continue;
|
||||||
})
|
}
|
||||||
.take(6)
|
final type = _normalizeProfileRelationType(relation.relationType);
|
||||||
.toList();
|
if (type == "BROTHER" && brothers.length < 3) {
|
||||||
|
brothers.add(relation);
|
||||||
|
} else if (type == "SISTERS" && sisters.length < 3) {
|
||||||
|
sisters.add(relation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return <CPRes>[...brothers, ...sisters];
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _profileRelationHasPartner(
|
bool _profileRelationHasPartner(
|
||||||
@ -1611,6 +1617,14 @@ class _PersonDetailPageState extends State<PersonDetailPage> {
|
|||||||
if (!mounted) {
|
if (!mounted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
Provider.of<RtcProvider?>(
|
||||||
|
context,
|
||||||
|
listen: false,
|
||||||
|
)?.invalidateRoomMicRelationBetween(
|
||||||
|
userId: payload.leftUserId,
|
||||||
|
peerUserId: partnerUserId,
|
||||||
|
relationType: payload.relationType,
|
||||||
|
);
|
||||||
await Provider.of<SocialChatUserProfileManager>(
|
await Provider.of<SocialChatUserProfileManager>(
|
||||||
context,
|
context,
|
||||||
listen: false,
|
listen: false,
|
||||||
@ -2589,6 +2603,20 @@ class _PersonDetailPageState extends State<PersonDetailPage> {
|
|||||||
relationType: cpRelation?.relationType ?? "CP",
|
relationType: cpRelation?.relationType ?? "CP",
|
||||||
)
|
)
|
||||||
.then((result) {
|
.then((result) {
|
||||||
|
if (mounted) {
|
||||||
|
Provider.of<RtcProvider?>(
|
||||||
|
context,
|
||||||
|
listen: false,
|
||||||
|
)?.invalidateRoomMicRelationBetween(
|
||||||
|
userId:
|
||||||
|
AccountStorage()
|
||||||
|
.getCurrentUser()
|
||||||
|
?.userProfile
|
||||||
|
?.id,
|
||||||
|
peerUserId: ref.userProfile?.id,
|
||||||
|
relationType: cpRelation?.relationType ?? "CP",
|
||||||
|
);
|
||||||
|
}
|
||||||
ref.refreshLoadedCpProfiles();
|
ref.refreshLoadedCpProfiles();
|
||||||
SmartDialog.dismiss(tag: "showPartWaysDialog");
|
SmartDialog.dismiss(tag: "showPartWaysDialog");
|
||||||
SCTts.show(
|
SCTts.show(
|
||||||
|
|||||||
@ -20,9 +20,11 @@ class RoomRtcEnterRoomConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class RoomRtcAnchorConfig {
|
class RoomRtcAnchorConfig {
|
||||||
const RoomRtcAnchorConfig({required this.muted});
|
const RoomRtcAnchorConfig({required this.muted, bool? publishMuted})
|
||||||
|
: publishMuted = publishMuted ?? muted;
|
||||||
|
|
||||||
final bool muted;
|
final bool muted;
|
||||||
|
final bool publishMuted;
|
||||||
}
|
}
|
||||||
|
|
||||||
class RoomRtcAudioMixingConfig {
|
class RoomRtcAudioMixingConfig {
|
||||||
|
|||||||
@ -10,7 +10,6 @@ import 'package:yumi/shared/tools/sc_permission_utils.dart';
|
|||||||
import 'package:yumi/shared/tools/sc_room_utils.dart';
|
import 'package:yumi/shared/tools/sc_room_utils.dart';
|
||||||
import 'package:yumi/shared/tools/sc_room_top_layer_guard.dart';
|
import 'package:yumi/shared/tools/sc_room_top_layer_guard.dart';
|
||||||
import 'package:yumi/shared/tools/sc_cp_relation_notice_utils.dart';
|
import 'package:yumi/shared/tools/sc_cp_relation_notice_utils.dart';
|
||||||
import 'package:yumi/shared/tools/sc_room_rocket_reward_popup_ack.dart';
|
|
||||||
import 'package:yumi/shared/data_sources/models/message/room_rocket_launch_broadcast_message.dart';
|
import 'package:yumi/shared/data_sources/models/message/room_rocket_launch_broadcast_message.dart';
|
||||||
import 'package:yumi/shared/data_sources/models/message/sc_floating_message.dart';
|
import 'package:yumi/shared/data_sources/models/message/sc_floating_message.dart';
|
||||||
import 'package:yumi/shared/data_sources/sources/local/floating_screen_manager.dart';
|
import 'package:yumi/shared/data_sources/sources/local/floating_screen_manager.dart';
|
||||||
@ -63,7 +62,6 @@ import '../../ui_kit/components/sc_float_ichart.dart';
|
|||||||
import '../../ui_kit/widgets/room/rocket/room_rocket_asset_preloader.dart';
|
import '../../ui_kit/widgets/room/rocket/room_rocket_asset_preloader.dart';
|
||||||
import '../../ui_kit/widgets/room/rocket/room_rocket_pag_effect_overlay.dart';
|
import '../../ui_kit/widgets/room/rocket/room_rocket_pag_effect_overlay.dart';
|
||||||
import '../../ui_kit/widgets/room/rocket/room_rocket_api_mapper.dart';
|
import '../../ui_kit/widgets/room/rocket/room_rocket_api_mapper.dart';
|
||||||
import '../../ui_kit/widgets/room/rocket/room_rocket_reward_dialog_loader.dart';
|
|
||||||
|
|
||||||
typedef OnSoundVoiceChange = Function(num index, int volum);
|
typedef OnSoundVoiceChange = Function(num index, int volum);
|
||||||
typedef RtcProvider = RealTimeCommunicationManager;
|
typedef RtcProvider = RealTimeCommunicationManager;
|
||||||
@ -321,8 +319,21 @@ class _RoomMicRelationProfileCache {
|
|||||||
bool isExpired(DateTime now, Duration ttl) => now.difference(loadedAt) > ttl;
|
bool isExpired(DateTime now, Duration ttl) => now.difference(loadedAt) > ttl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _RecentMicChangeSeatSnapshot {
|
||||||
|
const _RecentMicChangeSeatSnapshot({
|
||||||
|
required this.mic,
|
||||||
|
required this.expiresAtMs,
|
||||||
|
});
|
||||||
|
|
||||||
|
final MicRes mic;
|
||||||
|
final int expiresAtMs;
|
||||||
|
|
||||||
|
String get userId => (mic.user?.id ?? "").trim();
|
||||||
|
|
||||||
|
bool isExpired(int nowMs) => expiresAtMs <= nowMs;
|
||||||
|
}
|
||||||
|
|
||||||
class RealTimeCommunicationManager extends ChangeNotifier {
|
class RealTimeCommunicationManager extends ChangeNotifier {
|
||||||
static const String _roomRocketRewardDialogTag = 'showRoomRocketRewardDialog';
|
|
||||||
static const String _roomRocketPagLaunchDialogTag =
|
static const String _roomRocketPagLaunchDialogTag =
|
||||||
'showRoomRocketPagLaunchDialog';
|
'showRoomRocketPagLaunchDialog';
|
||||||
static const List<Duration> _roomRocketGiftRefreshDelays = [
|
static const List<Duration> _roomRocketGiftRefreshDelays = [
|
||||||
@ -348,10 +359,12 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
'SISTERS',
|
'SISTERS',
|
||||||
];
|
];
|
||||||
static const Duration _roomMicRelationCacheTtl = Duration(minutes: 5);
|
static const Duration _roomMicRelationCacheTtl = Duration(minutes: 5);
|
||||||
|
static const Duration _roomMicRelationDismissedPairTtl = Duration(minutes: 5);
|
||||||
static const int _roomPasswordNotTrueErrorCode = 9005;
|
static const int _roomPasswordNotTrueErrorCode = 9005;
|
||||||
|
|
||||||
static const Duration _micListPollingInterval = Duration(seconds: 2);
|
static const Duration _micListPollingInterval = Duration(seconds: 2);
|
||||||
static const Duration _onlineUsersPollingInterval = Duration(seconds: 3);
|
static const Duration _onlineUsersPollingInterval = Duration(seconds: 3);
|
||||||
|
static const Duration _micChangeSnapshotGracePeriod = Duration(seconds: 4);
|
||||||
static const int _exitNetworkRetryLimit = 3;
|
static const int _exitNetworkRetryLimit = 3;
|
||||||
static const Duration _exitNetworkRetryDelay = Duration(milliseconds: 500);
|
static const Duration _exitNetworkRetryDelay = Duration(milliseconds: 500);
|
||||||
static const Duration _selfMicStateGracePeriod = Duration(seconds: 4);
|
static const Duration _selfMicStateGracePeriod = Duration(seconds: 4);
|
||||||
@ -412,6 +425,8 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
bool _pendingMicListRefreshNotifyIfUnchanged = false;
|
bool _pendingMicListRefreshNotifyIfUnchanged = false;
|
||||||
Timer? _deferredMicListRefreshTimer;
|
Timer? _deferredMicListRefreshTimer;
|
||||||
int _lastMicListRefreshStartedAtMs = 0;
|
int _lastMicListRefreshStartedAtMs = 0;
|
||||||
|
final Map<num, _RecentMicChangeSeatSnapshot> _recentMicChangeSeatSnapshots =
|
||||||
|
<num, _RecentMicChangeSeatSnapshot>{};
|
||||||
num? _preferredSelfMicIndex;
|
num? _preferredSelfMicIndex;
|
||||||
num? _pendingSelfMicSourceIndex;
|
num? _pendingSelfMicSourceIndex;
|
||||||
int? _pendingSelfMicSwitchGuardUntilMs;
|
int? _pendingSelfMicSwitchGuardUntilMs;
|
||||||
@ -439,6 +454,8 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
final Map<String, _RoomMicRelationProfileCache> _roomMicRelationCache =
|
final Map<String, _RoomMicRelationProfileCache> _roomMicRelationCache =
|
||||||
<String, _RoomMicRelationProfileCache>{};
|
<String, _RoomMicRelationProfileCache>{};
|
||||||
final Set<String> _roomMicRelationLoadingUserIds = <String>{};
|
final Set<String> _roomMicRelationLoadingUserIds = <String>{};
|
||||||
|
final Map<String, DateTime> _roomMicRelationDismissedPairUntil =
|
||||||
|
<String, DateTime>{};
|
||||||
final Set<String> _roomMusicPublishingUserIds = <String>{};
|
final Set<String> _roomMusicPublishingUserIds = <String>{};
|
||||||
final Map<String, Timer> _roomMusicPublishingClearTimers = <String, Timer>{};
|
final Map<String, Timer> _roomMusicPublishingClearTimers = <String, Timer>{};
|
||||||
|
|
||||||
@ -490,6 +507,18 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
_roomMusicPublishingUserIds.contains(normalizedUserId);
|
_roomMusicPublishingUserIds.contains(normalizedUserId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool get _currentUserPublishingRoomMusic {
|
||||||
|
final currentUserId =
|
||||||
|
(AccountStorage().getCurrentUser()?.userProfile?.id ?? "").trim();
|
||||||
|
return isUserPublishingRoomMusic(currentUserId);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _isCurrentUserId(String userId) {
|
||||||
|
final currentUserId =
|
||||||
|
(AccountStorage().getCurrentUser()?.userProfile?.id ?? "").trim();
|
||||||
|
return currentUserId.isNotEmpty && currentUserId == userId.trim();
|
||||||
|
}
|
||||||
|
|
||||||
///房间红包列表
|
///房间红包列表
|
||||||
List<SCRoomRedPacketListRes> redPacketList = [];
|
List<SCRoomRedPacketListRes> redPacketList = [];
|
||||||
|
|
||||||
@ -574,6 +603,9 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
final roomId = (currenRoom?.roomProfile?.roomProfile?.id ?? "").trim();
|
final roomId = (currenRoom?.roomProfile?.roomProfile?.id ?? "").trim();
|
||||||
if (roomId.isNotEmpty) {
|
if (roomId.isNotEmpty) {
|
||||||
_scheduleRoomRocketEntryReadyFallback(roomId);
|
_scheduleRoomRocketEntryReadyFallback(roomId);
|
||||||
|
if (_roomStartupStatus == RoomStartupStatus.ready) {
|
||||||
|
_loadRoomRocketRewardPopups(roomId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
@ -772,6 +804,16 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
normalizedMessage.contains('the path was not found');
|
normalizedMessage.contains('the path was not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool _isInvalidKickOffMicPrecheckError(Object error) {
|
||||||
|
final normalizedMessage = _errorMessageFrom(error).toLowerCase();
|
||||||
|
return (normalizedMessage.contains('parameter') &&
|
||||||
|
(normalizedMessage.contains('incorrect') ||
|
||||||
|
normalizedMessage.contains('incomplete'))) ||
|
||||||
|
(normalizedMessage.contains('参数') &&
|
||||||
|
(normalizedMessage.contains('错误') ||
|
||||||
|
normalizedMessage.contains('不完整')));
|
||||||
|
}
|
||||||
|
|
||||||
String _errorMessageFrom(Object error) {
|
String _errorMessageFrom(Object error) {
|
||||||
if (error is DioException) {
|
if (error is DioException) {
|
||||||
final responseData = error.response?.data;
|
final responseData = error.response?.data;
|
||||||
@ -1037,6 +1079,144 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
return _stabilizeSelfMicSnapshot(completedMap, previousMap: previousMap);
|
return _stabilizeSelfMicSnapshot(completedMap, previousMap: previousMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _rememberMicChangeSnapshot(List<MicRes> mics) {
|
||||||
|
if (mics.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final nowMs = DateTime.now().millisecondsSinceEpoch;
|
||||||
|
_pruneRecentMicChangeSeatSnapshots(nowMs);
|
||||||
|
final expiresAtMs = nowMs + _micChangeSnapshotGracePeriod.inMilliseconds;
|
||||||
|
for (final mic in mics) {
|
||||||
|
final micIndex = mic.micIndex;
|
||||||
|
if (micIndex == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
_recentMicChangeSeatSnapshots[micIndex] = _RecentMicChangeSeatSnapshot(
|
||||||
|
mic: _mergeKnownVipIntoMic(mic).copyWith(micIndex: micIndex),
|
||||||
|
expiresAtMs: expiresAtMs,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _pruneRecentMicChangeSeatSnapshots([int? nowMs]) {
|
||||||
|
if (_recentMicChangeSeatSnapshots.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final resolvedNowMs = nowMs ?? DateTime.now().millisecondsSinceEpoch;
|
||||||
|
_recentMicChangeSeatSnapshots.removeWhere(
|
||||||
|
(_, snapshot) => snapshot.isExpired(resolvedNowMs),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<num, MicRes> _stabilizeMicListSnapshotWithRecentMicChange(
|
||||||
|
Map<num, MicRes> nextMap,
|
||||||
|
) {
|
||||||
|
final nowMs = DateTime.now().millisecondsSinceEpoch;
|
||||||
|
_pruneRecentMicChangeSeatSnapshots(nowMs);
|
||||||
|
if (_recentMicChangeSeatSnapshots.isEmpty) {
|
||||||
|
return nextMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<num, MicRes>? stabilizedMap;
|
||||||
|
final confirmedSeatIndexes = <num>[];
|
||||||
|
for (final entry in _recentMicChangeSeatSnapshots.entries) {
|
||||||
|
final seatIndex = entry.key;
|
||||||
|
final recentMic = entry.value.mic;
|
||||||
|
final recentUserId = entry.value.userId;
|
||||||
|
final currentMap = stabilizedMap ?? nextMap;
|
||||||
|
final incomingMic = currentMap[seatIndex];
|
||||||
|
final incomingUserId = (incomingMic?.user?.id ?? "").trim();
|
||||||
|
|
||||||
|
if (recentUserId.isEmpty) {
|
||||||
|
if (incomingUserId.isEmpty) {
|
||||||
|
confirmedSeatIndexes.add(seatIndex);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
stabilizedMap ??= Map<num, MicRes>.from(nextMap);
|
||||||
|
stabilizedMap[seatIndex] = _micChangeAuthoritativeSeat(
|
||||||
|
incomingMic: incomingMic,
|
||||||
|
recentMic: recentMic,
|
||||||
|
seatIndex: seatIndex,
|
||||||
|
clearUser: true,
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (incomingUserId == recentUserId) {
|
||||||
|
confirmedSeatIndexes.add(seatIndex);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
stabilizedMap ??= Map<num, MicRes>.from(nextMap);
|
||||||
|
for (final otherEntry in currentMap.entries) {
|
||||||
|
if (otherEntry.key == seatIndex) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ((otherEntry.value.user?.id ?? "").trim() != recentUserId) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
stabilizedMap[otherEntry.key] = otherEntry.value.copyWith(
|
||||||
|
clearUser: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
stabilizedMap[seatIndex] = _micChangeAuthoritativeSeat(
|
||||||
|
incomingMic: incomingMic,
|
||||||
|
recentMic: recentMic,
|
||||||
|
seatIndex: seatIndex,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final seatIndex in confirmedSeatIndexes) {
|
||||||
|
_recentMicChangeSeatSnapshots.remove(seatIndex);
|
||||||
|
}
|
||||||
|
return stabilizedMap ?? nextMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
MicRes _micChangeAuthoritativeSeat({
|
||||||
|
required MicRes? incomingMic,
|
||||||
|
required MicRes recentMic,
|
||||||
|
required num seatIndex,
|
||||||
|
bool clearUser = false,
|
||||||
|
}) {
|
||||||
|
final baseMic =
|
||||||
|
incomingMic ??
|
||||||
|
MicRes(
|
||||||
|
roomId: currenRoom?.roomProfile?.roomProfile?.id,
|
||||||
|
micIndex: seatIndex,
|
||||||
|
);
|
||||||
|
return baseMic.copyWith(
|
||||||
|
roomId: recentMic.roomId,
|
||||||
|
micIndex: recentMic.micIndex ?? seatIndex,
|
||||||
|
micLock: recentMic.micLock,
|
||||||
|
micMute: recentMic.micMute,
|
||||||
|
user: recentMic.user,
|
||||||
|
clearUser: clearUser,
|
||||||
|
roomToken: recentMic.roomToken,
|
||||||
|
emojiPath: recentMic.emojiPath,
|
||||||
|
type: recentMic.type,
|
||||||
|
number: recentMic.number,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _applyMicListSnapshot(
|
||||||
|
List<MicRes> roomWheatList, {
|
||||||
|
bool notifyIfUnchanged = true,
|
||||||
|
}) {
|
||||||
|
final nextMap = _buildMicMap(roomWheatList);
|
||||||
|
final stabilizedNextMap = _stabilizeMicListSnapshotWithRecentMicChange(
|
||||||
|
nextMap,
|
||||||
|
);
|
||||||
|
_applyMicSnapshot(stabilizedNextMap, notifyIfUnchanged: notifyIfUnchanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
@visibleForTesting
|
||||||
|
void debugApplyMicListSnapshot(
|
||||||
|
List<MicRes> roomWheatList, {
|
||||||
|
bool notifyIfUnchanged = true,
|
||||||
|
}) {
|
||||||
|
_applyMicListSnapshot(roomWheatList, notifyIfUnchanged: notifyIfUnchanged);
|
||||||
|
}
|
||||||
|
|
||||||
void _startSelfMicSwitchGuard({
|
void _startSelfMicSwitchGuard({
|
||||||
required num sourceIndex,
|
required num sourceIndex,
|
||||||
required num targetIndex,
|
required num targetIndex,
|
||||||
@ -1417,16 +1597,26 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final shouldPublishLocalAudio = role == _RoomRtcLocalAudioRole.anchor;
|
final shouldPublishLocalAudio = role == _RoomRtcLocalAudioRole.anchor;
|
||||||
final recordingSignalVolume = shouldPublishLocalAudio && !muted ? 100 : 0;
|
final keepAudioPublishing =
|
||||||
|
shouldPublishLocalAudio && muted && _currentUserPublishingRoomMusic;
|
||||||
|
final localAudioMuted =
|
||||||
|
!shouldPublishLocalAudio || (muted && !keepAudioPublishing);
|
||||||
|
final recordingSignalMuted = !shouldPublishLocalAudio || muted;
|
||||||
|
final recordingSignalVolume = recordingSignalMuted ? 0 : 100;
|
||||||
if (_lastAppliedLocalAudioRole == role &&
|
if (_lastAppliedLocalAudioRole == role &&
|
||||||
_lastAppliedLocalAudioMuted == !shouldPublishLocalAudio &&
|
_lastAppliedLocalAudioMuted == localAudioMuted &&
|
||||||
_lastAppliedRecordingSignalMuted == !shouldPublishLocalAudio &&
|
_lastAppliedRecordingSignalMuted == recordingSignalMuted &&
|
||||||
_lastAppliedRecordingSignalVolume == recordingSignalVolume) {
|
_lastAppliedRecordingSignalVolume == recordingSignalVolume) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shouldPublishLocalAudio) {
|
if (shouldPublishLocalAudio) {
|
||||||
unawaited(_switchRoomRtcToBroadcaster(muted: muted));
|
unawaited(
|
||||||
|
_switchRoomRtcToBroadcaster(
|
||||||
|
muted: muted,
|
||||||
|
keepAudioPublishing: keepAudioPublishing,
|
||||||
|
),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
unawaited(_switchRoomRtcToAudience());
|
unawaited(_switchRoomRtcToAudience());
|
||||||
}
|
}
|
||||||
@ -1449,8 +1639,13 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
Map<num, MicRes> nextMap, {
|
Map<num, MicRes> nextMap, {
|
||||||
bool notifyIfUnchanged = true,
|
bool notifyIfUnchanged = true,
|
||||||
}) {
|
}) {
|
||||||
final changed = !_sameMicMaps(roomWheatMap, nextMap);
|
final sanitizedNextMap = _withoutLocallyDismissedRoomMicRelationsFromMicMap(
|
||||||
roomWheatMap = nextMap;
|
nextMap,
|
||||||
|
);
|
||||||
|
final changed =
|
||||||
|
!identical(sanitizedNextMap, nextMap) ||
|
||||||
|
!_sameMicMaps(roomWheatMap, sanitizedNextMap);
|
||||||
|
roomWheatMap = sanitizedNextMap;
|
||||||
final relationCacheChanged = _mergeCachedRoomMicRelationProfiles();
|
final relationCacheChanged = _mergeCachedRoomMicRelationProfiles();
|
||||||
_scheduleRoomMicRelationHydration();
|
_scheduleRoomMicRelationHydration();
|
||||||
_syncSelfMicRuntimeState();
|
_syncSelfMicRuntimeState();
|
||||||
@ -1463,6 +1658,58 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void invalidateRoomMicRelationBetween({
|
||||||
|
required String? userId,
|
||||||
|
required String? peerUserId,
|
||||||
|
String? relationType,
|
||||||
|
}) {
|
||||||
|
final currentUserId =
|
||||||
|
(AccountStorage().getCurrentUser()?.userProfile?.id ?? "").trim();
|
||||||
|
final leftUserId =
|
||||||
|
(userId ?? "").trim().isNotEmpty ? userId!.trim() : currentUserId;
|
||||||
|
final rightUserId = (peerUserId ?? "").trim();
|
||||||
|
if (leftUserId.isEmpty ||
|
||||||
|
rightUserId.isEmpty ||
|
||||||
|
leftUserId == rightUserId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final now = DateTime.now();
|
||||||
|
_pruneRoomMicRelationDismissedPairs(now);
|
||||||
|
final normalizedType = scNormalizeCpRelationType(relationType);
|
||||||
|
final dismissedUntil = now.add(_roomMicRelationDismissedPairTtl);
|
||||||
|
final leftKeys = _roomMicRelationIdentityKeysForUserId(leftUserId);
|
||||||
|
final rightKeys = _roomMicRelationIdentityKeysForUserId(rightUserId);
|
||||||
|
for (final leftKey in leftKeys) {
|
||||||
|
for (final rightKey in rightKeys) {
|
||||||
|
_roomMicRelationDismissedPairUntil[_roomMicRelationDismissedPairKey(
|
||||||
|
leftKey,
|
||||||
|
rightKey,
|
||||||
|
normalizedType,
|
||||||
|
)] =
|
||||||
|
dismissedUntil;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final dismissedUserIds = <String>{leftUserId, rightUserId};
|
||||||
|
_roomMicRelationCache.removeWhere(
|
||||||
|
(cacheUserId, _) => dismissedUserIds.contains(cacheUserId),
|
||||||
|
);
|
||||||
|
_roomMicRelationLoadingUserIds.removeWhere(dismissedUserIds.contains);
|
||||||
|
|
||||||
|
final nextMap = _withoutLocallyDismissedRoomMicRelationsFromMicMap(
|
||||||
|
roomWheatMap,
|
||||||
|
);
|
||||||
|
if (!identical(nextMap, roomWheatMap)) {
|
||||||
|
roomWheatMap = nextMap;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
requestMicrophoneListRefresh(
|
||||||
|
notifyIfUnchanged: false,
|
||||||
|
minInterval: const Duration(milliseconds: 350),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
bool _mergeCachedRoomMicRelationProfiles() {
|
bool _mergeCachedRoomMicRelationProfiles() {
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
_roomMicRelationCache.removeWhere(
|
_roomMicRelationCache.removeWhere(
|
||||||
@ -1561,6 +1808,9 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
levelText:
|
levelText:
|
||||||
pair.levelText ?? _roomMicRelationLevelTextFromCabin(cabin),
|
pair.levelText ?? _roomMicRelationLevelTextFromCabin(cabin),
|
||||||
);
|
);
|
||||||
|
if (_isRoomMicRelationLocallyDismissed(enrichedPair)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (normalizedType == "CP") {
|
if (normalizedType == "CP") {
|
||||||
cpPairs.add(enrichedPair);
|
cpPairs.add(enrichedPair);
|
||||||
} else {
|
} else {
|
||||||
@ -1711,6 +1961,126 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
return user?.id?.trim() ?? "";
|
return user?.id?.trim() ?? "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Map<num, MicRes> _withoutLocallyDismissedRoomMicRelationsFromMicMap(
|
||||||
|
Map<num, MicRes> sourceMap,
|
||||||
|
) {
|
||||||
|
if (_roomMicRelationDismissedPairUntil.isEmpty || sourceMap.isEmpty) {
|
||||||
|
return sourceMap;
|
||||||
|
}
|
||||||
|
_pruneRoomMicRelationDismissedPairs(DateTime.now());
|
||||||
|
if (_roomMicRelationDismissedPairUntil.isEmpty) {
|
||||||
|
return sourceMap;
|
||||||
|
}
|
||||||
|
var changed = false;
|
||||||
|
final nextMap = sourceMap.map((index, mic) {
|
||||||
|
final user = mic.user;
|
||||||
|
if (user == null) {
|
||||||
|
return MapEntry(index, mic);
|
||||||
|
}
|
||||||
|
final nextUser = _withoutLocallyDismissedRoomMicRelations(user);
|
||||||
|
if (identical(nextUser, user)) {
|
||||||
|
return MapEntry(index, mic);
|
||||||
|
}
|
||||||
|
changed = true;
|
||||||
|
return MapEntry(index, mic.copyWith(user: nextUser));
|
||||||
|
});
|
||||||
|
return changed ? nextMap : sourceMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
SocialChatUserProfile _withoutLocallyDismissedRoomMicRelations(
|
||||||
|
SocialChatUserProfile user,
|
||||||
|
) {
|
||||||
|
final nextCpList = _withoutLocallyDismissedRoomMicRelationList(user.cpList);
|
||||||
|
final nextCloseFriendList = _withoutLocallyDismissedRoomMicRelationList(
|
||||||
|
user.closeFriendList,
|
||||||
|
);
|
||||||
|
if (identical(nextCpList, user.cpList) &&
|
||||||
|
identical(nextCloseFriendList, user.closeFriendList)) {
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
final effectiveCpList = nextCpList ?? user.cpList ?? const <CPRes>[];
|
||||||
|
return user.copyWith(
|
||||||
|
cpList: nextCpList,
|
||||||
|
closeFriendList: nextCloseFriendList,
|
||||||
|
isCpRelation: effectiveCpList.isNotEmpty,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<CPRes>? _withoutLocallyDismissedRoomMicRelationList(
|
||||||
|
List<CPRes>? sourceList,
|
||||||
|
) {
|
||||||
|
if (sourceList == null || sourceList.isEmpty) {
|
||||||
|
return sourceList;
|
||||||
|
}
|
||||||
|
final filtered = sourceList
|
||||||
|
.where((relation) => !_isRoomMicRelationLocallyDismissed(relation))
|
||||||
|
.toList(growable: false);
|
||||||
|
return filtered.length == sourceList.length ? sourceList : filtered;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _isRoomMicRelationLocallyDismissed(CPRes relation) {
|
||||||
|
if (_roomMicRelationDismissedPairUntil.isEmpty) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_pruneRoomMicRelationDismissedPairs(DateTime.now());
|
||||||
|
if (_roomMicRelationDismissedPairUntil.isEmpty) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final keys = _roomMicRelationDismissedPairKeysFromRelation(relation);
|
||||||
|
return keys.any(_roomMicRelationDismissedPairUntil.containsKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<String> _roomMicRelationDismissedPairKeysFromRelation(CPRes relation) {
|
||||||
|
final leftKeys = <String>{
|
||||||
|
relation.meUserId?.trim() ?? "",
|
||||||
|
relation.meAccount?.trim() ?? "",
|
||||||
|
}..remove("");
|
||||||
|
final rightKeys = <String>{
|
||||||
|
relation.cpUserId?.trim() ?? "",
|
||||||
|
relation.cpAccount?.trim() ?? "",
|
||||||
|
}..remove("");
|
||||||
|
if (leftKeys.isEmpty || rightKeys.isEmpty) {
|
||||||
|
return const <String>{};
|
||||||
|
}
|
||||||
|
final relationType = scNormalizeCpRelationType(relation.relationType);
|
||||||
|
return <String>{
|
||||||
|
for (final leftKey in leftKeys)
|
||||||
|
for (final rightKey in rightKeys)
|
||||||
|
_roomMicRelationDismissedPairKey(leftKey, rightKey, relationType),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
String _roomMicRelationDismissedPairKey(
|
||||||
|
String leftIdentity,
|
||||||
|
String rightIdentity,
|
||||||
|
String relationType,
|
||||||
|
) {
|
||||||
|
final identities = <String>[leftIdentity.trim(), rightIdentity.trim()]
|
||||||
|
..sort();
|
||||||
|
return '${scNormalizeCpRelationType(relationType)}|'
|
||||||
|
'${identities[0]}|${identities[1]}';
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<String> _roomMicRelationIdentityKeysForUserId(String userId) {
|
||||||
|
final normalizedUserId = userId.trim();
|
||||||
|
final keys = <String>{normalizedUserId};
|
||||||
|
for (final mic in roomWheatMap.values) {
|
||||||
|
final user = mic.user;
|
||||||
|
if ((user?.id ?? "").trim() == normalizedUserId) {
|
||||||
|
keys
|
||||||
|
..add(user?.id?.trim() ?? "")
|
||||||
|
..add(user?.account?.trim() ?? "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return keys..remove("");
|
||||||
|
}
|
||||||
|
|
||||||
|
void _pruneRoomMicRelationDismissedPairs(DateTime now) {
|
||||||
|
_roomMicRelationDismissedPairUntil.removeWhere(
|
||||||
|
(_, expiresAt) => !expiresAt.isAfter(now),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
bool _pruneRoomMusicPublishingUsersToMicSeats() {
|
bool _pruneRoomMusicPublishingUsersToMicSeats() {
|
||||||
if (_roomMusicPublishingUserIds.isEmpty) {
|
if (_roomMusicPublishingUserIds.isEmpty) {
|
||||||
return false;
|
return false;
|
||||||
@ -1797,6 +2167,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
if (clearMicSeats) {
|
if (clearMicSeats) {
|
||||||
_roomMicRelationCache.clear();
|
_roomMicRelationCache.clear();
|
||||||
_roomMicRelationLoadingUserIds.clear();
|
_roomMicRelationLoadingUserIds.clear();
|
||||||
|
_roomMicRelationDismissedPairUntil.clear();
|
||||||
roomWheatMap.clear();
|
roomWheatMap.clear();
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
@ -1935,13 +2306,17 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> _switchRoomRtcToBroadcaster({required bool muted}) async {
|
Future<bool> _switchRoomRtcToBroadcaster({
|
||||||
|
required bool muted,
|
||||||
|
bool keepAudioPublishing = false,
|
||||||
|
}) async {
|
||||||
if (!_roomRtcJoined || _roomRtcEngineAdapter == null) {
|
if (!_roomRtcJoined || _roomRtcEngineAdapter == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
final localAudioMuted = muted && !keepAudioPublishing;
|
||||||
final switched = await _currentRoomRtcEngineAdapter.switchToAnchor(
|
final switched = await _currentRoomRtcEngineAdapter.switchToAnchor(
|
||||||
RoomRtcAnchorConfig(muted: muted),
|
RoomRtcAnchorConfig(muted: muted, publishMuted: localAudioMuted),
|
||||||
);
|
);
|
||||||
if (!switched) {
|
if (!switched) {
|
||||||
_roomRtcRoleIsBroadcaster = false;
|
_roomRtcRoleIsBroadcaster = false;
|
||||||
@ -1949,8 +2324,8 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
_lastAppliedLocalAudioRole = _RoomRtcLocalAudioRole.anchor;
|
_lastAppliedLocalAudioRole = _RoomRtcLocalAudioRole.anchor;
|
||||||
_lastAppliedLocalAudioMuted = false;
|
_lastAppliedLocalAudioMuted = localAudioMuted;
|
||||||
_lastAppliedRecordingSignalMuted = false;
|
_lastAppliedRecordingSignalMuted = muted;
|
||||||
_lastAppliedRecordingSignalVolume = muted ? 0 : 100;
|
_lastAppliedRecordingSignalVolume = muted ? 0 : 100;
|
||||||
_roomRtcRoleIsBroadcaster = true;
|
_roomRtcRoleIsBroadcaster = true;
|
||||||
_resyncHeartbeatFromRoomRtcState();
|
_resyncHeartbeatFromRoomRtcState();
|
||||||
@ -1965,6 +2340,9 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
Future<void> _rejoinCurrentRoomRtcForSameRoomReuse(String roomId) async {
|
Future<void> _rejoinCurrentRoomRtcForSameRoomReuse(String roomId) async {
|
||||||
if (_hasCurrentRoomRtcConnection) {
|
if (_hasCurrentRoomRtcConnection) {
|
||||||
_setRoomStartupReady();
|
_setRoomStartupReady();
|
||||||
|
if (_voiceRoomRouteVisible) {
|
||||||
|
_loadRoomRocketRewardPopups(roomId);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1989,6 +2367,9 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_setRoomStartupReady();
|
_setRoomStartupReady();
|
||||||
|
if (_voiceRoomRouteVisible) {
|
||||||
|
_loadRoomRocketRewardPopups(roomId);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (!_isCurrentRoomId(roomId)) {
|
if (!_isCurrentRoomId(roomId)) {
|
||||||
return;
|
return;
|
||||||
@ -2030,7 +2411,10 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
final seatMuted = seat?.micMute ?? false;
|
final seatMuted = seat?.micMute ?? false;
|
||||||
if (seatMuted || muted) {
|
if (seatMuted || muted) {
|
||||||
if (_roomRtcJoined && isOnMai()) {
|
if (_roomRtcJoined && isOnMai()) {
|
||||||
await _switchRoomRtcToBroadcaster(muted: true);
|
await _switchRoomRtcToBroadcaster(
|
||||||
|
muted: true,
|
||||||
|
keepAudioPublishing: _currentUserPublishingRoomMusic,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
await _switchRoomRtcToBroadcaster(muted: false);
|
await _switchRoomRtcToBroadcaster(muted: false);
|
||||||
@ -3516,58 +3900,13 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _loadRoomRocketRewardPopups(String roomId) {
|
void _loadRoomRocketRewardPopups(String roomId) {
|
||||||
if (roomId.trim().isEmpty) {
|
final normalizedRoomId = roomId.trim();
|
||||||
|
if (normalizedRoomId.isEmpty ||
|
||||||
|
normalizedRoomId !=
|
||||||
|
(currenRoom?.roomProfile?.roomProfile?.id ?? "").trim()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
SCChatRoomRepository()
|
rtmProvider?.queryVoiceRoomRocketRewardPopups(normalizedRoomId);
|
||||||
.roomRocketRewardPopups(roomId)
|
|
||||||
.then((res) {
|
|
||||||
final popupRecords = SCRoomRocketRewardPopupAck.filterUnshownRecords(
|
|
||||||
res.records,
|
|
||||||
);
|
|
||||||
if (popupRecords.isEmpty ||
|
|
||||||
roomId !=
|
|
||||||
(currenRoom?.roomProfile?.roomProfile?.id ?? "").trim()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (SCRoomTopLayerGuard().isActive) {
|
|
||||||
SCRoomTopLayerGuard().runWhenInactive(() {
|
|
||||||
_loadRoomRocketRewardPopups(roomId);
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
SCRoomRocketRewardPopupAck.markShownAndAck(popupRecords);
|
|
||||||
final recordIds = SCRoomRocketRewardPopupAck.recordIds(popupRecords);
|
|
||||||
SmartDialog.dismiss(tag: _roomRocketRewardDialogTag);
|
|
||||||
SmartDialog.show(
|
|
||||||
tag: _roomRocketRewardDialogTag,
|
|
||||||
alignment: Alignment.center,
|
|
||||||
debounce: true,
|
|
||||||
animationType: SmartAnimationType.fade,
|
|
||||||
maskColor: Colors.transparent,
|
|
||||||
clickMaskDismiss: false,
|
|
||||||
builder:
|
|
||||||
(_) => SCRoomTopLayerSuppressor(
|
|
||||||
reason: 'room_rocket_reward_dialog',
|
|
||||||
keepDialogTags: const <String>{_roomRocketRewardDialogTag},
|
|
||||||
child: RoomRocketRewardDialogLoader(
|
|
||||||
roomId: roomId,
|
|
||||||
initialPopupRecords: popupRecords,
|
|
||||||
userProfileResolver: roomRocketRewardUserProfileForRecord,
|
|
||||||
onClose: () {
|
|
||||||
SmartDialog.dismiss(tag: _roomRocketRewardDialogTag);
|
|
||||||
scheduleRoomRocketPostLaunchStatusRefresh(roomId: roomId);
|
|
||||||
if (recordIds.isNotEmpty) {
|
|
||||||
SCRoomRocketRewardPopupAck.markShownAndAck(
|
|
||||||
popupRecords,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.catchError((_) {});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
///更新房间火箭信息
|
///更新房间火箭信息
|
||||||
@ -4086,8 +4425,10 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
if (roomId != currenRoom?.roomProfile?.roomProfile?.id) {
|
if (roomId != currenRoom?.roomProfile?.roomProfile?.id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final nextMap = _buildMicMap(roomWheatList);
|
_applyMicListSnapshot(
|
||||||
_applyMicSnapshot(nextMap, notifyIfUnchanged: notifyIfUnchanged);
|
roomWheatList,
|
||||||
|
notifyIfUnchanged: notifyIfUnchanged,
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (_isMissingPathError(error)) {
|
if (_isMissingPathError(error)) {
|
||||||
_disableMicListRefreshForCurrentSession = true;
|
_disableMicListRefreshForCurrentSession = true;
|
||||||
@ -4539,8 +4880,12 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
_isHandlingRoomRtcFailure = false;
|
_isHandlingRoomRtcFailure = false;
|
||||||
_pendingMicListRefresh = false;
|
_pendingMicListRefresh = false;
|
||||||
_pendingMicListRefreshNotifyIfUnchanged = false;
|
_pendingMicListRefreshNotifyIfUnchanged = false;
|
||||||
|
_recentMicChangeSeatSnapshots.clear();
|
||||||
_seatEmojiEventVersions.clear();
|
_seatEmojiEventVersions.clear();
|
||||||
_emojiPlaybackEventVersion = 0;
|
_emojiPlaybackEventVersion = 0;
|
||||||
|
_roomMicRelationCache.clear();
|
||||||
|
_roomMicRelationLoadingUserIds.clear();
|
||||||
|
_roomMicRelationDismissedPairUntil.clear();
|
||||||
_clearRoomMusicPublishingStates();
|
_clearRoomMusicPublishingStates();
|
||||||
roomRocketStatus = null;
|
roomRocketStatus = null;
|
||||||
rtmProvider
|
rtmProvider
|
||||||
@ -4788,6 +5133,9 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
final targetIndex = micGoUpRes.micIndex ?? index;
|
final targetIndex = micGoUpRes.micIndex ?? index;
|
||||||
final switched = await _switchRoomRtcToBroadcaster(
|
final switched = await _switchRoomRtcToBroadcaster(
|
||||||
muted: (micGoUpRes.micMute ?? false) || isMic,
|
muted: (micGoUpRes.micMute ?? false) || isMic,
|
||||||
|
keepAudioPublishing:
|
||||||
|
((micGoUpRes.micMute ?? false) || isMic) &&
|
||||||
|
_currentUserPublishingRoomMusic,
|
||||||
);
|
);
|
||||||
if (!switched) {
|
if (!switched) {
|
||||||
await _switchRoomRtcToAudience();
|
await _switchRoomRtcToAudience();
|
||||||
@ -4912,34 +5260,52 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
///踢人下麦
|
///踢人下麦
|
||||||
killXiaMai(String userId) {
|
Future<void> killXiaMai(String userId) async {
|
||||||
try {
|
try {
|
||||||
roomWheatMap.forEach((k, v) async {
|
final targetUserId = userId.trim();
|
||||||
if (v.user?.id == userId) {
|
final roomId = currenRoom?.roomProfile?.roomProfile?.id?.trim() ?? "";
|
||||||
var canKill = await SCChatRoomRepository().kickOffMicrophone(
|
if (targetUserId.isEmpty || roomId.isEmpty) {
|
||||||
currenRoom?.roomProfile?.roomProfile?.id ?? "",
|
SCTts.show("kill the user fail");
|
||||||
userId,
|
return;
|
||||||
);
|
}
|
||||||
if (canKill) {
|
num? targetMicIndex;
|
||||||
await SCChatRoomRepository().micKill(
|
for (final entry in roomWheatMap.entries) {
|
||||||
currenRoom?.roomProfile?.roomProfile?.id ?? "",
|
if ((entry.value.user?.id ?? "").trim() == targetUserId) {
|
||||||
k,
|
targetMicIndex = entry.key;
|
||||||
);
|
break;
|
||||||
Provider.of<RtmProvider>(context!, listen: false).dispatchMessage(
|
|
||||||
Msg(
|
|
||||||
groupId: currenRoom?.roomProfile?.roomProfile?.roomAccount,
|
|
||||||
msg: userId,
|
|
||||||
type: SCRoomMsgType.killXiaMai,
|
|
||||||
),
|
|
||||||
addLocal: false,
|
|
||||||
);
|
|
||||||
roomWheatMap[k]?.setUser = null;
|
|
||||||
notifyListeners();
|
|
||||||
} else {
|
|
||||||
SCTts.show("cant kill the user");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
if (targetMicIndex == null) {
|
||||||
|
SCTts.show("cant kill the user");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var canKill = true;
|
||||||
|
try {
|
||||||
|
canKill = await SCChatRoomRepository().kickOffMicrophone(
|
||||||
|
roomId,
|
||||||
|
targetUserId,
|
||||||
|
mickIndex: targetMicIndex,
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
if (!_isInvalidKickOffMicPrecheckError(error)) {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (canKill) {
|
||||||
|
await SCChatRoomRepository().micKill(roomId, targetMicIndex);
|
||||||
|
Provider.of<RtmProvider>(context!, listen: false).dispatchMessage(
|
||||||
|
Msg(
|
||||||
|
groupId: currenRoom?.roomProfile?.roomProfile?.roomAccount,
|
||||||
|
msg: targetUserId,
|
||||||
|
type: SCRoomMsgType.killXiaMai,
|
||||||
|
),
|
||||||
|
addLocal: false,
|
||||||
|
);
|
||||||
|
roomWheatMap[targetMicIndex]?.setUser = null;
|
||||||
|
notifyListeners();
|
||||||
|
} else {
|
||||||
|
SCTts.show("cant kill the user");
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
SCTts.show("kill the user fail");
|
SCTts.show("kill the user fail");
|
||||||
}
|
}
|
||||||
@ -5024,7 +5390,9 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
if (mics == null || mics.isEmpty) {
|
if (mics == null || mics.isEmpty) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_applyMicSnapshot(_buildMicMap(mics));
|
final nextMap = _buildMicMap(mics);
|
||||||
|
_rememberMicChangeSnapshot(mics);
|
||||||
|
_applyMicSnapshot(nextMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
///找一个空麦位 -1表示没有空位
|
///找一个空麦位 -1表示没有空位
|
||||||
@ -5142,7 +5510,10 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
if (isOnMaiInIndex(index)) {
|
if (isOnMaiInIndex(index)) {
|
||||||
await _switchRoomRtcToBroadcaster(muted: true);
|
await _switchRoomRtcToBroadcaster(
|
||||||
|
muted: true,
|
||||||
|
keepAudioPublishing: _currentUserPublishingRoomMusic,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
var mic = roomWheatMap[index];
|
var mic = roomWheatMap[index];
|
||||||
if (mic != null) {
|
if (mic != null) {
|
||||||
@ -5316,6 +5687,9 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
() {
|
() {
|
||||||
_roomMusicPublishingClearTimers.remove(normalizedUserId);
|
_roomMusicPublishingClearTimers.remove(normalizedUserId);
|
||||||
if (_roomMusicPublishingUserIds.remove(normalizedUserId)) {
|
if (_roomMusicPublishingUserIds.remove(normalizedUserId)) {
|
||||||
|
if (_isCurrentUserId(normalizedUserId)) {
|
||||||
|
_syncSelfMicRuntimeState();
|
||||||
|
}
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -5324,6 +5698,9 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
_roomMusicPublishingClearTimers.remove(normalizedUserId)?.cancel();
|
_roomMusicPublishingClearTimers.remove(normalizedUserId)?.cancel();
|
||||||
changed = _roomMusicPublishingUserIds.remove(normalizedUserId);
|
changed = _roomMusicPublishingUserIds.remove(normalizedUserId);
|
||||||
}
|
}
|
||||||
|
if (changed && _isCurrentUserId(normalizedUserId)) {
|
||||||
|
_syncSelfMicRuntimeState();
|
||||||
|
}
|
||||||
if (changed && notifyIfChanged) {
|
if (changed && notifyIfChanged) {
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
@ -5456,6 +5833,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
return _switchRoomRtcToBroadcaster(
|
return _switchRoomRtcToBroadcaster(
|
||||||
muted: (currentUserMic.micMute ?? false) || isMic,
|
muted: (currentUserMic.micMute ?? false) || isMic,
|
||||||
|
keepAudioPublishing: true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -148,8 +148,8 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
|||||||
static const int _roomRocketLaunchRecentTtlMs = 90000;
|
static const int _roomRocketLaunchRecentTtlMs = 90000;
|
||||||
static const int _roomRocketLaunchLooseRecentTtlMs = 12000;
|
static const int _roomRocketLaunchLooseRecentTtlMs = 12000;
|
||||||
static const int _roomRocketRegionBroadcastRecentTtlMs = 90000;
|
static const int _roomRocketRegionBroadcastRecentTtlMs = 90000;
|
||||||
static const int _roomRocketRewardPendingTtlMs = 120000;
|
static const int _roomRocketRewardPendingTtlMs = 3600000;
|
||||||
static const int _roomRocketRewardProbeTtlMs = 120000;
|
static const int _roomRocketRewardProbeTtlMs = 3600000;
|
||||||
static const int _roomRocketLaunchCountdownRetainMs = 10000;
|
static const int _roomRocketLaunchCountdownRetainMs = 10000;
|
||||||
static const int _roomRocketLaunchEffectConsumedTtlMs = 120000;
|
static const int _roomRocketLaunchEffectConsumedTtlMs = 120000;
|
||||||
static const Duration _createRoomGroupTimeout = Duration(seconds: 12);
|
static const Duration _createRoomGroupTimeout = Duration(seconds: 12);
|
||||||
@ -294,6 +294,7 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
|||||||
final Set<String> _hiddenCurrentRoomRocketLaunchKeys = <String>{};
|
final Set<String> _hiddenCurrentRoomRocketLaunchKeys = <String>{};
|
||||||
final Map<String, int> _roomRocketRewardHoldUntilTimes = <String, int>{};
|
final Map<String, int> _roomRocketRewardHoldUntilTimes = <String, int>{};
|
||||||
final Map<String, Timer> _roomRocketRewardHoldTimers = <String, Timer>{};
|
final Map<String, Timer> _roomRocketRewardHoldTimers = <String, Timer>{};
|
||||||
|
final Set<String> _roomRocketRewardPopupLoadingRoomIds = <String>{};
|
||||||
Timer? _roomRocketRewardPopupRetryTimer;
|
Timer? _roomRocketRewardPopupRetryTimer;
|
||||||
Timer? _roomRocketRoomReadyRewardRetryTimer;
|
Timer? _roomRocketRoomReadyRewardRetryTimer;
|
||||||
final Set<String> _shownCpInviteMessageKeys = <String>{};
|
final Set<String> _shownCpInviteMessageKeys = <String>{};
|
||||||
@ -3929,7 +3930,15 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
|||||||
.toSet();
|
.toSet();
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleVoiceRoomReadyForRocketRewards(String roomId, {int attempt = 0}) {
|
void queryVoiceRoomRocketRewardPopups(String roomId) {
|
||||||
|
handleVoiceRoomReadyForRocketRewards(roomId, forceQuery: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleVoiceRoomReadyForRocketRewards(
|
||||||
|
String roomId, {
|
||||||
|
int attempt = 0,
|
||||||
|
bool forceQuery = false,
|
||||||
|
}) {
|
||||||
final normalizedRoomId = roomId.trim();
|
final normalizedRoomId = roomId.trim();
|
||||||
if (normalizedRoomId.isEmpty) {
|
if (normalizedRoomId.isEmpty) {
|
||||||
return;
|
return;
|
||||||
@ -3941,11 +3950,15 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
|||||||
_recentRoomRocketRewardProbeTimes[normalizedRoomId] != null;
|
_recentRoomRocketRewardProbeTimes[normalizedRoomId] != null;
|
||||||
final hasLaunch =
|
final hasLaunch =
|
||||||
_pendingRoomRocketCountdownLaunches[normalizedRoomId] != null;
|
_pendingRoomRocketCountdownLaunches[normalizedRoomId] != null;
|
||||||
if (pending == null && !hasProbe && !hasLaunch) {
|
if (pending == null && !hasProbe && !hasLaunch && !forceQuery) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!_isCurrentVisibleVoiceRoom(normalizedRoomId)) {
|
if (!_isCurrentVisibleVoiceRoom(normalizedRoomId)) {
|
||||||
_scheduleRoomRocketReadyRewardRetry(normalizedRoomId, attempt);
|
_scheduleRoomRocketReadyRewardRetry(
|
||||||
|
normalizedRoomId,
|
||||||
|
attempt,
|
||||||
|
forceQuery: forceQuery,
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final resumedLaunch = _resumeRoomRocketLaunchForCurrentRoom(
|
final resumedLaunch = _resumeRoomRocketLaunchForCurrentRoom(
|
||||||
@ -3954,11 +3967,18 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
|||||||
final pendingAfterResume = _pendingRoomRocketRewardPopups[normalizedRoomId];
|
final pendingAfterResume = _pendingRoomRocketRewardPopups[normalizedRoomId];
|
||||||
final hasProbeAfterResume =
|
final hasProbeAfterResume =
|
||||||
_recentRoomRocketRewardProbeTimes[normalizedRoomId] != null;
|
_recentRoomRocketRewardProbeTimes[normalizedRoomId] != null;
|
||||||
if (pendingAfterResume == null && !hasProbeAfterResume) {
|
if (pendingAfterResume == null && !hasProbeAfterResume && !forceQuery) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!resumedLaunch && pendingAfterResume == null && attempt == 0) {
|
if (!resumedLaunch &&
|
||||||
_scheduleRoomRocketReadyRewardRetry(normalizedRoomId, attempt);
|
pendingAfterResume == null &&
|
||||||
|
attempt == 0 &&
|
||||||
|
!forceQuery) {
|
||||||
|
_scheduleRoomRocketReadyRewardRetry(
|
||||||
|
normalizedRoomId,
|
||||||
|
attempt,
|
||||||
|
forceQuery: forceQuery,
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_pendingRoomRocketRewardPopups.remove(normalizedRoomId);
|
_pendingRoomRocketRewardPopups.remove(normalizedRoomId);
|
||||||
@ -3984,7 +4004,11 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _scheduleRoomRocketReadyRewardRetry(String roomId, int attempt) {
|
void _scheduleRoomRocketReadyRewardRetry(
|
||||||
|
String roomId,
|
||||||
|
int attempt, {
|
||||||
|
bool forceQuery = false,
|
||||||
|
}) {
|
||||||
if (attempt >= _roomRocketRoomReadyRewardRetryDelays.length) {
|
if (attempt >= _roomRocketRoomReadyRewardRetryDelays.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -3993,7 +4017,11 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
|||||||
_roomRocketRoomReadyRewardRetryDelays[attempt],
|
_roomRocketRoomReadyRewardRetryDelays[attempt],
|
||||||
() {
|
() {
|
||||||
_roomRocketRoomReadyRewardRetryTimer = null;
|
_roomRocketRoomReadyRewardRetryTimer = null;
|
||||||
handleVoiceRoomReadyForRocketRewards(roomId, attempt: attempt + 1);
|
handleVoiceRoomReadyForRocketRewards(
|
||||||
|
roomId,
|
||||||
|
attempt: attempt + 1,
|
||||||
|
forceQuery: forceQuery,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -4010,11 +4038,12 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
|||||||
final now = DateTime.now().millisecondsSinceEpoch;
|
final now = DateTime.now().millisecondsSinceEpoch;
|
||||||
_pruneRoomRocketRewardFallbacks(now);
|
_pruneRoomRocketRewardFallbacks(now);
|
||||||
final existing = _pendingRoomRocketRewardPopups[normalizedRoomId];
|
final existing = _pendingRoomRocketRewardPopups[normalizedRoomId];
|
||||||
|
final existingRecords =
|
||||||
|
existing?.initialRoomRecords ?? const <SCRoomRocketRewardRecordRes>[];
|
||||||
final records =
|
final records =
|
||||||
initialRoomRecords.isNotEmpty
|
initialRoomRecords.isNotEmpty
|
||||||
? List<SCRoomRocketRewardRecordRes>.unmodifiable(initialRoomRecords)
|
? _mergeRoomRocketRewardRecords(existingRecords, initialRoomRecords)
|
||||||
: existing?.initialRoomRecords ??
|
: existingRecords;
|
||||||
const <SCRoomRocketRewardRecordRes>[];
|
|
||||||
_pendingRoomRocketRewardPopups[normalizedRoomId] =
|
_pendingRoomRocketRewardPopups[normalizedRoomId] =
|
||||||
_PendingRoomRocketRewardPopup(
|
_PendingRoomRocketRewardPopup(
|
||||||
roomId: normalizedRoomId,
|
roomId: normalizedRoomId,
|
||||||
@ -4023,6 +4052,47 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<SCRoomRocketRewardRecordRes> _mergeRoomRocketRewardRecords(
|
||||||
|
List<SCRoomRocketRewardRecordRes> existingRecords,
|
||||||
|
List<SCRoomRocketRewardRecordRes> incomingRecords,
|
||||||
|
) {
|
||||||
|
if (existingRecords.isEmpty) {
|
||||||
|
return List<SCRoomRocketRewardRecordRes>.unmodifiable(incomingRecords);
|
||||||
|
}
|
||||||
|
if (incomingRecords.isEmpty) {
|
||||||
|
return existingRecords;
|
||||||
|
}
|
||||||
|
final merged = <SCRoomRocketRewardRecordRes>[...existingRecords];
|
||||||
|
final seen = existingRecords.map(_roomRocketRewardRecordCacheKey).toSet();
|
||||||
|
for (final record in incomingRecords) {
|
||||||
|
if (seen.add(_roomRocketRewardRecordCacheKey(record))) {
|
||||||
|
merged.add(record);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return List<SCRoomRocketRewardRecordRes>.unmodifiable(merged);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _roomRocketRewardRecordCacheKey(SCRoomRocketRewardRecordRes record) {
|
||||||
|
final id = record.id.trim();
|
||||||
|
if (id.isNotEmpty) {
|
||||||
|
return 'id:$id';
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
record.launchNo.trim(),
|
||||||
|
record.roomId.trim(),
|
||||||
|
record.roundNo.toString(),
|
||||||
|
record.level.toString(),
|
||||||
|
record.userId.trim(),
|
||||||
|
record.rewardScene.trim().toUpperCase(),
|
||||||
|
record.rewardType.trim().toUpperCase(),
|
||||||
|
record.rewardItemId.trim(),
|
||||||
|
record.rewardName.trim(),
|
||||||
|
record.rewardAmount.toString(),
|
||||||
|
record.grantTime.trim(),
|
||||||
|
record.createTime.trim(),
|
||||||
|
].join('|');
|
||||||
|
}
|
||||||
|
|
||||||
void _markRoomRocketRewardProbe(String roomId) {
|
void _markRoomRocketRewardProbe(String roomId) {
|
||||||
final normalizedRoomId = roomId.trim();
|
final normalizedRoomId = roomId.trim();
|
||||||
if (normalizedRoomId.isEmpty) {
|
if (normalizedRoomId.isEmpty) {
|
||||||
@ -4532,12 +4602,14 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
|||||||
List<SCRoomRocketRewardRecordRes> initialRoomRecords =
|
List<SCRoomRocketRewardRecordRes> initialRoomRecords =
|
||||||
const <SCRoomRocketRewardRecordRes>[],
|
const <SCRoomRocketRewardRecordRes>[],
|
||||||
}) async {
|
}) async {
|
||||||
if (!_isCurrentVisibleVoiceRoom(roomId)) {
|
final normalizedRoomId = roomId.trim();
|
||||||
|
if (normalizedRoomId.isEmpty ||
|
||||||
|
!_isCurrentVisibleVoiceRoom(normalizedRoomId)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (_isRoomRocketRewardPopupHeld(roomId)) {
|
if (_isRoomRocketRewardPopupHeld(normalizedRoomId)) {
|
||||||
_scheduleRoomRocketRewardPopupAfterHold(
|
_scheduleRoomRocketRewardPopupAfterHold(
|
||||||
roomId,
|
normalizedRoomId,
|
||||||
initialRoomRecords: initialRoomRecords,
|
initialRoomRecords: initialRoomRecords,
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
@ -4546,7 +4618,7 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
|||||||
SCRoomTopLayerGuard().runWhenInactive(() {
|
SCRoomTopLayerGuard().runWhenInactive(() {
|
||||||
unawaited(
|
unawaited(
|
||||||
_showRoomRocketRewardPopupDialog(
|
_showRoomRocketRewardPopupDialog(
|
||||||
roomId,
|
normalizedRoomId,
|
||||||
attempt: attempt,
|
attempt: attempt,
|
||||||
initialRoomRecords: initialRoomRecords,
|
initialRoomRecords: initialRoomRecords,
|
||||||
),
|
),
|
||||||
@ -4554,16 +4626,21 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!_roomRocketRewardPopupLoadingRoomIds.add(normalizedRoomId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
final res = await SCChatRoomRepository().roomRocketRewardPopups(roomId);
|
final res = await SCChatRoomRepository().roomRocketRewardPopups(
|
||||||
if (!_isCurrentVisibleVoiceRoom(roomId)) {
|
normalizedRoomId,
|
||||||
|
);
|
||||||
|
if (!_isCurrentVisibleVoiceRoom(normalizedRoomId)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (res.records.isEmpty &&
|
if (res.records.isEmpty &&
|
||||||
initialRoomRecords.isEmpty &&
|
initialRoomRecords.isEmpty &&
|
||||||
attempt < _roomRocketRewardPopupRetryDelays.length) {
|
attempt < _roomRocketRewardPopupRetryDelays.length) {
|
||||||
_scheduleRoomRocketRewardPopupRetry(
|
_scheduleRoomRocketRewardPopupRetry(
|
||||||
roomId,
|
normalizedRoomId,
|
||||||
attempt,
|
attempt,
|
||||||
initialRoomRecords: initialRoomRecords,
|
initialRoomRecords: initialRoomRecords,
|
||||||
);
|
);
|
||||||
@ -4581,44 +4658,103 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
|||||||
_roomRocketRewardPopupRetryTimer?.cancel();
|
_roomRocketRewardPopupRetryTimer?.cancel();
|
||||||
_roomRocketRewardPopupRetryTimer = null;
|
_roomRocketRewardPopupRetryTimer = null;
|
||||||
SCRoomRocketRewardPopupAck.markShownAndAck(popupRecords);
|
SCRoomRocketRewardPopupAck.markShownAndAck(popupRecords);
|
||||||
final recordIds = SCRoomRocketRewardPopupAck.recordIds(popupRecords);
|
|
||||||
final userProfileResolver = _roomRocketRewardUserProfileResolver();
|
final userProfileResolver = _roomRocketRewardUserProfileResolver();
|
||||||
SmartDialog.dismiss(tag: _roomRocketRewardDialogTag);
|
final popupGroups = RoomRocketApiMapper.rewardPopupRecordGroups(
|
||||||
SmartDialog.show(
|
popupRecords,
|
||||||
tag: _roomRocketRewardDialogTag,
|
);
|
||||||
alignment: Alignment.center,
|
final groupsToShow =
|
||||||
debounce: true,
|
popupGroups.isNotEmpty
|
||||||
animationType: SmartAnimationType.fade,
|
? popupGroups
|
||||||
maskColor: Colors.transparent,
|
: const <List<SCRoomRocketRewardRecordRes>>[
|
||||||
clickMaskDismiss: false,
|
<SCRoomRocketRewardRecordRes>[],
|
||||||
builder:
|
];
|
||||||
(_) => SCRoomTopLayerSuppressor(
|
_showRoomRocketRewardPopupGroups(
|
||||||
reason: 'room_rocket_reward_dialog',
|
roomId: normalizedRoomId,
|
||||||
keepDialogTags: const <String>{_roomRocketRewardDialogTag},
|
popupGroups: groupsToShow,
|
||||||
child: RoomRocketRewardDialogLoader(
|
groupIndex: 0,
|
||||||
roomId: roomId,
|
initialRoomRecords: initialRoomRecords,
|
||||||
initialPopupRecords: popupRecords,
|
userProfileResolver: userProfileResolver,
|
||||||
initialRoomRecords: initialRoomRecords,
|
|
||||||
userProfileResolver: userProfileResolver,
|
|
||||||
onClose: () {
|
|
||||||
SmartDialog.dismiss(tag: _roomRocketRewardDialogTag);
|
|
||||||
_scheduleRoomRocketPostLaunchStatusRefresh(roomId);
|
|
||||||
if (recordIds.isNotEmpty) {
|
|
||||||
SCRoomRocketRewardPopupAck.markShownAndAck(popupRecords);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
_scheduleRoomRocketRewardPopupRetry(
|
_scheduleRoomRocketRewardPopupRetry(
|
||||||
roomId,
|
normalizedRoomId,
|
||||||
attempt,
|
attempt,
|
||||||
initialRoomRecords: initialRoomRecords,
|
initialRoomRecords: initialRoomRecords,
|
||||||
);
|
);
|
||||||
|
} finally {
|
||||||
|
_roomRocketRewardPopupLoadingRoomIds.remove(normalizedRoomId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _showRoomRocketRewardPopupGroups({
|
||||||
|
required String roomId,
|
||||||
|
required List<List<SCRoomRocketRewardRecordRes>> popupGroups,
|
||||||
|
required int groupIndex,
|
||||||
|
required List<SCRoomRocketRewardRecordRes> initialRoomRecords,
|
||||||
|
required RoomRocketRewardUserProfileResolver? userProfileResolver,
|
||||||
|
}) {
|
||||||
|
if (!_isCurrentVisibleVoiceRoom(roomId) ||
|
||||||
|
groupIndex >= popupGroups.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (SCRoomTopLayerGuard().isActive) {
|
||||||
|
SCRoomTopLayerGuard().runWhenInactive(() {
|
||||||
|
_showRoomRocketRewardPopupGroups(
|
||||||
|
roomId: roomId,
|
||||||
|
popupGroups: popupGroups,
|
||||||
|
groupIndex: groupIndex,
|
||||||
|
initialRoomRecords: initialRoomRecords,
|
||||||
|
userProfileResolver: userProfileResolver,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final popupRecords = popupGroups[groupIndex];
|
||||||
|
final roomRecords = RoomRocketApiMapper.rewardRoomRecordsForPopupGroup(
|
||||||
|
initialRoomRecords,
|
||||||
|
popupRecords,
|
||||||
|
);
|
||||||
|
final nextGroupIndex = groupIndex + 1;
|
||||||
|
SmartDialog.dismiss(tag: _roomRocketRewardDialogTag);
|
||||||
|
SmartDialog.show(
|
||||||
|
tag: _roomRocketRewardDialogTag,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
debounce: true,
|
||||||
|
animationType: SmartAnimationType.fade,
|
||||||
|
maskColor: Colors.transparent,
|
||||||
|
clickMaskDismiss: false,
|
||||||
|
builder:
|
||||||
|
(_) => SCRoomTopLayerSuppressor(
|
||||||
|
reason: 'room_rocket_reward_dialog',
|
||||||
|
keepDialogTags: const <String>{_roomRocketRewardDialogTag},
|
||||||
|
child: RoomRocketRewardDialogLoader(
|
||||||
|
roomId: roomId,
|
||||||
|
initialPopupRecords: popupRecords,
|
||||||
|
initialRoomRecords: roomRecords,
|
||||||
|
userProfileResolver: userProfileResolver,
|
||||||
|
onClose: () {
|
||||||
|
SmartDialog.dismiss(tag: _roomRocketRewardDialogTag);
|
||||||
|
_scheduleRoomRocketPostLaunchStatusRefresh(roomId);
|
||||||
|
SCRoomRocketRewardPopupAck.markShownAndAck(popupRecords);
|
||||||
|
if (nextGroupIndex < popupGroups.length) {
|
||||||
|
unawaited(
|
||||||
|
Future<void>.delayed(const Duration(milliseconds: 160), () {
|
||||||
|
_showRoomRocketRewardPopupGroups(
|
||||||
|
roomId: roomId,
|
||||||
|
popupGroups: popupGroups,
|
||||||
|
groupIndex: nextGroupIndex,
|
||||||
|
initialRoomRecords: initialRoomRecords,
|
||||||
|
userProfileResolver: userProfileResolver,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
void _scheduleRoomRocketRewardPopupRetry(
|
void _scheduleRoomRocketRewardPopupRetry(
|
||||||
String roomId,
|
String roomId,
|
||||||
int attempt, {
|
int attempt, {
|
||||||
|
|||||||
@ -34,6 +34,7 @@ abstract class RoomTrtcAudioEffectManager {
|
|||||||
void resumePlayMusic(int id);
|
void resumePlayMusic(int id);
|
||||||
void setMusicPublishVolume(int id, int volume);
|
void setMusicPublishVolume(int id, int volume);
|
||||||
void setMusicPlayoutVolume(int id, int volume);
|
void setMusicPlayoutVolume(int id, int volume);
|
||||||
|
void setVoiceCaptureVolume(int volume);
|
||||||
int getMusicCurrentPosInMS(int id);
|
int getMusicCurrentPosInMS(int id);
|
||||||
int getMusicDurationInMS(String path);
|
int getMusicDurationInMS(String path);
|
||||||
void seekMusicToPosInTime(int id, int pts);
|
void seekMusicToPosInTime(int id, int pts);
|
||||||
@ -156,6 +157,11 @@ class TencentRoomTrtcAudioEffectManager implements RoomTrtcAudioEffectManager {
|
|||||||
_manager.setMusicPlayoutVolume(id, volume);
|
_manager.setMusicPlayoutVolume(id, volume);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void setVoiceCaptureVolume(int volume) {
|
||||||
|
_manager.setVoiceCaptureVolume(volume);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int getMusicCurrentPosInMS(int id) {
|
int getMusicCurrentPosInMS(int id) {
|
||||||
return _manager.getMusicCurrentPosInMS(id);
|
return _manager.getMusicCurrentPosInMS(id);
|
||||||
@ -364,7 +370,8 @@ class TrtcRoomRtcEngineAdapter implements RoomRtcEngineAdapter {
|
|||||||
try {
|
try {
|
||||||
cloud.switchRole(TRTCRoleType.anchor);
|
cloud.switchRole(TRTCRoleType.anchor);
|
||||||
cloud.startLocalAudio(TRTCAudioQuality.defaultMode);
|
cloud.startLocalAudio(TRTCAudioQuality.defaultMode);
|
||||||
cloud.muteLocalAudio(config.muted);
|
_musicManager?.setVoiceCaptureVolume(config.muted ? 0 : 100);
|
||||||
|
cloud.muteLocalAudio(config.publishMuted);
|
||||||
_isLocalAudioStarted = true;
|
_isLocalAudioStarted = true;
|
||||||
return true;
|
return true;
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
|
|||||||
@ -180,6 +180,17 @@ class SocialChatUserProfileManager extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
final cpPairs = <CPRes>[];
|
final cpPairs = <CPRes>[];
|
||||||
final closeFriendPairs = <CPRes>[];
|
final closeFriendPairs = <CPRes>[];
|
||||||
|
_addCpRelationPairs(
|
||||||
|
cpPairs: cpPairs,
|
||||||
|
closeFriendPairs: closeFriendPairs,
|
||||||
|
relations: profile.cpList,
|
||||||
|
fallbackRelationType: 'CP',
|
||||||
|
);
|
||||||
|
_addCpRelationPairs(
|
||||||
|
cpPairs: cpPairs,
|
||||||
|
closeFriendPairs: closeFriendPairs,
|
||||||
|
relations: profile.closeFriendList,
|
||||||
|
);
|
||||||
await Future.wait(
|
await Future.wait(
|
||||||
_cpProfileRelationTypes.map((relationType) async {
|
_cpProfileRelationTypes.map((relationType) async {
|
||||||
final cabin = await _safeLoadCpCabin(userId, relationType);
|
final cabin = await _safeLoadCpCabin(userId, relationType);
|
||||||
@ -206,11 +217,12 @@ class SocialChatUserProfileManager extends ChangeNotifier {
|
|||||||
levelText: rightsLevelText ?? cabinLevelText,
|
levelText: rightsLevelText ?? cabinLevelText,
|
||||||
dismissCost: cpPair.dismissCost ?? _cpDismissCostFromCabin(cabin),
|
dismissCost: cpPair.dismissCost ?? _cpDismissCostFromCabin(cabin),
|
||||||
);
|
);
|
||||||
if (normalizedType == 'CP') {
|
_addCpRelationPair(
|
||||||
cpPairs.add(enrichedPair);
|
cpPairs: cpPairs,
|
||||||
} else {
|
closeFriendPairs: closeFriendPairs,
|
||||||
closeFriendPairs.add(enrichedPair);
|
relation: enrichedPair,
|
||||||
}
|
fallbackRelationType: normalizedType,
|
||||||
|
);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
@ -226,6 +238,81 @@ class SocialChatUserProfileManager extends ChangeNotifier {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _addCpRelationPairs({
|
||||||
|
required List<CPRes> cpPairs,
|
||||||
|
required List<CPRes> closeFriendPairs,
|
||||||
|
required List<CPRes>? relations,
|
||||||
|
String? fallbackRelationType,
|
||||||
|
}) {
|
||||||
|
for (final relation in relations ?? const <CPRes>[]) {
|
||||||
|
_addCpRelationPair(
|
||||||
|
cpPairs: cpPairs,
|
||||||
|
closeFriendPairs: closeFriendPairs,
|
||||||
|
relation: relation,
|
||||||
|
fallbackRelationType: fallbackRelationType,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _addCpRelationPair({
|
||||||
|
required List<CPRes> cpPairs,
|
||||||
|
required List<CPRes> closeFriendPairs,
|
||||||
|
required CPRes relation,
|
||||||
|
String? fallbackRelationType,
|
||||||
|
}) {
|
||||||
|
if (!_hasCpPair(relation)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final relationTypeSource =
|
||||||
|
(relation.relationType?.trim().isNotEmpty ?? false)
|
||||||
|
? relation.relationType
|
||||||
|
: fallbackRelationType;
|
||||||
|
if ((relationTypeSource ?? "").trim().isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final normalizedType = _normalizeCpRelationType(relationTypeSource);
|
||||||
|
final normalizedRelation = relation.copyWith(relationType: normalizedType);
|
||||||
|
final target = normalizedType == 'CP' ? cpPairs : closeFriendPairs;
|
||||||
|
final relationKey = _cpRelationPairKey(normalizedRelation);
|
||||||
|
final existingIndex = target.indexWhere(
|
||||||
|
(item) => _cpRelationPairKey(item) == relationKey,
|
||||||
|
);
|
||||||
|
if (existingIndex >= 0) {
|
||||||
|
target[existingIndex] = normalizedRelation;
|
||||||
|
} else {
|
||||||
|
target.add(normalizedRelation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _cpRelationPairKey(CPRes relation) {
|
||||||
|
final type = _normalizeCpRelationType(relation.relationType);
|
||||||
|
final userIds = <String>[
|
||||||
|
relation.meUserId?.trim() ?? "",
|
||||||
|
relation.cpUserId?.trim() ?? "",
|
||||||
|
]..removeWhere((value) => value.isEmpty);
|
||||||
|
if (userIds.length >= 2) {
|
||||||
|
userIds.sort();
|
||||||
|
return '$type|id|${userIds.join("|")}';
|
||||||
|
}
|
||||||
|
final accounts = <String>[
|
||||||
|
relation.meAccount?.trim() ?? "",
|
||||||
|
relation.cpAccount?.trim() ?? "",
|
||||||
|
]..removeWhere((value) => value.isEmpty);
|
||||||
|
if (accounts.length >= 2) {
|
||||||
|
accounts.sort();
|
||||||
|
return '$type|account|${accounts.join("|")}';
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
type,
|
||||||
|
relation.meUserId?.trim() ?? "",
|
||||||
|
relation.meAccount?.trim() ?? "",
|
||||||
|
relation.cpUserId?.trim() ?? "",
|
||||||
|
relation.cpAccount?.trim() ?? "",
|
||||||
|
relation.cpUserNickname?.trim() ?? "",
|
||||||
|
relation.meUserNickname?.trim() ?? "",
|
||||||
|
].join('|');
|
||||||
|
}
|
||||||
|
|
||||||
Future<SCCpCabinRes?> _safeLoadCpCabin(
|
Future<SCCpCabinRes?> _safeLoadCpCabin(
|
||||||
String userId,
|
String userId,
|
||||||
String relationType,
|
String relationType,
|
||||||
|
|||||||
@ -355,12 +355,145 @@ class SCRoomRocketRewardRes {
|
|||||||
|
|
||||||
factory SCRoomRocketRewardRes.fromJson(dynamic json) {
|
factory SCRoomRocketRewardRes.fromJson(dynamic json) {
|
||||||
final data = _asMap(json);
|
final data = _asMap(json);
|
||||||
|
final reward = _asMap(
|
||||||
|
_firstPayload([
|
||||||
|
data['reward'],
|
||||||
|
data['rewardInfo'],
|
||||||
|
data['reward_info'],
|
||||||
|
data['rewardConfig'],
|
||||||
|
data['reward_config'],
|
||||||
|
data['propsResources'],
|
||||||
|
data['props_resources'],
|
||||||
|
data['resource'],
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
final resource = _asMap(
|
||||||
|
_firstPayload([
|
||||||
|
data['propsResources'],
|
||||||
|
data['props_resources'],
|
||||||
|
data['resource'],
|
||||||
|
reward['propsResources'],
|
||||||
|
reward['props_resources'],
|
||||||
|
reward['resource'],
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
final rewardType = _stringValue(
|
||||||
|
data['rewardType'] ??
|
||||||
|
data['reward_type'] ??
|
||||||
|
data['detailType'] ??
|
||||||
|
data['detail_type'] ??
|
||||||
|
data['type'] ??
|
||||||
|
reward['rewardType'] ??
|
||||||
|
reward['reward_type'] ??
|
||||||
|
reward['detailType'] ??
|
||||||
|
reward['detail_type'] ??
|
||||||
|
reward['type'] ??
|
||||||
|
resource['type'],
|
||||||
|
);
|
||||||
|
final rewardAmountSource =
|
||||||
|
_isGoldRewardType(rewardType)
|
||||||
|
? _firstPayload([
|
||||||
|
data['rewardAmount'],
|
||||||
|
data['reward_amount'],
|
||||||
|
data['amount'],
|
||||||
|
data['content'],
|
||||||
|
data['rewardQuantity'],
|
||||||
|
data['reward_quantity'],
|
||||||
|
data['quantity'],
|
||||||
|
data['count'],
|
||||||
|
data['num'],
|
||||||
|
reward['rewardAmount'],
|
||||||
|
reward['reward_amount'],
|
||||||
|
reward['amount'],
|
||||||
|
reward['content'],
|
||||||
|
reward['rewardQuantity'],
|
||||||
|
reward['reward_quantity'],
|
||||||
|
reward['quantity'],
|
||||||
|
reward['count'],
|
||||||
|
reward['num'],
|
||||||
|
])
|
||||||
|
: _firstPayload([
|
||||||
|
data['rewardAmount'],
|
||||||
|
data['reward_amount'],
|
||||||
|
data['rewardQuantity'],
|
||||||
|
data['reward_quantity'],
|
||||||
|
data['quantity'],
|
||||||
|
data['count'],
|
||||||
|
data['num'],
|
||||||
|
data['amount'],
|
||||||
|
data['content'],
|
||||||
|
reward['rewardAmount'],
|
||||||
|
reward['reward_amount'],
|
||||||
|
reward['rewardQuantity'],
|
||||||
|
reward['reward_quantity'],
|
||||||
|
reward['quantity'],
|
||||||
|
reward['count'],
|
||||||
|
reward['num'],
|
||||||
|
reward['amount'],
|
||||||
|
reward['content'],
|
||||||
|
]);
|
||||||
return SCRoomRocketRewardRes(
|
return SCRoomRocketRewardRes(
|
||||||
rewardScene: _stringValue(data['rewardScene']),
|
rewardScene: _stringValue(
|
||||||
rewardType: _stringValue(data['rewardType']),
|
data['rewardScene'] ??
|
||||||
rewardItemId: _stringValue(data['rewardItemId']),
|
data['reward_scene'] ??
|
||||||
rewardName: _stringValue(data['rewardName']),
|
reward['rewardScene'] ??
|
||||||
rewardCover: _stringValue(data['rewardCover']),
|
reward['reward_scene'],
|
||||||
|
),
|
||||||
|
rewardType: rewardType,
|
||||||
|
rewardItemId: _stringValue(
|
||||||
|
data['rewardItemId'] ??
|
||||||
|
data['reward_item_id'] ??
|
||||||
|
data['itemId'] ??
|
||||||
|
data['item_id'] ??
|
||||||
|
data['resourceId'] ??
|
||||||
|
data['resource_id'] ??
|
||||||
|
reward['rewardItemId'] ??
|
||||||
|
reward['reward_item_id'] ??
|
||||||
|
reward['itemId'] ??
|
||||||
|
reward['item_id'] ??
|
||||||
|
reward['resourceId'] ??
|
||||||
|
reward['resource_id'] ??
|
||||||
|
reward['id'] ??
|
||||||
|
reward['amount'] ??
|
||||||
|
reward['content'] ??
|
||||||
|
resource['id'],
|
||||||
|
),
|
||||||
|
rewardName: _stringValue(
|
||||||
|
data['rewardName'] ??
|
||||||
|
data['reward_name'] ??
|
||||||
|
data['name'] ??
|
||||||
|
data['badgeName'] ??
|
||||||
|
data['badge_name'] ??
|
||||||
|
reward['rewardName'] ??
|
||||||
|
reward['reward_name'] ??
|
||||||
|
reward['name'] ??
|
||||||
|
reward['badgeName'] ??
|
||||||
|
reward['badge_name'] ??
|
||||||
|
resource['name'] ??
|
||||||
|
resource['badgeName'] ??
|
||||||
|
resource['badge_name'],
|
||||||
|
),
|
||||||
|
rewardCover: _firstAssetString([
|
||||||
|
data['rewardCover'],
|
||||||
|
data['reward_cover'],
|
||||||
|
data['cover'],
|
||||||
|
data['sourceUrl'],
|
||||||
|
data['source_url'],
|
||||||
|
data['iconUrl'],
|
||||||
|
data['icon_url'],
|
||||||
|
reward['rewardCover'],
|
||||||
|
reward['reward_cover'],
|
||||||
|
reward['cover'],
|
||||||
|
reward['sourceUrl'],
|
||||||
|
reward['source_url'],
|
||||||
|
reward['iconUrl'],
|
||||||
|
reward['icon_url'],
|
||||||
|
resource['cover'],
|
||||||
|
resource['sourceUrl'],
|
||||||
|
resource['source_url'],
|
||||||
|
resource['iconUrl'],
|
||||||
|
resource['icon_url'],
|
||||||
|
]),
|
||||||
rocketIconUrl: _firstAssetString([
|
rocketIconUrl: _firstAssetString([
|
||||||
data['rocket_icon_url'],
|
data['rocket_icon_url'],
|
||||||
data['rocketIconUrl'],
|
data['rocketIconUrl'],
|
||||||
@ -376,8 +509,28 @@ class SCRoomRocketRewardRes {
|
|||||||
_asMap(data['config'])['rocketIconUrl'],
|
_asMap(data['config'])['rocketIconUrl'],
|
||||||
_asMap(data['config'])['rocketIcon'],
|
_asMap(data['config'])['rocketIcon'],
|
||||||
]),
|
]),
|
||||||
rewardAmount: _rewardAmountValue(data['rewardAmount']) ?? 0,
|
rewardAmount: _rewardAmountValue(rewardAmountSource) ?? 0,
|
||||||
expireDays: _dayValue(data['expireDays']),
|
expireDays: _dayValue(
|
||||||
|
_firstPayload([
|
||||||
|
data['expireDays'],
|
||||||
|
data['expire_days'],
|
||||||
|
data['durationDays'],
|
||||||
|
data['duration_days'],
|
||||||
|
data['validDays'],
|
||||||
|
data['valid_days'],
|
||||||
|
data['validityDays'],
|
||||||
|
data['validity_days'],
|
||||||
|
reward['expireDays'],
|
||||||
|
reward['expire_days'],
|
||||||
|
reward['durationDays'],
|
||||||
|
reward['duration_days'],
|
||||||
|
reward['validDays'],
|
||||||
|
reward['valid_days'],
|
||||||
|
reward['validityDays'],
|
||||||
|
reward['validity_days'],
|
||||||
|
reward['quantity'],
|
||||||
|
]),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -578,11 +731,20 @@ class SCRoomRocketRewardPreviewRes {
|
|||||||
|
|
||||||
static bool _looksLikeReward(Map<String, dynamic> data) {
|
static bool _looksLikeReward(Map<String, dynamic> data) {
|
||||||
return data.containsKey('rewardScene') ||
|
return data.containsKey('rewardScene') ||
|
||||||
|
data.containsKey('reward_scene') ||
|
||||||
data.containsKey('rewardType') ||
|
data.containsKey('rewardType') ||
|
||||||
|
data.containsKey('reward_type') ||
|
||||||
data.containsKey('rewardItemId') ||
|
data.containsKey('rewardItemId') ||
|
||||||
|
data.containsKey('reward_item_id') ||
|
||||||
data.containsKey('rewardName') ||
|
data.containsKey('rewardName') ||
|
||||||
|
data.containsKey('reward_name') ||
|
||||||
data.containsKey('rewardCover') ||
|
data.containsKey('rewardCover') ||
|
||||||
data.containsKey('rewardAmount');
|
data.containsKey('reward_cover') ||
|
||||||
|
data.containsKey('rewardAmount') ||
|
||||||
|
data.containsKey('reward_amount') ||
|
||||||
|
data.containsKey('reward') ||
|
||||||
|
data.containsKey('rewardInfo') ||
|
||||||
|
data.containsKey('reward_info');
|
||||||
}
|
}
|
||||||
|
|
||||||
static String _normalizedScene(String value) {
|
static String _normalizedScene(String value) {
|
||||||
@ -925,6 +1087,102 @@ class SCRoomRocketRewardRecordRes {
|
|||||||
winner,
|
winner,
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
|
final reward = _asMap(
|
||||||
|
_firstPayload([
|
||||||
|
data['reward'],
|
||||||
|
data['rewardInfo'],
|
||||||
|
data['reward_info'],
|
||||||
|
data['rewardConfig'],
|
||||||
|
data['reward_config'],
|
||||||
|
data['propsResources'],
|
||||||
|
data['props_resources'],
|
||||||
|
data['resource'],
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
final resource = _asMap(
|
||||||
|
_firstPayload([
|
||||||
|
data['propsResources'],
|
||||||
|
data['props_resources'],
|
||||||
|
data['resource'],
|
||||||
|
reward['propsResources'],
|
||||||
|
reward['props_resources'],
|
||||||
|
reward['resource'],
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
final rewardType = _stringValue(
|
||||||
|
data['rewardType'] ??
|
||||||
|
data['reward_type'] ??
|
||||||
|
data['detailType'] ??
|
||||||
|
data['detail_type'] ??
|
||||||
|
data['type'] ??
|
||||||
|
reward['rewardType'] ??
|
||||||
|
reward['reward_type'] ??
|
||||||
|
reward['detailType'] ??
|
||||||
|
reward['detail_type'] ??
|
||||||
|
reward['type'] ??
|
||||||
|
resource['type'],
|
||||||
|
);
|
||||||
|
final rewardAmountSource =
|
||||||
|
_isGoldRewardType(rewardType)
|
||||||
|
? _firstPayload([
|
||||||
|
data['rewardAmount'],
|
||||||
|
data['reward_amount'],
|
||||||
|
data['amount'],
|
||||||
|
data['content'],
|
||||||
|
data['rewardQuantity'],
|
||||||
|
data['reward_quantity'],
|
||||||
|
data['quantity'],
|
||||||
|
data['count'],
|
||||||
|
data['num'],
|
||||||
|
reward['rewardAmount'],
|
||||||
|
reward['reward_amount'],
|
||||||
|
reward['amount'],
|
||||||
|
reward['content'],
|
||||||
|
reward['rewardQuantity'],
|
||||||
|
reward['reward_quantity'],
|
||||||
|
reward['quantity'],
|
||||||
|
reward['count'],
|
||||||
|
reward['num'],
|
||||||
|
])
|
||||||
|
: _firstPayload([
|
||||||
|
data['rewardAmount'],
|
||||||
|
data['reward_amount'],
|
||||||
|
data['rewardQuantity'],
|
||||||
|
data['reward_quantity'],
|
||||||
|
data['quantity'],
|
||||||
|
data['count'],
|
||||||
|
data['num'],
|
||||||
|
data['amount'],
|
||||||
|
data['content'],
|
||||||
|
reward['rewardAmount'],
|
||||||
|
reward['reward_amount'],
|
||||||
|
reward['rewardQuantity'],
|
||||||
|
reward['reward_quantity'],
|
||||||
|
reward['quantity'],
|
||||||
|
reward['count'],
|
||||||
|
reward['num'],
|
||||||
|
reward['amount'],
|
||||||
|
reward['content'],
|
||||||
|
]);
|
||||||
|
final expireDaysSource = _firstPayload([
|
||||||
|
data['expireDays'],
|
||||||
|
data['expire_days'],
|
||||||
|
data['durationDays'],
|
||||||
|
data['duration_days'],
|
||||||
|
data['validDays'],
|
||||||
|
data['valid_days'],
|
||||||
|
data['validityDays'],
|
||||||
|
data['validity_days'],
|
||||||
|
reward['expireDays'],
|
||||||
|
reward['expire_days'],
|
||||||
|
reward['durationDays'],
|
||||||
|
reward['duration_days'],
|
||||||
|
reward['validDays'],
|
||||||
|
reward['valid_days'],
|
||||||
|
reward['validityDays'],
|
||||||
|
reward['validity_days'],
|
||||||
|
reward['quantity'],
|
||||||
|
]);
|
||||||
return SCRoomRocketRewardRecordRes(
|
return SCRoomRocketRewardRecordRes(
|
||||||
id: _stringValue(data['id']),
|
id: _stringValue(data['id']),
|
||||||
launchNo: _stringValue(data['launchNo'] ?? data['launch_no']),
|
launchNo: _stringValue(data['launchNo'] ?? data['launch_no']),
|
||||||
@ -1085,16 +1343,68 @@ class SCRoomRocketRewardRecordRes {
|
|||||||
profile['actual_account'],
|
profile['actual_account'],
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
rewardScene: _stringValue(data['rewardScene'] ?? data['reward_scene']),
|
rewardScene: _stringValue(
|
||||||
rewardType: _stringValue(data['rewardType'] ?? data['reward_type']),
|
data['rewardScene'] ??
|
||||||
rewardItemId: _stringValue(
|
data['reward_scene'] ??
|
||||||
data['rewardItemId'] ?? data['reward_item_id'],
|
reward['rewardScene'] ??
|
||||||
|
reward['reward_scene'],
|
||||||
),
|
),
|
||||||
rewardName: _stringValue(data['rewardName'] ?? data['reward_name']),
|
rewardType: rewardType,
|
||||||
rewardCover: _stringValue(data['rewardCover'] ?? data['reward_cover']),
|
rewardItemId: _stringValue(
|
||||||
rewardAmount:
|
data['rewardItemId'] ??
|
||||||
_rewardAmountValue(data['rewardAmount'] ?? data['reward_amount']) ??
|
data['reward_item_id'] ??
|
||||||
0,
|
data['itemId'] ??
|
||||||
|
data['item_id'] ??
|
||||||
|
data['resourceId'] ??
|
||||||
|
data['resource_id'] ??
|
||||||
|
reward['rewardItemId'] ??
|
||||||
|
reward['reward_item_id'] ??
|
||||||
|
reward['itemId'] ??
|
||||||
|
reward['item_id'] ??
|
||||||
|
reward['resourceId'] ??
|
||||||
|
reward['resource_id'] ??
|
||||||
|
reward['id'] ??
|
||||||
|
reward['amount'] ??
|
||||||
|
reward['content'] ??
|
||||||
|
resource['id'],
|
||||||
|
),
|
||||||
|
rewardName: _stringValue(
|
||||||
|
data['rewardName'] ??
|
||||||
|
data['reward_name'] ??
|
||||||
|
data['name'] ??
|
||||||
|
data['badgeName'] ??
|
||||||
|
data['badge_name'] ??
|
||||||
|
reward['rewardName'] ??
|
||||||
|
reward['reward_name'] ??
|
||||||
|
reward['name'] ??
|
||||||
|
reward['badgeName'] ??
|
||||||
|
reward['badge_name'] ??
|
||||||
|
resource['name'] ??
|
||||||
|
resource['badgeName'] ??
|
||||||
|
resource['badge_name'],
|
||||||
|
),
|
||||||
|
rewardCover: _firstAssetString([
|
||||||
|
data['rewardCover'],
|
||||||
|
data['reward_cover'],
|
||||||
|
data['cover'],
|
||||||
|
data['sourceUrl'],
|
||||||
|
data['source_url'],
|
||||||
|
data['iconUrl'],
|
||||||
|
data['icon_url'],
|
||||||
|
reward['rewardCover'],
|
||||||
|
reward['reward_cover'],
|
||||||
|
reward['cover'],
|
||||||
|
reward['sourceUrl'],
|
||||||
|
reward['source_url'],
|
||||||
|
reward['iconUrl'],
|
||||||
|
reward['icon_url'],
|
||||||
|
resource['cover'],
|
||||||
|
resource['sourceUrl'],
|
||||||
|
resource['source_url'],
|
||||||
|
resource['iconUrl'],
|
||||||
|
resource['icon_url'],
|
||||||
|
]),
|
||||||
|
rewardAmount: _rewardAmountValue(rewardAmountSource) ?? 0,
|
||||||
rocketIconUrl: _firstAssetString([
|
rocketIconUrl: _firstAssetString([
|
||||||
data['rocket_icon_url'],
|
data['rocket_icon_url'],
|
||||||
data['rocketIconUrl'],
|
data['rocketIconUrl'],
|
||||||
@ -1110,7 +1420,7 @@ class SCRoomRocketRewardRecordRes {
|
|||||||
_asMap(data['config'])['rocketIconUrl'],
|
_asMap(data['config'])['rocketIconUrl'],
|
||||||
_asMap(data['config'])['rocketIcon'],
|
_asMap(data['config'])['rocketIcon'],
|
||||||
]),
|
]),
|
||||||
expireDays: _dayValue(data['expireDays'] ?? data['expire_days']),
|
expireDays: _dayValue(expireDaysSource),
|
||||||
grantStatus: _stringValue(data['grantStatus'] ?? data['grant_status']),
|
grantStatus: _stringValue(data['grantStatus'] ?? data['grant_status']),
|
||||||
popupStatus: _stringValue(data['popupStatus'] ?? data['popup_status']),
|
popupStatus: _stringValue(data['popupStatus'] ?? data['popup_status']),
|
||||||
grantTime: _stringValue(data['grantTime'] ?? data['grant_time']),
|
grantTime: _stringValue(data['grantTime'] ?? data['grant_time']),
|
||||||
@ -1253,6 +1563,9 @@ bool _looksLikeRewardRecord(Map<String, dynamic> data) {
|
|||||||
'reward_cover',
|
'reward_cover',
|
||||||
'rewardAmount',
|
'rewardAmount',
|
||||||
'reward_amount',
|
'reward_amount',
|
||||||
|
'reward',
|
||||||
|
'rewardInfo',
|
||||||
|
'reward_info',
|
||||||
'grantStatus',
|
'grantStatus',
|
||||||
'grant_status',
|
'grant_status',
|
||||||
'popupStatus',
|
'popupStatus',
|
||||||
@ -1420,6 +1733,15 @@ num? _rewardAmountValue(dynamic value) {
|
|||||||
return _numValue(value) ?? _dayNumberValue(value);
|
return _numValue(value) ?? _dayNumberValue(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool _isGoldRewardType(String value) {
|
||||||
|
final normalized = value.trim().toUpperCase();
|
||||||
|
return normalized == 'GOLD' ||
|
||||||
|
normalized == 'COIN' ||
|
||||||
|
normalized == 'COINS' ||
|
||||||
|
normalized == 'GOLD_COIN' ||
|
||||||
|
normalized == 'GOLD_COINS';
|
||||||
|
}
|
||||||
|
|
||||||
int _dayValue(dynamic value, {int fallback = 0}) {
|
int _dayValue(dynamic value, {int fallback = 0}) {
|
||||||
final numeric = _numValue(value) ?? _dayNumberValue(value);
|
final numeric = _numValue(value) ?? _dayNumberValue(value);
|
||||||
return numeric?.toInt() ?? fallback;
|
return numeric?.toInt() ?? fallback;
|
||||||
|
|||||||
@ -937,10 +937,21 @@ class SCChatRoomRepository implements SocialChatRoomRepository {
|
|||||||
|
|
||||||
///room/setting/kick-off-microphone
|
///room/setting/kick-off-microphone
|
||||||
@override
|
@override
|
||||||
Future<bool> kickOffMicrophone(String roomId, String kickUserId) async {
|
Future<bool> kickOffMicrophone(
|
||||||
|
String roomId,
|
||||||
|
String kickUserId, {
|
||||||
|
num? mickIndex,
|
||||||
|
}) async {
|
||||||
|
final data = <String, dynamic>{
|
||||||
|
"roomId": roomId,
|
||||||
|
"kickUserId": kickUserId,
|
||||||
|
"userId": kickUserId,
|
||||||
|
if (mickIndex != null) "mickIndex": mickIndex,
|
||||||
|
if (mickIndex != null) "micIndex": mickIndex,
|
||||||
|
};
|
||||||
final result = await http.post<bool>(
|
final result = await http.post<bool>(
|
||||||
"a4465894e21be08d833ef701566ffe1b6a835338046b35fb94271445c996f6e3574aff257ce7e668d08f4caccd1c6232",
|
"a4465894e21be08d833ef701566ffe1b6a835338046b35fb94271445c996f6e3574aff257ce7e668d08f4caccd1c6232",
|
||||||
data: {"roomId": roomId, "kickUserId": kickUserId},
|
data: data,
|
||||||
fromJson: (json) => json as bool,
|
fromJson: (json) => json as bool,
|
||||||
);
|
);
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@ -75,6 +75,9 @@ SCCpRelationPairInfo? _relationInfoIfContainsBoth(
|
|||||||
SocialChatUserProfile left,
|
SocialChatUserProfile left,
|
||||||
SocialChatUserProfile right,
|
SocialChatUserProfile right,
|
||||||
) {
|
) {
|
||||||
|
if (_isInactiveRelationStatus(relation.status)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
final leftKeys = _profileKeys(left);
|
final leftKeys = _profileKeys(left);
|
||||||
final rightKeys = _profileKeys(right);
|
final rightKeys = _profileKeys(right);
|
||||||
if (leftKeys.isEmpty || rightKeys.isEmpty) {
|
if (leftKeys.isEmpty || rightKeys.isEmpty) {
|
||||||
@ -92,6 +95,21 @@ SCCpRelationPairInfo? _relationInfoIfContainsBoth(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool _isInactiveRelationStatus(String? status) {
|
||||||
|
final normalizedStatus = status?.trim().toUpperCase() ?? "";
|
||||||
|
return const <String>{
|
||||||
|
"NONE",
|
||||||
|
"INACTIVE",
|
||||||
|
"DISMISSED",
|
||||||
|
"DISSOLVED",
|
||||||
|
"CANCELLED",
|
||||||
|
"CANCELED",
|
||||||
|
"EXPIRED",
|
||||||
|
"ENDED",
|
||||||
|
"REJECTED",
|
||||||
|
}.contains(normalizedStatus);
|
||||||
|
}
|
||||||
|
|
||||||
bool _hasRelationIdentity(SocialChatUserProfile? profile) =>
|
bool _hasRelationIdentity(SocialChatUserProfile? profile) =>
|
||||||
_profileKeys(profile).isNotEmpty;
|
_profileKeys(profile).isNotEmpty;
|
||||||
|
|
||||||
|
|||||||
@ -29,6 +29,58 @@ class RoomRocketRewardUserProfile {
|
|||||||
class RoomRocketApiMapper {
|
class RoomRocketApiMapper {
|
||||||
const RoomRocketApiMapper._();
|
const RoomRocketApiMapper._();
|
||||||
|
|
||||||
|
static List<List<SCRoomRocketRewardRecordRes>> rewardPopupRecordGroups(
|
||||||
|
List<SCRoomRocketRewardRecordRes> records,
|
||||||
|
) {
|
||||||
|
if (records.isEmpty) {
|
||||||
|
return const <List<SCRoomRocketRewardRecordRes>>[];
|
||||||
|
}
|
||||||
|
final order = <String>[];
|
||||||
|
final groups = <String, List<SCRoomRocketRewardRecordRes>>{};
|
||||||
|
for (var index = 0; index < records.length; index += 1) {
|
||||||
|
final record = records[index];
|
||||||
|
final launchNo = record.launchNo.trim();
|
||||||
|
final id = record.id.trim();
|
||||||
|
final key =
|
||||||
|
launchNo.isNotEmpty
|
||||||
|
? 'launch:$launchNo'
|
||||||
|
: id.isNotEmpty
|
||||||
|
? 'record:$id'
|
||||||
|
: 'index:$index';
|
||||||
|
if (!groups.containsKey(key)) {
|
||||||
|
order.add(key);
|
||||||
|
groups[key] = <SCRoomRocketRewardRecordRes>[];
|
||||||
|
}
|
||||||
|
groups[key]!.add(record);
|
||||||
|
}
|
||||||
|
return order
|
||||||
|
.map(
|
||||||
|
(key) => List<SCRoomRocketRewardRecordRes>.unmodifiable(groups[key]!),
|
||||||
|
)
|
||||||
|
.toList(growable: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<SCRoomRocketRewardRecordRes> rewardRoomRecordsForPopupGroup(
|
||||||
|
List<SCRoomRocketRewardRecordRes> roomRecords,
|
||||||
|
List<SCRoomRocketRewardRecordRes> popupRecords,
|
||||||
|
) {
|
||||||
|
if (roomRecords.isEmpty || popupRecords.isEmpty) {
|
||||||
|
return roomRecords;
|
||||||
|
}
|
||||||
|
final launchNos =
|
||||||
|
popupRecords
|
||||||
|
.map((record) => record.launchNo.trim())
|
||||||
|
.where((item) => item.isNotEmpty)
|
||||||
|
.toSet();
|
||||||
|
if (launchNos.isEmpty) {
|
||||||
|
return roomRecords;
|
||||||
|
}
|
||||||
|
final matched = roomRecords
|
||||||
|
.where((record) => launchNos.contains(record.launchNo.trim()))
|
||||||
|
.toList(growable: false);
|
||||||
|
return matched.isEmpty ? roomRecords : matched;
|
||||||
|
}
|
||||||
|
|
||||||
static List<RoomRocketLevelItem>? levelsFromStatus(
|
static List<RoomRocketLevelItem>? levelsFromStatus(
|
||||||
SCRoomRocketStatusRes? status,
|
SCRoomRocketStatusRes? status,
|
||||||
) {
|
) {
|
||||||
|
|||||||
@ -88,12 +88,12 @@ class _RoomRocketRewardDialogLoaderState
|
|||||||
if (useInitialPopupRecords) {
|
if (useInitialPopupRecords) {
|
||||||
_usedInitialPopupRecords = true;
|
_usedInitialPopupRecords = true;
|
||||||
}
|
}
|
||||||
final launchNo = _launchNoFromRecords(
|
final launchNos = _launchNosFromRecords(
|
||||||
nextPopupRecords.isNotEmpty ? nextPopupRecords : _popupRecords,
|
nextPopupRecords.isNotEmpty ? nextPopupRecords : _popupRecords,
|
||||||
);
|
);
|
||||||
final nextRoomRecords = await _loadRoomRewardRecords(
|
final nextRoomRecordPage = await _loadRoomRewardRecordPage(
|
||||||
requestCursor,
|
requestCursor,
|
||||||
launchNo: launchNo,
|
launchNos: launchNos,
|
||||||
);
|
);
|
||||||
if (!mounted) {
|
if (!mounted) {
|
||||||
return;
|
return;
|
||||||
@ -103,17 +103,17 @@ class _RoomRocketRewardDialogLoaderState
|
|||||||
_replaceUniqueRewardRecords(_popupRecords, nextPopupRecords);
|
_replaceUniqueRewardRecords(_popupRecords, nextPopupRecords);
|
||||||
_replaceUniqueRewardRecords(_roomRecords, [
|
_replaceUniqueRewardRecords(_roomRecords, [
|
||||||
...widget.initialRoomRecords,
|
...widget.initialRoomRecords,
|
||||||
...nextRoomRecords,
|
...nextRoomRecordPage.records,
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
_appendUniqueRewardRecords(_roomRecords, nextRoomRecords);
|
_appendUniqueRewardRecords(_roomRecords, nextRoomRecordPage.records);
|
||||||
}
|
}
|
||||||
_rewards = RoomRocketApiMapper.rewardItemsFromRecords(_popupRecords);
|
_rewards = RoomRocketApiMapper.rewardItemsFromRecords(_popupRecords);
|
||||||
_winners = RoomRocketApiMapper.winnersFromRecords(
|
_winners = RoomRocketApiMapper.winnersFromRecords(
|
||||||
_visibleRoomRewardRecords(),
|
_visibleRoomRewardRecords(),
|
||||||
userProfileResolver: widget.userProfileResolver,
|
userProfileResolver: widget.userProfileResolver,
|
||||||
);
|
);
|
||||||
_hasMore = nextRoomRecords.length >= _rewardDialogPageSize;
|
_hasMore = nextRoomRecordPage.hasMore;
|
||||||
if (_hasMore) {
|
if (_hasMore) {
|
||||||
_cursor = requestCursor + 1;
|
_cursor = requestCursor + 1;
|
||||||
}
|
}
|
||||||
@ -125,7 +125,8 @@ class _RoomRocketRewardDialogLoaderState
|
|||||||
'popupCount=${nextPopupRecords.length} '
|
'popupCount=${nextPopupRecords.length} '
|
||||||
'popupTotal=${_popupRecords.length} '
|
'popupTotal=${_popupRecords.length} '
|
||||||
'initialRoomCount=${widget.initialRoomRecords.length} '
|
'initialRoomCount=${widget.initialRoomRecords.length} '
|
||||||
'roomCount=${nextRoomRecords.length} '
|
'launchNos=${launchNos.join(",")} '
|
||||||
|
'roomCount=${nextRoomRecordPage.records.length} '
|
||||||
'roomTotal=${_roomRecords.length} '
|
'roomTotal=${_roomRecords.length} '
|
||||||
'visibleRoomTotal=${_visibleRoomRewardRecords().length} '
|
'visibleRoomTotal=${_visibleRoomRewardRecords().length} '
|
||||||
'hasMore=$_hasMore nextCursor=$_cursor',
|
'hasMore=$_hasMore nextCursor=$_cursor',
|
||||||
@ -167,7 +168,7 @@ class _RoomRocketRewardDialogLoaderState
|
|||||||
try {
|
try {
|
||||||
_rewardDialogDebug(
|
_rewardDialogDebug(
|
||||||
'request reward-room-records roomId=${widget.roomId} '
|
'request reward-room-records roomId=${widget.roomId} '
|
||||||
'cursor=$cursor limit=$_rewardDialogPageSize',
|
'cursor=$cursor limit=$_rewardDialogPageSize launchNo=$launchNo',
|
||||||
);
|
);
|
||||||
final res = await SCChatRoomRepository().roomRocketRewardRecords(
|
final res = await SCChatRoomRepository().roomRocketRewardRecords(
|
||||||
widget.roomId,
|
widget.roomId,
|
||||||
@ -183,6 +184,39 @@ class _RoomRocketRewardDialogLoaderState
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<_RoomRocketRewardRecordPage> _loadRoomRewardRecordPage(
|
||||||
|
int cursor, {
|
||||||
|
required Set<String> launchNos,
|
||||||
|
}) async {
|
||||||
|
final normalizedLaunchNos =
|
||||||
|
launchNos.where((item) => item.trim().isNotEmpty).toSet();
|
||||||
|
if (normalizedLaunchNos.isEmpty) {
|
||||||
|
final records = await _loadRoomRewardRecords(cursor);
|
||||||
|
return _RoomRocketRewardRecordPage(
|
||||||
|
records: records,
|
||||||
|
hasMore: records.length >= _rewardDialogPageSize,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
final pages = await Future.wait(
|
||||||
|
normalizedLaunchNos.map((launchNo) async {
|
||||||
|
final records = await _loadRoomRewardRecords(
|
||||||
|
cursor,
|
||||||
|
launchNo: launchNo,
|
||||||
|
);
|
||||||
|
return _RoomRocketRewardRecordPage(
|
||||||
|
records: records,
|
||||||
|
hasMore: records.length >= _rewardDialogPageSize,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return _RoomRocketRewardRecordPage(
|
||||||
|
records: debugUniqueRoomRocketRewardRecords(
|
||||||
|
pages.expand((page) => page.records).toList(growable: false),
|
||||||
|
),
|
||||||
|
hasMore: pages.any((page) => page.hasMore),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
List<SCRoomRocketRewardRecordRes> _visibleRoomRewardRecords() {
|
List<SCRoomRocketRewardRecordRes> _visibleRoomRewardRecords() {
|
||||||
if (_roomRecords.isEmpty) {
|
if (_roomRecords.isEmpty) {
|
||||||
return _popupRecords;
|
return _popupRecords;
|
||||||
@ -217,14 +251,21 @@ class _RoomRocketRewardDialogLoaderState
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String _launchNoFromRecords(List<SCRoomRocketRewardRecordRes> records) {
|
class _RoomRocketRewardRecordPage {
|
||||||
for (final record in records) {
|
const _RoomRocketRewardRecordPage({
|
||||||
final launchNo = record.launchNo.trim();
|
required this.records,
|
||||||
if (launchNo.isNotEmpty) {
|
required this.hasMore,
|
||||||
return launchNo;
|
});
|
||||||
}
|
|
||||||
}
|
final List<SCRoomRocketRewardRecordRes> records;
|
||||||
return '';
|
final bool hasMore;
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<String> _launchNosFromRecords(List<SCRoomRocketRewardRecordRes> records) {
|
||||||
|
return records
|
||||||
|
.map((record) => record.launchNo.trim())
|
||||||
|
.where((launchNo) => launchNo.isNotEmpty)
|
||||||
|
.toSet();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _replaceUniqueRewardRecords(
|
void _replaceUniqueRewardRecords(
|
||||||
|
|||||||
@ -290,7 +290,7 @@ class _RoomUserInfoCardState extends State<RoomUserInfoCard> {
|
|||||||
required bool canShowSetting,
|
required bool canShowSetting,
|
||||||
}) {
|
}) {
|
||||||
final hasCpRelation = _hasCpRelation(profile);
|
final hasCpRelation = _hasCpRelation(profile);
|
||||||
final closeFriends = _closeFriendProfileData(profile).take(5).toList();
|
final closeFriends = _closeFriendProfileData(profile);
|
||||||
|
|
||||||
return Stack(
|
return Stack(
|
||||||
children: [
|
children: [
|
||||||
@ -790,7 +790,8 @@ class _RoomUserInfoCardState extends State<RoomUserInfoCard> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<String> _closeFriendProfileData(SocialChatUserProfile? profile) {
|
List<String> _closeFriendProfileData(SocialChatUserProfile? profile) {
|
||||||
final avatars = <String>[];
|
final brotherAvatars = <String>[];
|
||||||
|
final sisterAvatars = <String>[];
|
||||||
for (final relation in profile?.closeFriendList ?? const <CPRes>[]) {
|
for (final relation in profile?.closeFriendList ?? const <CPRes>[]) {
|
||||||
final type = _normalizeCpRelationType(relation.relationType);
|
final type = _normalizeCpRelationType(relation.relationType);
|
||||||
if (type != "BROTHER" && type != "SISTERS") {
|
if (type != "BROTHER" && type != "SISTERS") {
|
||||||
@ -802,10 +803,14 @@ class _RoomUserInfoCardState extends State<RoomUserInfoCard> {
|
|||||||
final friend = _relationPartner(profile, relation);
|
final friend = _relationPartner(profile, relation);
|
||||||
final avatar = friend.avatarUrl?.trim() ?? "";
|
final avatar = friend.avatarUrl?.trim() ?? "";
|
||||||
if (avatar.isNotEmpty) {
|
if (avatar.isNotEmpty) {
|
||||||
avatars.add(avatar);
|
if (type == "BROTHER" && brotherAvatars.length < 3) {
|
||||||
|
brotherAvatars.add(avatar);
|
||||||
|
} else if (type == "SISTERS" && sisterAvatars.length < 3) {
|
||||||
|
sisterAvatars.add(avatar);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return avatars;
|
return <String>[...brotherAvatars, ...sisterAvatars];
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildCloseFriendAvatar(String avatarUrl) {
|
Widget _buildCloseFriendAvatar(String avatarUrl) {
|
||||||
@ -1525,6 +1530,16 @@ class _RoomUserInfoCardState extends State<RoomUserInfoCard> {
|
|||||||
partnerUserId,
|
partnerUserId,
|
||||||
relationType: cp.relationType,
|
relationType: cp.relationType,
|
||||||
);
|
);
|
||||||
|
if (context.mounted) {
|
||||||
|
Provider.of<RtcProvider?>(
|
||||||
|
context,
|
||||||
|
listen: false,
|
||||||
|
)?.invalidateRoomMicRelationBetween(
|
||||||
|
userId: cp.leftUserId,
|
||||||
|
peerUserId: partnerUserId,
|
||||||
|
relationType: cp.relationType,
|
||||||
|
);
|
||||||
|
}
|
||||||
await userProvider?.refreshLoadedCpProfiles();
|
await userProvider?.refreshLoadedCpProfiles();
|
||||||
await SmartDialog.dismiss(tag: RoomCpProgressDialog.dialogTag);
|
await SmartDialog.dismiss(tag: RoomCpProgressDialog.dialogTag);
|
||||||
SCTts.show(l10n.operationSuccessful);
|
SCTts.show(l10n.operationSuccessful);
|
||||||
|
|||||||
@ -78,6 +78,34 @@ void main() {
|
|||||||
expect(text, isNot(contains('4. Leaderboard')));
|
expect(text, isNot(contains('4. Leaderboard')));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('groups unread reward popups by launch number', () {
|
||||||
|
final groups = RoomRocketApiMapper.rewardPopupRecordGroups([
|
||||||
|
_rewardRecord(id: 'r1', launchNo: 'launch-a'),
|
||||||
|
_rewardRecord(id: 'r2', launchNo: 'launch-a'),
|
||||||
|
_rewardRecord(id: 'r3', launchNo: 'launch-b'),
|
||||||
|
_rewardRecord(id: 'r4', launchNo: ''),
|
||||||
|
_rewardRecord(id: '', launchNo: ''),
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(groups.map((group) => group.length), [2, 1, 1, 1]);
|
||||||
|
expect(groups.first.map((record) => record.id), ['r1', 'r2']);
|
||||||
|
expect(groups[1].single.launchNo, 'launch-b');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('matches room winner records to the current popup batch', () {
|
||||||
|
final roomRecords = [
|
||||||
|
_rewardRecord(id: 'room-a', launchNo: 'launch-a'),
|
||||||
|
_rewardRecord(id: 'room-b', launchNo: 'launch-b'),
|
||||||
|
];
|
||||||
|
final matched = RoomRocketApiMapper.rewardRoomRecordsForPopupGroup(
|
||||||
|
roomRecords,
|
||||||
|
[_rewardRecord(id: 'popup-b', launchNo: 'launch-b')],
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(matched, hasLength(1));
|
||||||
|
expect(matched.single.id, 'room-b');
|
||||||
|
});
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'parses level-specific reward previews and rocket kings from levels',
|
'parses level-specific reward previews and rocket kings from levels',
|
||||||
() {
|
() {
|
||||||
@ -494,6 +522,30 @@ void main() {
|
|||||||
expect(winners.last.rewards.single.amount, '200');
|
expect(winners.last.rewards.single.amount, '200');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('parses nested rocket reward record resource payloads', () {
|
||||||
|
final record = SCRoomRocketRewardRecordRes.fromJson({
|
||||||
|
'id': 'r1',
|
||||||
|
'launchNo': 'launch-1',
|
||||||
|
'roomId': 'room-1',
|
||||||
|
'userInfo': {'id': 'u1', 'nickname': 'winner'},
|
||||||
|
'reward': {
|
||||||
|
'detailType': 'AVATAR_FRAME',
|
||||||
|
'amount': '1969014438069481474',
|
||||||
|
'quantity': 7,
|
||||||
|
'name': 'yumi-KingTOP1 frame',
|
||||||
|
'cover': {'sourceUrl': 'https://example.com/frame.png'},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
final reward = RoomRocketApiMapper.rewardItemsFromRecords([record]).single;
|
||||||
|
|
||||||
|
expect(record.rewardType, 'AVATAR_FRAME');
|
||||||
|
expect(record.rewardItemId, isNotEmpty);
|
||||||
|
expect(record.rewardName, 'yumi-KingTOP1 frame');
|
||||||
|
expect(record.rewardCover, 'https://example.com/frame.png');
|
||||||
|
expect(record.expireDays, 7);
|
||||||
|
expect(reward.displayText, '7d');
|
||||||
|
});
|
||||||
|
|
||||||
test('parses rocket level contents response from go api', () {
|
test('parses rocket level contents response from go api', () {
|
||||||
final contents = SCRoomRocketLevelContentsRes.fromJson({
|
final contents = SCRoomRocketLevelContentsRes.fromJson({
|
||||||
'configured': true,
|
'configured': true,
|
||||||
@ -565,3 +617,26 @@ void main() {
|
|||||||
expect(groups?.setOff.single.amount, '200');
|
expect(groups?.setOff.single.amount, '200');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SCRoomRocketRewardRecordRes _rewardRecord({
|
||||||
|
required String id,
|
||||||
|
required String launchNo,
|
||||||
|
}) {
|
||||||
|
return SCRoomRocketRewardRecordRes.fromJson({
|
||||||
|
'id': id,
|
||||||
|
'launchNo': launchNo,
|
||||||
|
'roomId': 'room-1',
|
||||||
|
'dayKey': '2026-05-15',
|
||||||
|
'roundNo': 1,
|
||||||
|
'level': 3,
|
||||||
|
'userId': 'user-1',
|
||||||
|
'userNickname': 'User 1',
|
||||||
|
'rewardScene': 'IN_ROOM',
|
||||||
|
'rewardType': 'GOLD',
|
||||||
|
'rewardItemId': 'gold',
|
||||||
|
'rewardName': 'Yumi',
|
||||||
|
'rewardAmount': 100,
|
||||||
|
'grantStatus': 'SUCCESS',
|
||||||
|
'popupStatus': 'UNREAD',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@ -134,6 +134,49 @@ void main() {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
test('micChange keeps a recent up-mic snapshot over stale mic list data', () {
|
||||||
|
final rtcProvider = RealTimeCommunicationManager();
|
||||||
|
final user = SocialChatUserProfile(id: 'user-1', userNickname: 'Alice');
|
||||||
|
|
||||||
|
rtcProvider.micChange([MicRes(micIndex: 0, user: user)]);
|
||||||
|
expect(rtcProvider.roomWheatMap[0]?.user?.id, 'user-1');
|
||||||
|
|
||||||
|
rtcProvider.debugApplyMicListSnapshot([
|
||||||
|
MicRes(micIndex: 0),
|
||||||
|
], notifyIfUnchanged: false);
|
||||||
|
|
||||||
|
expect(rtcProvider.roomWheatMap[0]?.user?.id, 'user-1');
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
'micChange keeps a recent down-mic snapshot over stale mic list data',
|
||||||
|
() {
|
||||||
|
final rtcProvider =
|
||||||
|
RealTimeCommunicationManager()
|
||||||
|
..roomWheatMap = {
|
||||||
|
0: MicRes(
|
||||||
|
micIndex: 0,
|
||||||
|
user: SocialChatUserProfile(
|
||||||
|
id: 'user-1',
|
||||||
|
userNickname: 'Alice',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
rtcProvider.micChange([MicRes(micIndex: 0)]);
|
||||||
|
expect(rtcProvider.roomWheatMap[0]?.user, isNull);
|
||||||
|
|
||||||
|
rtcProvider.debugApplyMicListSnapshot([
|
||||||
|
MicRes(
|
||||||
|
micIndex: 0,
|
||||||
|
user: SocialChatUserProfile(id: 'user-1', userNickname: 'Alice'),
|
||||||
|
),
|
||||||
|
], notifyIfUnchanged: false);
|
||||||
|
|
||||||
|
expect(rtcProvider.roomWheatMap[0]?.user, isNull);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
testWidgets('RoomSeatWidget derives seat count from highest mic index', (
|
testWidgets('RoomSeatWidget derives seat count from highest mic index', (
|
||||||
tester,
|
tester,
|
||||||
) async {
|
) async {
|
||||||
|
|||||||
@ -78,6 +78,23 @@ void main() {
|
|||||||
|
|
||||||
expect(scCpRelationTypeBetweenProfiles(left, right), isNull);
|
expect(scCpRelationTypeBetweenProfiles(left, right), isNull);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('ignores inactive relation payloads', () {
|
||||||
|
final left = SocialChatUserProfile(
|
||||||
|
id: '100',
|
||||||
|
cpList: [
|
||||||
|
CPRes(
|
||||||
|
meUserId: '100',
|
||||||
|
cpUserId: '200',
|
||||||
|
relationType: 'CP',
|
||||||
|
status: 'DISMISSED',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
final right = SocialChatUserProfile(id: '200');
|
||||||
|
|
||||||
|
expect(scCpRelationTypeBetweenProfiles(left, right), isNull);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
group('scCpRelationLevelTextFromCabin', () {
|
group('scCpRelationLevelTextFromCabin', () {
|
||||||
|
|||||||
@ -55,6 +55,24 @@ void main() {
|
|||||||
expect(cloud.localAudioMuted, isTrue);
|
expect(cloud.localAudioMuted, isTrue);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('keeps music publishing while microphone voice is muted', () async {
|
||||||
|
final cloud = _FakeTrtcCloud();
|
||||||
|
final adapter = TrtcRoomRtcEngineAdapter(cloudFactory: () async => cloud);
|
||||||
|
|
||||||
|
await _enter(adapter);
|
||||||
|
expect(
|
||||||
|
await adapter.switchToAnchor(
|
||||||
|
const RoomRtcAnchorConfig(muted: true, publishMuted: false),
|
||||||
|
),
|
||||||
|
isTrue,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(cloud.lastRole, TRTCRoleType.anchor);
|
||||||
|
expect(cloud.localAudioStarted, isTrue);
|
||||||
|
expect(cloud.localAudioMuted, isFalse);
|
||||||
|
expect(cloud.audioEffectManager.voiceCaptureVolume, 0);
|
||||||
|
});
|
||||||
|
|
||||||
test('maps TRTC music calls to AudioEffectManager', () async {
|
test('maps TRTC music calls to AudioEffectManager', () async {
|
||||||
final cloud = _FakeTrtcCloud();
|
final cloud = _FakeTrtcCloud();
|
||||||
final adapter = TrtcRoomRtcEngineAdapter(cloudFactory: () async => cloud);
|
final adapter = TrtcRoomRtcEngineAdapter(cloudFactory: () async => cloud);
|
||||||
@ -197,6 +215,7 @@ class _FakeAudioEffectManager implements RoomTrtcAudioEffectManager {
|
|||||||
final resumedIds = <int>[];
|
final resumedIds = <int>[];
|
||||||
int? playoutVolume;
|
int? playoutVolume;
|
||||||
int? publishVolume;
|
int? publishVolume;
|
||||||
|
int? voiceCaptureVolume;
|
||||||
int position = 0;
|
int position = 0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -233,6 +252,11 @@ class _FakeAudioEffectManager implements RoomTrtcAudioEffectManager {
|
|||||||
playoutVolume = volume;
|
playoutVolume = volume;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void setVoiceCaptureVolume(int volume) {
|
||||||
|
voiceCaptureVolume = volume;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int getMusicCurrentPosInMS(int id) {
|
int getMusicCurrentPosInMS(int id) {
|
||||||
return position;
|
return position;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user