This commit is contained in:
roxy 2026-04-30 10:11:30 +08:00
parent fd445ccdaa
commit 36ba0f3573
16 changed files with 626 additions and 108 deletions

View File

@ -502,7 +502,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 7;
CURRENT_PROJECT_VERSION = 8;
DEVELOPMENT_TEAM = S9X2AJ2US9;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
@ -511,7 +511,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.2.5;
MARKETING_VERSION = 1.2.6;
PRODUCT_BUNDLE_IDENTIFIER = com.org.yumiparty;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -693,7 +693,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/RunnerDebug.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 7;
CURRENT_PROJECT_VERSION = 8;
DEVELOPMENT_TEAM = F33K8VUZ62;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
@ -702,7 +702,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.2.5;
MARKETING_VERSION = 1.2.6;
PRODUCT_BUNDLE_IDENTIFIER = com.org.yumiparty;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -722,7 +722,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/RunnerRelease.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 7;
CURRENT_PROJECT_VERSION = 8;
DEVELOPMENT_TEAM = F33K8VUZ62;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
@ -731,7 +731,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.2.5;
MARKETING_VERSION = 1.2.6;
PRODUCT_BUNDLE_IDENTIFIER = com.org.yumiparty;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";

View File

@ -272,8 +272,7 @@ class _RoomFollowPageState
Provider.of<RtcProvider>(context, listen: false).joinVoiceRoomSession(
context,
roomRes.roomProfile?.id ?? "",
previewSeatCount:
roomRes.roomProfile?.extValues?.roomSetting?.mikeSize?.toInt(),
previewData: RoomEntryPreviewData.fromFollowRoom(roomRes),
);
},
);

View File

@ -272,8 +272,7 @@ class _SCRoomHistoryPageState
Provider.of<RtcProvider>(context, listen: false).joinVoiceRoomSession(
context,
roomRes.roomProfile?.id ?? "",
previewSeatCount:
roomRes.roomProfile?.extValues?.roomSetting?.mikeSize?.toInt(),
previewData: RoomEntryPreviewData.fromFollowRoom(roomRes),
);
},
);

View File

@ -238,23 +238,26 @@ class _HomeMinePageState extends State<SCHomeMinePage>
],
),
onTap: () {
String roomId =
Provider.of<SocialChatRoomManager>(
context,
listen: false,
).myRoom?.id ??
"";
final roomManager = Provider.of<SocialChatRoomManager>(
context,
listen: false,
);
final myRoom = roomManager.myRoom;
final roomId = myRoom?.id ?? "";
Provider.of<RealTimeCommunicationManager>(
context,
listen: false,
).joinVoiceRoomSession(
context,
roomId,
previewSeatCount:
Provider.of<SocialChatRoomManager>(
context,
listen: false,
).myRoom?.setting?.mikeSize?.toInt(),
previewData:
myRoom == null
? null
: RoomEntryPreviewData.fromMyRoom(
myRoom,
ownerProfile:
AccountStorage().getCurrentUser()?.userProfile,
),
);
},
)

View File

@ -15,6 +15,7 @@ import '../../../../shared/business_logic/models/res/follow_room_res.dart';
import '../../../../shared/business_logic/models/res/room_res.dart';
import '../../../../services/audio/rtc_manager.dart';
import '../../../../services/general/sc_app_general_manager.dart';
import '../../../../services/home/home_room_preload_manager.dart';
import '../../../../ui_kit/components/sc_compontent.dart';
import '../../../../ui_kit/components/text/sc_text.dart';
import '../../../../ui_kit/widgets/room/room_live_audio_indicator.dart';
@ -122,7 +123,7 @@ class _HomePartyPageState extends State<SCHomePartyPage>
enablePullUp: false,
controller: _refreshController,
onRefresh: () {
loadData();
loadData(forceRefresh: true);
},
onLoading: () {},
child: SingleChildScrollView(
@ -968,20 +969,20 @@ class _HomePartyPageState extends State<SCHomePartyPage>
);
}
loadData() {
final generalManager = Provider.of<SCAppGeneralManager>(
context,
listen: false,
);
setState(() {
isLoading = true;
_isBannerLoading =
generalManager.exploreBanners.isEmpty &&
generalManager.homeBanners.isEmpty;
_isLeaderboardLoading = generalManager.appLeaderResult == null;
});
SCChatRoomRepository()
.discovery(allRegion: true)
loadData({bool forceRefresh = false}) {
final generalManager = Provider.of<SCAppGeneralManager>(
context,
listen: false,
);
setState(() {
isLoading = true;
_isBannerLoading =
generalManager.exploreBanners.isEmpty &&
generalManager.homeBanners.isEmpty;
_isLeaderboardLoading = generalManager.appLeaderResult == null;
});
SCHomeRoomPreloadManager.instance
.loadPartyRooms(forceRefresh: forceRefresh)
.then((values) {
rooms = _mergeLatestRoomsWithCurrentCounters(values);
isLoading = false;
@ -1020,7 +1021,9 @@ class _HomePartyPageState extends State<SCHomePartyPage>
_buildItem(SocialChatRoomRes res, int index) {
final rankBorderAsset =
index < _topRankBorderAssets.length ? _topRankBorderAssets[index] : null;
index < _topRankBorderAssets.length
? _topRankBorderAssets[index]
: null;
final rankBorderOverflow = 12.w;
final rankInfoHorizontalInset = rankBorderAsset != null ? 10.w : 0.w;
final rankInfoBottomInset = rankBorderAsset != null ? 9.w : 0.w;
@ -1177,7 +1180,7 @@ class _HomePartyPageState extends State<SCHomePartyPage>
Provider.of<RtcProvider>(context, listen: false).joinVoiceRoomSession(
context,
res.id ?? "",
previewSeatCount: res.extValues?.roomSetting?.mikeSize?.toInt(),
previewData: RoomEntryPreviewData.fromSocialChatRoom(res),
);
},
);

View File

@ -591,6 +591,24 @@ class _RoomEditPageState extends State<RoomEditPage> {
rtcProvider?.currenRoom?.roomProfile?.roomProfile?.id ??
"",
clearRoomData: true,
previewData: RoomEntryPreviewData(
roomId:
mergedRoomInfo.id ??
rtcProvider?.currenRoom?.roomProfile?.roomProfile?.id,
roomAccount:
rtcProvider?.currenRoom?.roomProfile?.roomProfile?.roomAccount,
userId: rtcProvider?.currenRoom?.roomProfile?.roomProfile?.userId,
roomCover: mergedRoomInfo.roomCover,
roomName: mergedRoomInfo.roomName ?? submittedRoomName,
roomDesc: mergedRoomInfo.roomDesc ?? submittedRoomDesc,
roomSetting: rtcProvider?.currenRoom?.roomProfile?.roomSetting,
ownerProfile: rtcProvider?.currenRoom?.roomProfile?.userProfile,
roomAdminCount:
rtcProvider?.currenRoom?.roomProfile?.roomCounter?.adminCount,
roomMemberCount:
rtcProvider?.currenRoom?.roomProfile?.roomCounter?.memberCount,
entrantsRole: rtcProvider?.currenRoom?.entrants?.roles,
),
);
} else {
SCLoadingManager.hide();

View File

@ -65,6 +65,8 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
final Map<String, _LuckyGiftComboSession> _luckyGiftComboSessions =
<String, _LuckyGiftComboSession>{};
int _shownRoomStartupFailureToken = 0;
RtcProvider? _rtcProvider;
bool _roomProviderRebuildScheduled = false;
@override
void initState() {
@ -90,11 +92,20 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
@override
void didChangeDependencies() {
super.didChangeDependencies();
final rtcProvider = context.read<RtcProvider>();
if (!identical(_rtcProvider, rtcProvider)) {
_rtcProvider?.removeListener(_handleRoomProviderChanged);
_rtcProvider = rtcProvider;
rtcProvider.addListener(_handleRoomProviderChanged);
}
_ensureRoomVisualEffectsEnabled();
}
@override
void dispose() {
_rtcProvider?.removeListener(_handleRoomProviderChanged);
_rtcProvider = null;
_roomProviderRebuildScheduled = false;
_suspendRoomVisualEffects();
_tabController.removeListener(_handleTabChange);
_tabController.dispose(); //
@ -109,6 +120,20 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
setState(() {});
}
void _handleRoomProviderChanged() {
if (!mounted || _roomProviderRebuildScheduled) {
return;
}
_roomProviderRebuildScheduled = true;
WidgetsBinding.instance.addPostFrameCallback((_) {
_roomProviderRebuildScheduled = false;
if (!mounted) {
return;
}
setState(() {});
});
}
void _enableRoomVisualEffects() {
Provider.of<RtcProvider>(
context,

View File

@ -20,6 +20,9 @@ class VoiceRoomRoute implements SCIRouterProvider {
static String roomBackgroundPreview = '/room/background/preview';
static String roomBackgroundUpload = '/room/background/upload';
static String roomMusic = '/room/music';
static int _voiceRoomRouteDepth = 0;
static bool get isVoiceRoomOpen => _voiceRoomRouteDepth > 0;
static Route<T> _buildRoute<T>(Widget page, {RouteSettings? settings}) {
if (Platform.isIOS) {
@ -54,14 +57,26 @@ class VoiceRoomRoute implements SCIRouterProvider {
bool rootNavigator = false,
}) {
final navigator = Navigator.of(context, rootNavigator: rootNavigator);
if (!replace && isVoiceRoomOpen) {
debugPrint('[RoomRoute] skip duplicate voice room route');
return Future<T?>.value();
}
final route = _buildRoute<T>(
const VoiceRoomPage(),
settings: RouteSettings(name: voiceRoom),
);
_voiceRoomRouteDepth = replace ? 1 : _voiceRoomRouteDepth + 1;
Future<T?> routeFuture;
if (replace) {
return navigator.pushReplacement<T, T>(route);
routeFuture = navigator.pushReplacement<T, T>(route);
} else {
routeFuture = navigator.push<T>(route);
}
return navigator.push<T>(route);
return routeFuture.whenComplete(() {
if (_voiceRoomRouteDepth > 0) {
_voiceRoomRouteDepth -= 1;
}
});
}
static Future<T?> openRoomEdit<T>(

View File

@ -434,13 +434,11 @@ class _SearchRoomListState extends State<SearchRoomList> {
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () async {
if (context != null) {
Provider.of<RtcProvider>(context, listen: false).joinVoiceRoomSession(
context,
e.id ?? "",
previewSeatCount: e.extValues?.roomSetting?.mikeSize?.toInt(),
);
}
Provider.of<RtcProvider>(context, listen: false).joinVoiceRoomSession(
context,
e.id ?? "",
previewData: RoomEntryPreviewData.fromSocialChatRoom(e),
);
},
child: Stack(
alignment: Alignment.bottomCenter,
@ -544,9 +542,9 @@ class _SearchRoomListState extends State<SearchRoomList> {
width: 20.w,
height: 20.w,
),
(e.extValues?.roomSetting?.password?.isEmpty ?? false)
? SizedBox(width: 3.w)
: Container(height: 10.w),
(e.extValues?.roomSetting?.password?.isEmpty ?? false)
? SizedBox(width: 3.w)
: Container(height: 10.w),
(e.extValues?.roomSetting?.password?.isEmpty ?? false)
? text(
e.displayMemberCount,

View File

@ -14,6 +14,7 @@ import 'package:yumi/shared/data_sources/sources/local/user_manager.dart';
import 'package:yumi/shared/business_logic/models/res/login_res.dart';
import 'package:yumi/app/routes/sc_routes.dart';
import 'package:yumi/app/routes/sc_fluro_navigator.dart';
import 'package:yumi/services/home/home_room_preload_manager.dart';
import 'package:yumi/shared/data_sources/sources/local/file_cache_manager.dart';
import 'package:yumi/ui_kit/components/sc_compontent.dart';
@ -95,6 +96,7 @@ class _SplashPageState extends State<SplashPage> {
_selectedVariant = _pickSplashVariant();
WidgetsBinding.instance.addPostFrameCallback((_) {
unawaited(FileCacheManager.getInstance().getFilePath());
SCHomeRoomPreloadManager.instance.preloadInitialHomeRooms();
unawaited(WeeklyStarSplashCache.refreshCacheInBackground());
unawaited(LastWeeklyCPSplashCache.refreshCacheInBackground());
});

View File

@ -7,7 +7,6 @@ import 'package:yumi/app_localizations.dart';
import 'package:yumi/modules/index/main_route.dart';
import 'package:yumi/modules/store/store_route.dart';
import 'package:yumi/modules/user/settings/settings_route.dart';
import 'package:yumi/modules/user/task/task_route.dart';
import 'package:yumi/modules/user/vip/vip_route.dart';
import 'package:yumi/modules/wallet/wallet_route.dart';
import 'package:yumi/services/auth/user_profile_manager.dart';
@ -114,6 +113,14 @@ class _MePage2State extends State<MePage2> {
return repository.vipStatus();
}
void _refreshAfterVipReturn(SocialChatUserProfileManager profileManager) {
debugPrint('[VIP][MeEntry] refresh user data after vip page return');
profileManager.fetchUserProfileData(loadGuardCount: false);
profileManager.balance();
_loadCounter();
_loadVipStatus();
}
void _openInviteActivity() {
final inviteActivityUrl = _appendToken(_inviteActivityUrl);
if (inviteActivityUrl.isEmpty) return;
@ -287,12 +294,6 @@ class _MePage2State extends State<MePage2> {
Widget _buildEntryRow() {
return Row(
children: [
_buildEntryItem(
title: SCAppLocalizations.of(context)!.task,
iconPath: 'sc_images/index/sc_icon_task.png',
onTap: () => SCNavigatorUtils.push(context, TaskRoute.task),
),
SizedBox(width: 10.w),
_buildEntryItem(
title: SCAppLocalizations.of(context)!.store,
iconPath: 'sc_images/index/sc_icon_shop.png',
@ -304,12 +305,6 @@ class _MePage2State extends State<MePage2> {
iconPath: 'sc_images/index/sc_icon_bag.png',
onTap: () => SCNavigatorUtils.push(context, StoreRoute.bags),
),
SizedBox(width: 10.w),
_buildEntryItem(
title: SCAppLocalizations.of(context)!.level,
iconPath: 'sc_images/index/sc_icon_level.png',
onTap: () => SCNavigatorUtils.push(context, SCMainRoute.levelList),
),
],
);
}
@ -408,7 +403,7 @@ class _MePage2State extends State<MePage2> {
onTap: () async {
await SCNavigatorUtils.push(context, VipRoute.detail);
if (mounted) {
_loadVipStatus();
_refreshAfterVipReturn(profileManager);
}
},
child: Container(

View File

@ -27,6 +27,10 @@ import 'package:yumi/shared/data_sources/sources/local/data_persistence.dart';
import 'package:yumi/shared/business_logic/models/res/join_room_res.dart';
import 'package:yumi/shared/business_logic/models/res/login_res.dart';
import 'package:yumi/shared/business_logic/models/res/mic_res.dart';
import 'package:yumi/shared/business_logic/models/res/follow_room_res.dart'
as follow_room;
import 'package:yumi/shared/business_logic/models/res/my_room_res.dart'
as my_room;
import 'package:yumi/shared/business_logic/models/res/room_res.dart';
import 'package:yumi/modules/room/voice_room_route.dart';
import 'package:yumi/ui_kit/widgets/room/empty_mai_select.dart';
@ -76,8 +80,178 @@ class _RoomStartupAsyncResult<T> {
}
}
class _AgoraJoinCanceled implements Exception {
const _AgoraJoinCanceled(this.reason);
final String reason;
@override
String toString() => reason;
}
enum _PendingRoomStartupSeatActionType { clickSeat, goUpMic }
class RoomEntryPreviewData {
const RoomEntryPreviewData({
this.roomId,
this.roomAccount,
this.userId,
this.roomCover,
this.roomName,
this.roomDesc,
this.event,
this.sysOrigin,
this.countryCode,
this.countryName,
this.nationalFlag,
this.langCode,
this.del,
this.createTime,
this.updateTime,
this.activeTime,
this.roomSetting,
this.ownerProfile,
this.roomAdminCount,
this.roomMemberCount,
this.entrantsRole,
});
factory RoomEntryPreviewData.fromSocialChatRoom(SocialChatRoomRes room) {
return RoomEntryPreviewData(
roomId: room.id,
roomAccount: room.roomAccount,
userId: room.userId,
roomCover: room.roomCover,
roomName: room.roomName,
roomDesc: room.roomDesc,
event: room.event,
sysOrigin: room.sysOrigin,
countryCode: room.countryCode,
countryName: room.countryName,
nationalFlag: room.nationalFlag,
roomSetting: room.extValues?.roomSetting,
ownerProfile: room.userProfile,
roomAdminCount: room.roomCounter?.adminCount,
roomMemberCount: room.roomCounter?.memberCount,
);
}
factory RoomEntryPreviewData.fromFollowRoom(follow_room.FollowRoomRes room) {
final roomProfile = room.roomProfile;
return RoomEntryPreviewData(
roomId: roomProfile?.id ?? room.id,
roomAccount: roomProfile?.roomAccount,
userId: roomProfile?.userId,
roomCover: roomProfile?.roomCover,
roomName: roomProfile?.roomName,
roomDesc: roomProfile?.roomDesc,
event: roomProfile?.event,
sysOrigin: roomProfile?.sysOrigin,
countryCode: roomProfile?.countryCode,
countryName: roomProfile?.countryName,
nationalFlag: roomProfile?.nationalFlag,
roomSetting: roomProfile?.extValues?.roomSetting,
ownerProfile: roomProfile?.userProfile,
roomAdminCount: roomProfile?.roomCounter?.adminCount,
roomMemberCount: roomProfile?.roomCounter?.memberCount,
);
}
factory RoomEntryPreviewData.fromMyRoom(
my_room.MyRoomRes room, {
SocialChatUserProfile? ownerProfile,
}) {
return RoomEntryPreviewData(
roomId: room.id,
roomAccount: room.roomAccount,
userId: room.userId,
roomCover: room.roomCover,
roomName: room.roomName,
roomDesc: room.roomDesc,
event: room.event,
sysOrigin: room.sysOrigin,
countryCode: room.countryCode,
countryName: room.countryName,
nationalFlag: room.nationalFlag,
langCode: room.langCode,
del: room.del,
createTime: room.createTime,
updateTime: room.updateTime,
activeTime: room.activeTime,
roomSetting: room.setting,
ownerProfile: ownerProfile,
roomAdminCount: room.counter?.adminCount,
roomMemberCount: room.counter?.memberCount,
entrantsRole: SCRoomRolesType.HOMEOWNER.name,
);
}
final String? roomId;
final String? roomAccount;
final String? userId;
final String? roomCover;
final String? roomName;
final String? roomDesc;
final String? event;
final String? sysOrigin;
final String? countryCode;
final String? countryName;
final String? nationalFlag;
final String? langCode;
final bool? del;
final num? createTime;
final num? updateTime;
final num? activeTime;
final RoomSetting? roomSetting;
final SocialChatUserProfile? ownerProfile;
final num? roomAdminCount;
final num? roomMemberCount;
final String? entrantsRole;
int? get seatCount => roomSetting?.mikeSize?.toInt();
JoinRoomRes toJoinRoomRes({String? fallbackRoomId}) {
final resolvedRoomId =
(roomId ?? "").trim().isNotEmpty ? roomId : fallbackRoomId;
return JoinRoomRes(
entrants:
(entrantsRole ?? "").trim().isNotEmpty
? Entrants(roles: entrantsRole)
: null,
roomProfile: RoomProfile(
regionCode: countryCode,
roomCounter:
roomAdminCount != null || roomMemberCount != null
? RoomCounter(
adminCount: roomAdminCount,
memberCount: roomMemberCount,
)
: null,
roomProfile: RoomProfile2(
activeTime: activeTime,
countryCode: countryCode,
countryName: countryName,
createTime: createTime,
del: del,
event: event,
id: resolvedRoomId,
langCode: langCode,
nationalFlag: nationalFlag,
roomAccount: roomAccount,
roomCover: roomCover,
roomDesc: roomDesc,
roomName: roomName,
sysOrigin: sysOrigin,
updateTime: updateTime,
userId: userId,
),
roomSetting: roomSetting,
userProfile: ownerProfile,
),
);
}
}
class _PendingRoomStartupSeatAction {
const _PendingRoomStartupSeatAction.clickSeat({
required this.index,
@ -122,20 +296,24 @@ class RealTimeCommunicationManager extends ChangeNotifier {
bool _isHandlingAgoraRoomFailure = false;
bool _agoraJoined = false;
bool _agoraRoleIsBroadcaster = false;
String? _agoraJoinedChannelId;
bool _agoraAudioConfigured = false;
RoomStartupStatus _roomStartupStatus = RoomStartupStatus.idle;
RoomStartupFailureType _roomStartupFailureType = RoomStartupFailureType.none;
int _roomStartupFailureToken = 0;
int _roomEntryRequestSerial = 0;
int? _previewRoomSeatCount;
bool _currentRoomIsEntryPreview = false;
bool _isHandlingRoomStartupFailure = false;
Timer? _micListPollingTimer;
Timer? _onlineUsersPollingTimer;
Timer? _joinAgoraTimeoutTimer;
Timer? _agoraDisconnectedCleanupTimer;
Timer? _roomEntryEffectTimer;
Completer<void>? _joinAgoraCompleter;
Future<RtcEngine>? _rtcEngineInitTask;
Future<void>? _rtcEnginePrewarmTask;
Future<void>? _pendingRoomSwitchAgoraLeaveTask;
String? _joiningChannelId;
bool _isRefreshingMicList = false;
bool _isRefreshingOnlineUsers = false;
@ -247,6 +425,10 @@ class RealTimeCommunicationManager extends ChangeNotifier {
if (_roomVisualEffectsEnabled == enabled) {
return;
}
if (!enabled) {
_roomEntryEffectTimer?.cancel();
_roomEntryEffectTimer = null;
}
_roomVisualEffectsEnabled = enabled;
notifyListeners();
}
@ -273,6 +455,9 @@ class RealTimeCommunicationManager extends ChangeNotifier {
}
void _setRoomStartupFailed(RoomStartupFailureType failureType) {
if (_roomStartupStatus != RoomStartupStatus.loading) {
return;
}
_stopRoomStatePolling();
_isHandlingRoomStartupFailure = false;
_roomStartupFailureType = failureType;
@ -825,10 +1010,11 @@ class RealTimeCommunicationManager extends ChangeNotifier {
}
void _resetAgoraTracking({bool clearJoiningChannel = true}) {
_cancelAgoraJoinWaiter();
_cancelAgoraJoinWaiter(const _AgoraJoinCanceled('Agora join canceled'));
_cancelAgoraDisconnectedCleanup();
_agoraJoined = false;
_agoraRoleIsBroadcaster = false;
_agoraJoinedChannelId = null;
if (clearJoiningChannel) {
_joiningChannelId = null;
}
@ -1138,7 +1324,24 @@ class RealTimeCommunicationManager extends ChangeNotifier {
});
}
Future<void> _cleanupAgoraRoomState({bool clearMicSeats = false}) async {
Future<void> _cleanupAgoraRoomState({
bool clearMicSeats = false,
String? onlyIfJoinedChannelId,
}) async {
final expectedChannelId = (onlyIfJoinedChannelId ?? "").trim();
if (expectedChannelId.isNotEmpty) {
final joinedChannelId = (_agoraJoinedChannelId ?? "").trim();
final currentRoomId =
(currenRoom?.roomProfile?.roomProfile?.id ?? "").trim();
if (joinedChannelId.isNotEmpty && joinedChannelId != expectedChannelId) {
return;
}
if (joinedChannelId.isEmpty &&
currentRoomId.isNotEmpty &&
currentRoomId != expectedChannelId) {
return;
}
}
_resetAgoraTracking();
try {
await engine?.leaveChannel();
@ -1261,7 +1464,16 @@ class RealTimeCommunicationManager extends ChangeNotifier {
return;
}
_cancelAgoraJoinWaiter();
final pendingRoomSwitchLeaveTask = _pendingRoomSwitchAgoraLeaveTask;
if (pendingRoomSwitchLeaveTask != null) {
try {
await pendingRoomSwitchLeaveTask;
} catch (error) {
debugPrint('[Agora] pending room switch leave failed: $error');
}
}
_cancelAgoraJoinWaiter(const _AgoraJoinCanceled('Agora join superseded'));
_cancelAgoraDisconnectedCleanup();
_joiningChannelId = channelId;
_agoraJoined = false;
@ -1303,6 +1515,13 @@ class RealTimeCommunicationManager extends ChangeNotifier {
_syncSelfMicRuntimeState();
rtcEngine.muteAllRemoteAudioStreams(roomIsMute);
} catch (e) {
if (e is _AgoraJoinCanceled) {
debugPrint('[Agora] join canceled: $e');
if (throwOnError) {
rethrow;
}
return;
}
await _handleAgoraJoinFailed(e, exitRoom: !throwOnError);
if (throwOnError) {
rethrow;
@ -1475,6 +1694,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
}
_agoraJoined = true;
_agoraRoleIsBroadcaster = false;
_agoraJoinedChannelId = joinedChannelId.trim();
_cancelAgoraDisconnectedCleanup();
_joinAgoraTimeoutTimer?.cancel();
_joinAgoraTimeoutTimer = null;
@ -1533,6 +1753,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
reason ==
ConnectionChangedReasonType.connectionChangedRejoinSuccess) {
_agoraJoined = true;
_agoraJoinedChannelId = connection.channelId?.trim();
_cancelAgoraDisconnectedCleanup();
_resyncHeartbeatFromAgoraState();
return;
@ -1771,6 +1992,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
bool needOpenRedenvelope = false,
String redPackId = "",
int? previewSeatCount,
RoomEntryPreviewData? previewData,
}) async {
int nextTime = DateTime.now().millisecondsSinceEpoch;
if (nextTime - startTime < 1000) {
@ -1787,6 +2009,16 @@ class RealTimeCommunicationManager extends ChangeNotifier {
throw ArgumentError('Microphone permission is denied.');
}
if (roomId == currenRoom?.roomProfile?.roomProfile?.id) {
if (_currentRoomIsEntryPreview ||
_roomStartupStatus == RoomStartupStatus.loading) {
SCFloatIchart().remove();
if (!context.mounted) {
return;
}
VoiceRoomRoute.openVoiceRoom(context);
return;
}
///,
final loaded = await loadRoomInfo(
currenRoom?.roomProfile?.roomProfile?.id ?? "",
@ -1811,7 +2043,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
} else {
SCFloatIchart().remove();
if (currenRoom != null) {
await exitCurrentVoiceRoomSession(false, deferRemoteCleanup: false);
_switchAwayFromCurrentRoomForNewEntry();
}
if (!context.mounted) {
return;
@ -1823,7 +2055,11 @@ class RealTimeCommunicationManager extends ChangeNotifier {
);
_setRoomStartupLoading();
setRoomVisualEffectsEnabled(true);
_setPreviewRoomSeatCount(previewSeatCount, notify: false);
_applyRoomEntryPreview(
roomId,
previewData: previewData,
previewSeatCount: previewSeatCount,
);
VoiceRoomRoute.openVoiceRoom(context);
notifyListeners();
final entryRequestSerial = ++_roomEntryRequestSerial;
@ -1858,9 +2094,11 @@ class RealTimeCommunicationManager extends ChangeNotifier {
return;
}
currenRoom = enteredRoom;
_currentRoomIsEntryPreview = false;
_previewRoomSeatCount = null;
notifyListeners();
await initializeRoomSession(
entryRequestSerial: entryRequestSerial,
needOpenRedenvelope: needOpenRedenvelope,
redPackId: redPackId,
shouldOpenRoomPage: false,
@ -1879,6 +2117,17 @@ class RealTimeCommunicationManager extends ChangeNotifier {
_roomStartupStatus == RoomStartupStatus.loading;
}
bool _isCurrentRoomEntrySession(int entryRequestSerial, String roomId) {
return entryRequestSerial == _roomEntryRequestSerial &&
roomId.isNotEmpty &&
currenRoom?.roomProfile?.roomProfile?.id == roomId;
}
bool _isActiveRoomStartup(int entryRequestSerial, String roomId) {
return _isActiveRoomEntryRequest(entryRequestSerial) &&
_isCurrentRoomEntrySession(entryRequestSerial, roomId);
}
void _setPreviewRoomSeatCount(int? seatCount, {bool notify = true}) {
final normalizedSeatCount = _normalizePreviewRoomSeatCount(seatCount);
if (_previewRoomSeatCount == normalizedSeatCount) {
@ -1902,11 +2151,31 @@ class RealTimeCommunicationManager extends ChangeNotifier {
}
}
void _applyRoomEntryPreview(
String roomId, {
RoomEntryPreviewData? previewData,
int? previewSeatCount,
}) {
final resolvedPreviewSeatCount = previewData?.seatCount ?? previewSeatCount;
_setPreviewRoomSeatCount(resolvedPreviewSeatCount, notify: false);
if (previewData == null) {
return;
}
currenRoom = previewData.toJoinRoomRes(fallbackRoomId: roomId);
_currentRoomIsEntryPreview = true;
}
Future<void> _cleanupStaleEnteredRoom(JoinRoomRes enteredRoom) async {
final staleRoomId = enteredRoom.roomProfile?.roomProfile?.id ?? "";
if (staleRoomId.isEmpty) {
return;
}
if (_isCurrentRoomId(staleRoomId)) {
debugPrint(
'[RoomStartup] skip stale entry cleanup: room $staleRoomId is active again',
);
return;
}
await _retryExitNetworkRequest('quit stale entered room $staleRoomId', () {
return SCAccountRepository().quitRoom(staleRoomId).then((didQuit) {
if (!didQuit) {
@ -1918,12 +2187,17 @@ class RealTimeCommunicationManager extends ChangeNotifier {
///
Future<void> initializeRoomSession({
required int entryRequestSerial,
bool needOpenRedenvelope = false,
String? redPackId,
bool shouldOpenRoomPage = true,
}) async {
_disableMicListRefreshForCurrentSession = false;
_disableOnlineUsersRefreshForCurrentSession = false;
final startupRoomId = currenRoom?.roomProfile?.roomProfile?.id ?? "";
if (!_isActiveRoomStartup(entryRequestSerial, startupRoomId)) {
return;
}
if ((currenRoom?.roomProfile?.roomProfile?.event ==
SCRoomInfoEventType.WAITING_CONFIRMED.name ||
currenRoom?.roomProfile?.roomProfile?.event ==
@ -1938,26 +2212,39 @@ class RealTimeCommunicationManager extends ChangeNotifier {
);
if (currenRoom?.roomProfile?.roomProfile?.id !=
AccountStorage().getCurrentUser()?.userProfile?.id) {
SCChatRoomRepository()
.isFollowRoom(currenRoom?.roomProfile?.roomProfile?.id ?? "")
.then((value) {
isFollowRoomRes = value;
notifyListeners();
});
final followRoomId = startupRoomId;
SCChatRoomRepository().isFollowRoom(followRoomId).then((value) {
if (!_isActiveRoomStartup(entryRequestSerial, followRoomId)) {
return;
}
isFollowRoomRes = value;
notifyListeners();
});
}
_setRoomStartupLoading();
setRoomVisualEffectsEnabled(true);
if (shouldOpenRoomPage) {
VoiceRoomRoute.openVoiceRoom(context!);
}
unawaited(_bootstrapEnteredVoiceRoomSession());
unawaited(
_bootstrapEnteredVoiceRoomSession(
entryRequestSerial: entryRequestSerial,
roomId: startupRoomId,
groupId: currenRoom?.roomProfile?.roomProfile?.roomAccount ?? "",
),
);
}
Future<void> _bootstrapEnteredVoiceRoomSession() async {
Future<void> _bootstrapEnteredVoiceRoomSession({
required int entryRequestSerial,
required String roomId,
required String groupId,
}) async {
var failureType = RoomStartupFailureType.im;
if (!_isActiveRoomStartup(entryRequestSerial, roomId)) {
return;
}
try {
final roomId = currenRoom?.roomProfile?.roomProfile?.id ?? "";
final groupId = currenRoom?.roomProfile?.roomProfile?.roomAccount ?? "";
final joinGroupFuture = rtmProvider?.joinRoomGroup(groupId, "");
final rtcTokenFuture = _captureStartupFuture(
_fetchRtcTokenForCurrentRoom(isPublisher: false),
@ -1965,6 +2252,9 @@ class RealTimeCommunicationManager extends ChangeNotifier {
final microphoneListFuture = retrieveMicrophoneList();
final onlineUsersFuture = fetchOnlineUsersList();
final joinResult = await joinGroupFuture;
if (!_isActiveRoomStartup(entryRequestSerial, roomId)) {
return;
}
if (joinResult == null || joinResult.code != 0) {
debugPrint(
'[RoomStartup] join IM group failed code=${joinResult?.code} desc=${joinResult?.desc}',
@ -1986,16 +2276,36 @@ class RealTimeCommunicationManager extends ChangeNotifier {
);
await Future.wait([microphoneListFuture, onlineUsersFuture]);
if (!_isActiveRoomStartup(entryRequestSerial, roomId)) {
return;
}
_startRoomStatePolling();
Provider.of<SocialChatRoomManager>(
context!,
listen: false,
).fetchContributionLevelData(roomId);
final currentContext = context;
if (currentContext != null && currentContext.mounted) {
Provider.of<SocialChatRoomManager>(
currentContext,
listen: false,
).fetchContributionLevelData(roomId);
}
failureType = RoomStartupFailureType.rtc;
final rtcToken = (await rtcTokenFuture).requireValue();
final rtcTokenResult = await rtcTokenFuture;
if (!_isActiveRoomStartup(entryRequestSerial, roomId)) {
return;
}
final rtcToken = rtcTokenResult.requireValue();
await joinAgoraVoiceChannel(throwOnError: true, rtcToken: rtcToken);
if (!_isActiveRoomStartup(entryRequestSerial, roomId)) {
await _cleanupAgoraRoomState(
clearMicSeats: currenRoom == null,
onlyIfJoinedChannelId: roomId,
);
return;
}
await _bootstrapRoomHeartbeatState();
if (!_isActiveRoomStartup(entryRequestSerial, roomId)) {
return;
}
_setRoomStartupReady();
Msg joinMsg = Msg(
@ -2006,30 +2316,46 @@ class RealTimeCommunicationManager extends ChangeNotifier {
type: SCRoomMsgType.joinRoom,
);
rtmProvider?.dispatchMessage(joinMsg, addLocal: true);
_playRoomEntryEffects(joinMsg);
_playRoomEntryEffects(
joinMsg,
entryRequestSerial: entryRequestSerial,
roomId: roomId,
);
_loadRoomSecondaryData();
} catch (error, stackTrace) {
if (!_isActiveRoomStartup(entryRequestSerial, roomId)) {
debugPrint('[RoomStartup] ignore canceled bootstrap failure: $error');
return;
}
debugPrint('[RoomStartup] bootstrap failed: $error\n$stackTrace');
_setRoomStartupFailed(failureType);
}
}
void _playRoomEntryEffects(Msg joinMsg) {
void _playRoomEntryEffects(
Msg joinMsg, {
required int entryRequestSerial,
required String roomId,
}) {
if (SCGlobalConfig.isEntryVehicleAnimation) {
if (AccountStorage().getCurrentUser()?.userProfile?.getMountains() !=
null &&
shouldShowRoomVisualEffects) {
Future.delayed(Duration(milliseconds: 550), () {
SCGiftVapSvgaManager().play(
AccountStorage()
.getCurrentUser()
?.userProfile
?.getMountains()
?.sourceUrl ??
"",
priority: 100,
type: 1,
);
final sourceUrl =
AccountStorage()
.getCurrentUser()
?.userProfile
?.getMountains()
?.sourceUrl ??
"";
if (sourceUrl.isNotEmpty &&
shouldShowRoomVisualEffects &&
_isCurrentRoomEntrySession(entryRequestSerial, roomId)) {
_roomEntryEffectTimer?.cancel();
_roomEntryEffectTimer = Timer(const Duration(milliseconds: 550), () {
_roomEntryEffectTimer = null;
if (!shouldShowRoomVisualEffects ||
!_isCurrentRoomEntrySession(entryRequestSerial, roomId)) {
return;
}
SCGiftVapSvgaManager().play(sourceUrl, priority: 100, type: 1);
});
}
}
@ -2193,6 +2519,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
_stopRoomStatePolling();
final groupId = currenRoom?.roomProfile?.roomProfile?.roomAccount ?? "";
final roomId = currenRoom?.roomProfile?.roomProfile?.id ?? "";
final isPreviewOnlyExit = _currentRoomIsEntryPreview;
final exitRtmProvider = rtmProvider;
final navigationContext = context;
try {
@ -2218,8 +2545,8 @@ class RealTimeCommunicationManager extends ChangeNotifier {
final agoraLeaveTask = _leaveAgoraRoomForExit();
final remoteCleanupTask = _cleanupExitedRoomRemotely(
exitRtmProvider: exitRtmProvider,
groupId: groupId,
roomId: roomId,
groupId: isPreviewOnlyExit ? "" : groupId,
roomId: isPreviewOnlyExit ? "" : roomId,
shouldSendOnlineHeartbeat: !isLogout,
);
if (deferRemoteCleanup) {
@ -2231,6 +2558,44 @@ class RealTimeCommunicationManager extends ChangeNotifier {
}
}
void _switchAwayFromCurrentRoomForNewEntry() {
_stopRoomStatePolling();
final groupId = currenRoom?.roomProfile?.roomProfile?.roomAccount ?? "";
final roomId = currenRoom?.roomProfile?.roomProfile?.id ?? "";
final isPreviewOnlyExit = _currentRoomIsEntryPreview;
final exitRtmProvider = rtmProvider;
try {
exitRtmProvider?.msgAllListener = null;
exitRtmProvider?.msgChatListener = null;
exitRtmProvider?.msgGiftListener = null;
SCGiftVapSvgaManager().stopPlayback();
setRoomVisualEffectsEnabled(false);
SCHeartbeatUtils.cancelTimer();
exitRtmProvider?.cleanRoomData();
} catch (e) {
debugPrint('rtc切换房间本地清理出错: $e');
}
_clearData();
final agoraLeaveTask = _leaveAgoraRoomForExit();
_pendingRoomSwitchAgoraLeaveTask = agoraLeaveTask;
unawaited(
agoraLeaveTask.whenComplete(() {
if (identical(_pendingRoomSwitchAgoraLeaveTask, agoraLeaveTask)) {
_pendingRoomSwitchAgoraLeaveTask = null;
}
}),
);
unawaited(
_cleanupExitedRoomRemotely(
exitRtmProvider: exitRtmProvider,
groupId: isPreviewOnlyExit ? "" : groupId,
roomId: isPreviewOnlyExit ? "" : roomId,
shouldSendOnlineHeartbeat: false,
),
);
}
Future<void> _cleanupExitedRoomRemotely({
required RtmProvider? exitRtmProvider,
required String groupId,
@ -2348,6 +2713,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
}
_isHandlingRoomStartupFailure = true;
final failedRoom = currenRoom;
final isPreviewOnlyFailure = _currentRoomIsEntryPreview;
final groupId = failedRoom?.roomProfile?.roomProfile?.roomAccount ?? "";
final roomId = failedRoom?.roomProfile?.roomProfile?.id ?? "";
final navigatorContext = navigationContext ?? context;
@ -2359,12 +2725,12 @@ class RealTimeCommunicationManager extends ChangeNotifier {
SCHeartbeatUtils.cancelTimer();
SCHeartbeatUtils.cancelAnchorTimer();
rtmProvider?.cleanRoomData();
if (groupId.isNotEmpty) {
if (!isPreviewOnlyFailure && groupId.isNotEmpty) {
await rtmProvider?.quitGroup(groupId);
}
await _cleanupAgoraRoomState(clearMicSeats: true);
await Future.delayed(const Duration(milliseconds: 100));
if (roomId.isNotEmpty) {
if (!isPreviewOnlyFailure && roomId.isNotEmpty) {
await SCAccountRepository().quitRoom(roomId);
}
} catch (e) {
@ -2382,6 +2748,8 @@ class RealTimeCommunicationManager extends ChangeNotifier {
///
void _clearData() {
_roomEntryRequestSerial += 1;
_roomEntryEffectTimer?.cancel();
_roomEntryEffectTimer = null;
_previewRoomSeatCount = null;
_finishRoomStartupSeatLoading(executePendingAction: false);
_resetSelfMicGoUpFailureState();
@ -2394,6 +2762,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
_isSelfMicActionInFlight = false;
_roomStartupStatus = RoomStartupStatus.idle;
_roomStartupFailureType = RoomStartupFailureType.none;
_currentRoomIsEntryPreview = false;
_isHandlingRoomStartupFailure = false;
_isHandlingAgoraRoomFailure = false;
_pendingMicListRefresh = false;

View File

@ -0,0 +1,65 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:yumi/shared/business_logic/models/res/room_res.dart';
import 'package:yumi/shared/data_sources/sources/local/user_manager.dart';
import 'package:yumi/shared/data_sources/sources/repositories/sc_room_repository_imp.dart';
class SCHomeRoomPreloadManager {
SCHomeRoomPreloadManager._();
static final SCHomeRoomPreloadManager instance = SCHomeRoomPreloadManager._();
static const Duration _partyRoomCacheTtl = Duration(seconds: 30);
Future<List<SocialChatRoomRes>>? _partyRoomsFuture;
List<SocialChatRoomRes>? _partyRooms;
DateTime? _partyRoomsLoadedAt;
void preloadInitialHomeRooms() {
final token = AccountStorage().getToken();
if (AccountStorage().getCurrentUser() == null || token.isEmpty) {
return;
}
unawaited(
loadPartyRooms().catchError((error, stackTrace) {
debugPrint('[HomePreload] party rooms preload failed: $error');
return <SocialChatRoomRes>[];
}),
);
}
Future<List<SocialChatRoomRes>> loadPartyRooms({bool forceRefresh = false}) {
if (!forceRefresh && _hasFreshPartyRoomCache) {
return Future.value(List<SocialChatRoomRes>.from(_partyRooms!));
}
if (!forceRefresh) {
final pendingFuture = _partyRoomsFuture;
if (pendingFuture != null) {
return pendingFuture;
}
}
final future = SCChatRoomRepository()
.discovery(allRegion: true)
.then((rooms) {
_partyRooms = List<SocialChatRoomRes>.from(rooms);
_partyRoomsLoadedAt = DateTime.now();
return List<SocialChatRoomRes>.from(rooms);
})
.whenComplete(() {
_partyRoomsFuture = null;
});
_partyRoomsFuture = future;
return future;
}
bool get _hasFreshPartyRoomCache {
final rooms = _partyRooms;
final loadedAt = _partyRoomsLoadedAt;
if (rooms == null || loadedAt == null) {
return false;
}
return DateTime.now().difference(loadedAt) < _partyRoomCacheTtl;
}
}

View File

@ -1,3 +1,5 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:yumi/app_localizations.dart';
@ -6,8 +8,14 @@ import 'package:yumi/ui_kit/components/text/sc_text.dart';
import 'package:provider/provider.dart';
import 'package:yumi/app/routes/sc_routes.dart';
import 'package:yumi/app/routes/sc_fluro_navigator.dart';
import 'package:yumi/services/audio/rtm_manager.dart';
import 'package:yumi/services/gift/gift_animation_manager.dart';
import 'package:yumi/services/music/room_music_manager.dart';
import 'package:yumi/services/audio/rtc_manager.dart';
import 'package:yumi/shared/data_sources/sources/local/floating_screen_manager.dart';
import 'package:yumi/shared/tools/sc_gift_vap_svga_manager.dart';
import 'package:yumi/shared/tools/sc_room_effect_scheduler.dart';
import 'package:yumi/ui_kit/widgets/room/anim/room_entrance_widget.dart';
import '../../components/sc_float_ichart.dart';
@ -54,15 +62,34 @@ class _ExitMinRoomPageState extends State<ExitMinRoomPage> {
),
],
),
onTap: () {
onTap: () async {
final rtcProvider = context.read<RtcProvider>();
if (rtcProvider.roomStartupStatus ==
RoomStartupStatus.loading ||
rtcProvider.roomStartupStatus == RoomStartupStatus.failed) {
SCNavigatorUtils.goBack(context);
await rtcProvider.exitCurrentVoiceRoomSession(false);
return;
}
final giftAnimationManager =
context.read<GiftAnimationManager>();
final rtmProvider = context.read<RtmProvider>();
rtcProvider.setRoomVisualEffectsEnabled(false);
rtmProvider.msgFloatingGiftListener = null;
rtmProvider.msgLuckyGiftRewardTickerListener = null;
RoomEntranceHelper.clearQueue();
OverlayManager().removeRoom();
SCRoomEffectScheduler().clearDeferredTasks(
reason: 'room_minimize',
);
SCGiftVapSvgaManager().stopPlayback();
unawaited(context.read<RoomMusicManager>().stopForRoomExit());
giftAnimationManager.cleanupAnimationResources();
SCFloatIchart().show();
SCNavigatorUtils.popUntil(
context,
ModalRoute.withName(SCRoutes.home),
);
giftAnimationManager.cleanupAnimationResources();
},
),
SCDebounceWidget(

View File

@ -38,7 +38,7 @@ class _RoomMenuDialogState extends State<RoomMenuDialog> {
static const int _menuReport = 5;
static const int _menuBackground = 6;
static const int _menuMusic = 7;
static bool get _showBackgroundFeature => true;
static bool get _showBackgroundFeature => false;
List<RoomMenu> items2 = [];

View File

@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 1.2.5+7
version: 1.2.6+8
environment: