fix: update room rocket launch flow
This commit is contained in:
parent
ccb5579a85
commit
e5fdb78fd6
@ -21,6 +21,7 @@ import 'package:yumi/ui_kit/components/sc_tts.dart';
|
||||
import 'package:yumi/app/routes/sc_routes.dart';
|
||||
import 'package:yumi/app/routes/sc_fluro_navigator.dart';
|
||||
import 'package:yumi/shared/tools/sc_loading_manager.dart';
|
||||
import 'package:yumi/shared/tools/sc_pick_utils.dart';
|
||||
import 'package:yumi/shared/business_logic/models/res/gift_res.dart';
|
||||
import 'package:yumi/shared/data_sources/sources/local/user_manager.dart';
|
||||
import 'package:yumi/shared/data_sources/sources/repositories/sc_gift_repository_imp.dart';
|
||||
@ -60,6 +61,7 @@ class _PersonDetailPageState extends State<PersonDetailPage> {
|
||||
bool isLoading = true;
|
||||
bool isBlacklistLoading = true;
|
||||
bool isBlacklist = false;
|
||||
bool _isBackgroundUpdating = false;
|
||||
|
||||
Map<String, SCUserCounterRes> counterMap = {};
|
||||
List<SocialChatGiftRes> giftWallList = [];
|
||||
@ -582,22 +584,25 @@ class _PersonDetailPageState extends State<PersonDetailPage> {
|
||||
),
|
||||
items:
|
||||
backgroundPhotos.map((item) {
|
||||
return GestureDetector(
|
||||
child: netImage(
|
||||
return _buildProfileBackgroundTapTarget(
|
||||
ref,
|
||||
netImage(
|
||||
url: item.url ?? "",
|
||||
width: ScreenUtil().screenWidth,
|
||||
height: 300.w,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
onTap: () {},
|
||||
);
|
||||
}).toList(),
|
||||
)
|
||||
: Image.asset(
|
||||
'sc_images/person/sc_icon_profile_card_default_bg.png',
|
||||
width: ScreenUtil().screenWidth,
|
||||
height: 300.w,
|
||||
fit: BoxFit.cover,
|
||||
: _buildProfileBackgroundTapTarget(
|
||||
ref,
|
||||
Image.asset(
|
||||
'sc_images/person/sc_icon_profile_card_default_bg.png',
|
||||
width: ScreenUtil().screenWidth,
|
||||
height: 300.w,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
@ -972,6 +977,79 @@ class _PersonDetailPageState extends State<PersonDetailPage> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildProfileBackgroundTapTarget(
|
||||
SocialChatUserProfileManager ref,
|
||||
Widget child,
|
||||
) {
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap:
|
||||
widget.isMe == "true"
|
||||
? () => _pickAndUpdateProfileBackground(ref)
|
||||
: null,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
void _pickAndUpdateProfileBackground(SocialChatUserProfileManager ref) {
|
||||
if (_isBackgroundUpdating) {
|
||||
return;
|
||||
}
|
||||
SCPickUtils.pickImage(
|
||||
context,
|
||||
(bool success, String url) {
|
||||
final imageUrl = url.trim();
|
||||
if (!success || imageUrl.isEmpty || !mounted) {
|
||||
return;
|
||||
}
|
||||
_updateProfileBackground(ref, imageUrl);
|
||||
},
|
||||
aspectRatio: ScreenUtil().screenWidth / 300.w,
|
||||
backOriginalFile: false,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _updateProfileBackground(
|
||||
SocialChatUserProfileManager ref,
|
||||
String imageUrl,
|
||||
) async {
|
||||
if (_isBackgroundUpdating) {
|
||||
return;
|
||||
}
|
||||
_isBackgroundUpdating = true;
|
||||
SCLoadingManager.show(context: context);
|
||||
try {
|
||||
final updatedProfile = await SCAccountRepository().updateUserInfo(
|
||||
backgroundPhotos: [imageUrl],
|
||||
);
|
||||
final returnedPhotos = updatedProfile.backgroundPhotos ?? [];
|
||||
final hasUploadedPhoto = returnedPhotos.any(
|
||||
(photo) => (photo.url ?? "").trim() == imageUrl,
|
||||
);
|
||||
final backgroundPhotos =
|
||||
hasUploadedPhoto
|
||||
? returnedPhotos
|
||||
: [PersonPhoto(url: imageUrl, status: 1)];
|
||||
final mergedProfile = updatedProfile.copyWith(
|
||||
backgroundPhotos: backgroundPhotos,
|
||||
);
|
||||
ref.userProfile = mergedProfile;
|
||||
ref.syncCurrentUserProfile(mergedProfile);
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
setState(() {});
|
||||
SCTts.show(SCAppLocalizations.of(context)!.operationSuccessful);
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
SCTts.show("update fail $e");
|
||||
}
|
||||
} finally {
|
||||
_isBackgroundUpdating = false;
|
||||
SCLoadingManager.hide();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _openChat() async {
|
||||
final conversation = V2TimConversation(
|
||||
type: ConversationType.V2TIM_C2C,
|
||||
|
||||
@ -1,6 +1,11 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:yumi/app/routes/sc_fluro_navigator.dart';
|
||||
import 'package:yumi/shared/business_logic/models/res/login_res.dart';
|
||||
import 'package:yumi/shared/business_logic/models/res/sc_user_badge_res.dart';
|
||||
import 'package:yumi/shared/data_sources/sources/local/user_manager.dart';
|
||||
import 'package:yumi/shared/data_sources/sources/repositories/sc_user_repository_impl.dart';
|
||||
import 'package:yumi/ui_kit/components/sc_compontent.dart';
|
||||
import 'package:yumi/ui_kit/components/text/sc_text.dart';
|
||||
|
||||
class ProfileWallOfHonorsPage extends StatefulWidget {
|
||||
@ -18,11 +23,55 @@ class _ProfileWallOfHonorsPageState extends State<ProfileWallOfHonorsPage> {
|
||||
static const Color _mutedText = Color(0xff8F9A98);
|
||||
|
||||
int _selectedTabIndex = 0;
|
||||
final int _honorCount = 0;
|
||||
final int _eventCount = 0;
|
||||
bool _loading = true;
|
||||
SCUserBadgeRes _badges = SCUserBadgeRes.empty();
|
||||
|
||||
List<WearBadge> get _honorBadges =>
|
||||
_badges.displayBadges.where(_isHonorBadge).toList();
|
||||
|
||||
List<WearBadge> get _eventBadges =>
|
||||
_badges.displayBadges.where(_isEventBadge).toList();
|
||||
|
||||
int get _honorCount => _honorBadges.length;
|
||||
int get _eventCount => _eventBadges.length;
|
||||
int get _totalCount => _honorCount + _eventCount;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadBadges();
|
||||
}
|
||||
|
||||
Future<void> _loadBadges() async {
|
||||
final userId = widget.tageId.trim();
|
||||
if (userId.isEmpty) {
|
||||
setState(() {
|
||||
_loading = false;
|
||||
_badges = SCUserBadgeRes.empty();
|
||||
});
|
||||
return;
|
||||
}
|
||||
try {
|
||||
final currentUserId =
|
||||
AccountStorage().getCurrentUser()?.userProfile?.id?.trim() ?? "";
|
||||
final result =
|
||||
currentUserId.isNotEmpty && currentUserId == userId
|
||||
? await SCAccountRepository().currentUserBadges()
|
||||
: await SCAccountRepository().otherUserBadges(userId);
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_badges = result;
|
||||
_loading = false;
|
||||
});
|
||||
} catch (_) {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_badges = SCUserBadgeRes.empty(userId: userId);
|
||||
_loading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildTopBar() {
|
||||
return SizedBox(
|
||||
width: double.infinity,
|
||||
@ -131,6 +180,106 @@ class _ProfileWallOfHonorsPageState extends State<ProfileWallOfHonorsPage> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLoadingState() {
|
||||
return const SliverFillRemaining(
|
||||
hasScrollBody: false,
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
width: 28,
|
||||
height: 28,
|
||||
child: CircularProgressIndicator(color: Colors.white, strokeWidth: 2),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBadgeGrid() {
|
||||
final list = _selectedTabIndex == 0 ? _honorBadges : _eventBadges;
|
||||
if (list.isEmpty) {
|
||||
return _buildEmptyState();
|
||||
}
|
||||
return SliverPadding(
|
||||
padding: EdgeInsets.fromLTRB(16.w, 4.w, 16.w, 32.w),
|
||||
sliver: SliverGrid(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) => _buildBadgeItem(list[index]),
|
||||
childCount: list.length,
|
||||
),
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 3,
|
||||
mainAxisExtent: 132.w,
|
||||
mainAxisSpacing: 14.w,
|
||||
crossAxisSpacing: 12.w,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBadgeItem(WearBadge badge) {
|
||||
final url = _badgeUrl(badge);
|
||||
final name = (badge.badgeName ?? "").trim();
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
width: 86.w,
|
||||
height: 86.w,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xff0E332D),
|
||||
borderRadius: BorderRadius.circular(8.w),
|
||||
border: Border.all(
|
||||
color: const Color(0xff18F2B1).withValues(alpha: 0.24),
|
||||
),
|
||||
),
|
||||
child: netImage(
|
||||
url: url,
|
||||
width: 68.w,
|
||||
height: 68.w,
|
||||
fit: BoxFit.contain,
|
||||
noDefaultImg: true,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8.w),
|
||||
text(
|
||||
name.isEmpty ? "--" : name,
|
||||
fontSize: 12,
|
||||
textColor: Colors.white,
|
||||
fontWeight: FontWeight.w400,
|
||||
maxLines: 2,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
bool _isHonorBadge(WearBadge badge) {
|
||||
final sourceType = badge.sourceType?.trim().toUpperCase() ?? "";
|
||||
final type = badge.type?.trim().toUpperCase() ?? "";
|
||||
return sourceType == "ACHIEVEMENT" ||
|
||||
sourceType == "VIP" ||
|
||||
(sourceType.isEmpty &&
|
||||
(type == "LONG" || type == "ACHIEVEMENT" || type == "VIP"));
|
||||
}
|
||||
|
||||
bool _isEventBadge(WearBadge badge) {
|
||||
final sourceType = badge.sourceType?.trim().toUpperCase() ?? "";
|
||||
final type = badge.type?.trim().toUpperCase() ?? "";
|
||||
if (sourceType == "ACTIVITY") {
|
||||
return true;
|
||||
}
|
||||
if (sourceType.isNotEmpty) {
|
||||
return false;
|
||||
}
|
||||
return type == "ACTIVITY" ||
|
||||
type == "SHORT" ||
|
||||
(type != "LONG" && type != "VIP" && type != "ACHIEVEMENT");
|
||||
}
|
||||
|
||||
String _badgeUrl(WearBadge badge) {
|
||||
return (badge.selectUrl ?? "").trim();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
@ -143,7 +292,7 @@ class _ProfileWallOfHonorsPageState extends State<ProfileWallOfHonorsPage> {
|
||||
SliverToBoxAdapter(
|
||||
child: Column(children: [_buildTopBar(), _buildTabs()]),
|
||||
),
|
||||
_buildEmptyState(),
|
||||
_loading ? _buildLoadingState() : _buildBadgeGrid(),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@ -42,6 +42,8 @@ class RoomRtcAudioMixingConfig {
|
||||
abstract class RoomRtcEngineAdapter {
|
||||
RoomRtcEngineKind get kind;
|
||||
|
||||
bool get isInRoom;
|
||||
|
||||
Future<void> prewarm();
|
||||
|
||||
Future<void> enterRoomAsAudience(RoomRtcEnterRoomConfig config);
|
||||
|
||||
@ -345,7 +345,6 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
Timer? _roomRocketLaunchAnimationTimer;
|
||||
Timer? _roomRocketGiftRefreshTimer;
|
||||
final List<Timer> _roomRocketPostLaunchRefreshTimers = [];
|
||||
String? _lastRoomRocketLaunchNoticeKey;
|
||||
Timer? _roomRedPacketPresenceTimer;
|
||||
Future<void>? _rtcEnginePrewarmTask;
|
||||
Future<void>? _pendingRoomSwitchRtcLeaveTask;
|
||||
@ -1061,6 +1060,11 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
_resetLocalAudioRuntimeTracking();
|
||||
}
|
||||
|
||||
bool get _hasCurrentRoomRtcConnection {
|
||||
final adapter = _roomRtcEngineAdapter;
|
||||
return _roomRtcJoined && adapter != null && adapter.isInRoom;
|
||||
}
|
||||
|
||||
bool get _canReportMicActive {
|
||||
return _roomRtcJoined && _roomRtcRoleIsBroadcaster && isOnMai();
|
||||
}
|
||||
@ -1507,6 +1511,42 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _rejoinCurrentRoomRtcForSameRoomReuse(String roomId) async {
|
||||
if (_hasCurrentRoomRtcConnection) {
|
||||
_setRoomStartupReady();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final trtcUserSig = await _fetchRoomRtcJoinCredentialForCurrentRoom();
|
||||
if (!_isCurrentRoomId(roomId)) {
|
||||
return;
|
||||
}
|
||||
await joinRoomRtcVoiceChannel(
|
||||
throwOnError: true,
|
||||
trtcUserSig: trtcUserSig,
|
||||
);
|
||||
if (!_isCurrentRoomId(roomId)) {
|
||||
await _cleanupRoomRtcState(
|
||||
clearMicSeats: currenRoom == null,
|
||||
onlyIfBusinessRoomId: roomId,
|
||||
);
|
||||
return;
|
||||
}
|
||||
await _bootstrapRoomHeartbeatState();
|
||||
if (!_isCurrentRoomId(roomId)) {
|
||||
return;
|
||||
}
|
||||
_setRoomStartupReady();
|
||||
} catch (error) {
|
||||
if (!_isCurrentRoomId(roomId)) {
|
||||
return;
|
||||
}
|
||||
_stopVoiceRoomForegroundService();
|
||||
_setRoomStartupFailed(RoomStartupFailureType.rtc);
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> _switchRoomRtcToAudience() async {
|
||||
try {
|
||||
final switched = await _currentRoomRtcEngineAdapter.switchToAudience();
|
||||
@ -1779,12 +1819,20 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
retrieveMicrophoneList();
|
||||
fetchOnlineUsersList();
|
||||
_startRoomStatePolling();
|
||||
_setRoomStartupReady();
|
||||
final shouldRejoinRoomRtc = !_hasCurrentRoomRtcConnection;
|
||||
if (shouldRejoinRoomRtc) {
|
||||
_setRoomStartupLoading();
|
||||
} else {
|
||||
_setRoomStartupReady();
|
||||
}
|
||||
setRoomVisualEffectsEnabled(true);
|
||||
_startRoomRedPacketPresenceHeartbeat(roomId);
|
||||
_refreshRoomRedPacketListAfterEntry(roomId: roomId);
|
||||
_startVoiceRoomForegroundService();
|
||||
VoiceRoomRoute.openVoiceRoom(context);
|
||||
if (shouldRejoinRoomRtc) {
|
||||
unawaited(_rejoinCurrentRoomRtcForSameRoomReuse(roomId));
|
||||
}
|
||||
} else {
|
||||
JoinRoomRes? preEnteredRoom;
|
||||
if (pwd == null && _previewRoomRequiresPassword(context, previewData)) {
|
||||
@ -2823,10 +2871,8 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
|
||||
///更新房间火箭信息
|
||||
void updateRoomRocketConfigurationStatus(SCRoomRocketStatusRes res) {
|
||||
final previousStatus = roomRocketStatus;
|
||||
roomRocketStatus = res;
|
||||
notifyListeners();
|
||||
_publishRoomRocketLaunchNoticeIfNeeded(previousStatus, res);
|
||||
_scheduleRoomRocketAssetPreload(
|
||||
(res.roomId ?? currenRoom?.roomProfile?.roomProfile?.id ?? "").trim(),
|
||||
res,
|
||||
@ -2845,10 +2891,8 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
(currenRoom?.roomProfile?.roomProfile?.id ?? "").trim()) {
|
||||
return;
|
||||
}
|
||||
final previousStatus = roomRocketStatus;
|
||||
roomRocketStatus = res;
|
||||
notifyListeners();
|
||||
_publishRoomRocketLaunchNoticeIfNeeded(previousStatus, res);
|
||||
_scheduleRoomRocketAssetPreload(resolvedRoomId, res);
|
||||
}
|
||||
|
||||
@ -2966,114 +3010,6 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
return null;
|
||||
}
|
||||
|
||||
void _publishRoomRocketLaunchNoticeIfNeeded(
|
||||
SCRoomRocketStatusRes? previous,
|
||||
SCRoomRocketStatusRes current,
|
||||
) {
|
||||
if (!_shouldPublishRoomRocketLaunchNotice(previous, current)) {
|
||||
return;
|
||||
}
|
||||
final roomId =
|
||||
(current.roomId ?? currenRoom?.roomProfile?.roomProfile?.id ?? "")
|
||||
.trim();
|
||||
final groupId =
|
||||
(currenRoom?.roomProfile?.roomProfile?.roomAccount ?? "").trim();
|
||||
if (roomId.isEmpty || groupId.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final level = _roomRocketNoticeLevel(current);
|
||||
final noticeKey = _roomRocketLaunchNoticeKey(
|
||||
roomId: roomId,
|
||||
level: level,
|
||||
roundNo: current.roundNo ?? 0,
|
||||
);
|
||||
if (_lastRoomRocketLaunchNoticeKey == noticeKey) {
|
||||
return;
|
||||
}
|
||||
_lastRoomRocketLaunchNoticeKey = noticeKey;
|
||||
rtmProvider?.addMsg(
|
||||
Msg(
|
||||
groupId: groupId,
|
||||
msg: _roomRocketLaunchNoticeSeconds(current).toString(),
|
||||
type: SCRoomMsgType.rocketLaunchNotice,
|
||||
number: _roomRocketLaunchNoticeSeconds(current),
|
||||
rocketIconUrl: _rocketIconUrlForStatus(current, level),
|
||||
giftBatchId: noticeKey,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
bool _shouldPublishRoomRocketLaunchNotice(
|
||||
SCRoomRocketStatusRes? previous,
|
||||
SCRoomRocketStatusRes current,
|
||||
) {
|
||||
if (previous == null) {
|
||||
return false;
|
||||
}
|
||||
final currentRoomId =
|
||||
(currenRoom?.roomProfile?.roomProfile?.id ?? "").trim();
|
||||
if (currentRoomId.isNotEmpty &&
|
||||
(current.roomId ?? currentRoomId).trim() != currentRoomId) {
|
||||
return false;
|
||||
}
|
||||
return _roomRocketStatusPercent(previous) < 99.5 &&
|
||||
_roomRocketStatusPercent(current) >= 99.5;
|
||||
}
|
||||
|
||||
double _roomRocketStatusPercent(SCRoomRocketStatusRes? status) {
|
||||
if (status == null) {
|
||||
return 0;
|
||||
}
|
||||
final value = status.displayPercent ?? status.energyPercent;
|
||||
if (value != null) {
|
||||
final raw = value.toDouble();
|
||||
final percent = raw > 0 && raw <= 1 ? raw * 100 : raw;
|
||||
return percent.clamp(0, 100).toDouble();
|
||||
}
|
||||
final currentEnergy = status.currentEnergy ?? 0;
|
||||
final maxEnergy = status.maxEnergy ?? status.needEnergy ?? 0;
|
||||
if (maxEnergy <= 0) {
|
||||
return 0;
|
||||
}
|
||||
return ((currentEnergy / maxEnergy) * 100).clamp(0, 100).toDouble();
|
||||
}
|
||||
|
||||
int _roomRocketNoticeLevel(SCRoomRocketStatusRes status) {
|
||||
final level = status.currentLevel ?? status.level?.toInt() ?? 1;
|
||||
return level.clamp(1, 99).toInt();
|
||||
}
|
||||
|
||||
int _roomRocketLaunchNoticeSeconds(SCRoomRocketStatusRes status) {
|
||||
final seconds = status.resetRemainingSeconds ?? 0;
|
||||
if (seconds > 0 && seconds <= 60) {
|
||||
return seconds;
|
||||
}
|
||||
return 10;
|
||||
}
|
||||
|
||||
String _rocketIconUrlForStatus(SCRoomRocketStatusRes status, int level) {
|
||||
for (final item in status.levels ?? const []) {
|
||||
if (item.level == level && item.rocketIconUrl.trim().isNotEmpty) {
|
||||
return item.rocketIconUrl;
|
||||
}
|
||||
}
|
||||
for (final item in roomRocketStatus?.levels ?? const []) {
|
||||
if (item.level == level && item.rocketIconUrl.trim().isNotEmpty) {
|
||||
return item.rocketIconUrl;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
String _roomRocketLaunchNoticeKey({
|
||||
required String roomId,
|
||||
required int level,
|
||||
required int roundNo,
|
||||
}) {
|
||||
final roundKey = roundNo > 0 ? roundNo.toString() : 'full';
|
||||
return ['rocket_launch_notice', roomId, level, roundKey].join('|');
|
||||
}
|
||||
|
||||
void _playRoomRocketLaunchAnimation({
|
||||
required String animationUrl,
|
||||
required RoomRocketLaunchBroadcastMessage launch,
|
||||
@ -3680,7 +3616,6 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
_roomRocketGiftRefreshTimer?.cancel();
|
||||
_roomRocketGiftRefreshTimer = null;
|
||||
_cancelRoomRocketPostLaunchStatusRefresh();
|
||||
_lastRoomRocketLaunchNoticeKey = null;
|
||||
RoomRocketAssetPreloader.cancelCurrentRoom();
|
||||
_stopRoomRedPacketPresenceHeartbeat();
|
||||
_previewRoomSeatCount = null;
|
||||
|
||||
@ -121,6 +121,7 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
static const int _roomRocketLaunchRecentTtlMs = 90000;
|
||||
static const int _roomRocketLaunchLooseRecentTtlMs = 12000;
|
||||
static const int _roomRocketRegionBroadcastRecentTtlMs = 90000;
|
||||
static const Duration _createRoomGroupTimeout = Duration(seconds: 12);
|
||||
static const List<Duration> _roomRocketRewardPopupRetryDelays = [
|
||||
Duration(milliseconds: 700),
|
||||
Duration(seconds: 2),
|
||||
@ -654,11 +655,20 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
String groupID,
|
||||
String groupName,
|
||||
) {
|
||||
return V2TIMGroupManager().createGroup(
|
||||
groupID: groupID,
|
||||
groupType: GroupType.AVChatRoom,
|
||||
groupName: groupName,
|
||||
);
|
||||
return V2TIMGroupManager()
|
||||
.createGroup(
|
||||
groupID: groupID,
|
||||
groupType: GroupType.AVChatRoom,
|
||||
groupName: groupName,
|
||||
)
|
||||
.timeout(
|
||||
_createRoomGroupTimeout,
|
||||
onTimeout:
|
||||
() => V2TimValueCallback<String>(
|
||||
code: -10001,
|
||||
desc: 'Create IM group timeout',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
///加入房间的im群聊
|
||||
@ -1931,28 +1941,6 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
currentContext,
|
||||
listen: false,
|
||||
);
|
||||
final isCurrentVisibleRoom =
|
||||
rtcProvider.shouldShowRoomVisualEffects &&
|
||||
rtcProvider.isVoiceRoomRouteVisible;
|
||||
final launch = _buildRoomRocketLaunchFromStatusPayload(
|
||||
rtcProvider: rtcProvider,
|
||||
payload: data,
|
||||
roomId: roomId,
|
||||
);
|
||||
final shouldHandleLaunch = _shouldHandleRoomRocketLaunchFromStatusPayload(
|
||||
rtcProvider: rtcProvider,
|
||||
payload: data,
|
||||
launch: launch,
|
||||
);
|
||||
if (shouldHandleLaunch) {
|
||||
_publishRoomRocketRegionBroadcastIfNeeded(launch);
|
||||
if (isCurrentVisibleRoom) {
|
||||
_handleCurrentVisibleRoomRocketLaunch(
|
||||
rtcProvider: rtcProvider,
|
||||
launch: launch,
|
||||
);
|
||||
}
|
||||
}
|
||||
unawaited(rtcProvider.refreshRoomRocketStatus(roomId: roomId));
|
||||
}
|
||||
|
||||
@ -2330,141 +2318,6 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
return ['rocket_launch_loose', launch.roomId, launch.safeLevel].join('|');
|
||||
}
|
||||
|
||||
bool _shouldHandleRoomRocketLaunchFromStatusPayload({
|
||||
required RealTimeCommunicationManager rtcProvider,
|
||||
required Map<String, dynamic> payload,
|
||||
required RoomRocketLaunchBroadcastMessage launch,
|
||||
}) {
|
||||
final currentPercent = _roomRocketPayloadPercent(payload);
|
||||
if (currentPercent < 99.5) {
|
||||
return false;
|
||||
}
|
||||
final previousPercent = _roomRocketStatusPercent(
|
||||
rtcProvider.roomRocketStatus,
|
||||
);
|
||||
if (previousPercent < 99.5) {
|
||||
return true;
|
||||
}
|
||||
final launchKey = _roomRocketLaunchNoticeKey(launch);
|
||||
if (launchKey.isEmpty) {
|
||||
return false;
|
||||
}
|
||||
_pruneRecentRoomRocketLaunches(DateTime.now().millisecondsSinceEpoch);
|
||||
final looseLaunchKey = _roomRocketLaunchLooseKey(launch);
|
||||
return !_recentRoomRocketLaunchTimes.containsKey(launchKey) &&
|
||||
(looseLaunchKey.isEmpty ||
|
||||
!_recentRoomRocketLaunchLooseTimes.containsKey(looseLaunchKey));
|
||||
}
|
||||
|
||||
RoomRocketLaunchBroadcastMessage _buildRoomRocketLaunchFromStatusPayload({
|
||||
required RealTimeCommunicationManager rtcProvider,
|
||||
required Map<String, dynamic> payload,
|
||||
required String roomId,
|
||||
}) {
|
||||
final roomProfile = rtcProvider.currenRoom?.roomProfile?.roomProfile;
|
||||
final level = _roomRocketPayloadLevel(
|
||||
payload,
|
||||
rtcProvider.roomRocketStatus?.currentLevel ??
|
||||
rtcProvider.roomRocketStatus?.level?.toInt() ??
|
||||
1,
|
||||
);
|
||||
return RoomRocketLaunchBroadcastMessage(
|
||||
roomId: roomId,
|
||||
roomAccount: _firstNonBlank([
|
||||
roomProfile?.roomAccount,
|
||||
_payloadText(payload['roomAccount'] ?? payload['room_account']),
|
||||
]),
|
||||
roomName: _firstNonBlank([
|
||||
roomProfile?.roomName,
|
||||
_payloadText(payload['roomName'] ?? payload['room_name']),
|
||||
]),
|
||||
roomCoverUrl: _firstNonBlank([
|
||||
roomProfile?.roomCover,
|
||||
_payloadAssetText(payload['roomCoverUrl'] ?? payload['room_cover_url']),
|
||||
_payloadAssetText(payload['roomCover'] ?? payload['room_cover']),
|
||||
_payloadAssetText(payload['roomAvatar'] ?? payload['room_avatar']),
|
||||
_payloadAssetText(payload['roomIcon'] ?? payload['room_icon']),
|
||||
_payloadAssetText(payload['coverUrl'] ?? payload['cover_url']),
|
||||
_payloadAssetText(payload['cover']),
|
||||
_payloadAssetText(_broadcastPayloadMap(payload['room'])['coverUrl']),
|
||||
_payloadAssetText(_broadcastPayloadMap(payload['room'])['cover']),
|
||||
_payloadAssetText(_broadcastPayloadMap(payload['room'])['avatar']),
|
||||
_payloadAssetText(
|
||||
_broadcastPayloadMap(payload['roomInfo'])['coverUrl'],
|
||||
),
|
||||
_payloadAssetText(_broadcastPayloadMap(payload['roomInfo'])['cover']),
|
||||
_payloadAssetText(_broadcastPayloadMap(payload['roomInfo'])['avatar']),
|
||||
]),
|
||||
roundNo:
|
||||
_payloadNum(payload['roundNo'] ?? payload['round_no'])?.toInt() ?? 0,
|
||||
level: level,
|
||||
rocketIconUrl: _firstNonBlank([
|
||||
_rocketIconFromPayload(payload),
|
||||
_rocketIconUrlForLevel(level),
|
||||
]),
|
||||
rocketAnimationUrl: _rocketAnimationFromPayload(payload),
|
||||
durationSeconds: _roomRocketPayloadNoticeSeconds(payload),
|
||||
triggerUser: const RoomRocketLaunchTriggerUser(
|
||||
userId: '',
|
||||
nickname: '',
|
||||
avatar: '',
|
||||
account: '',
|
||||
countryCode: '',
|
||||
countryName: '',
|
||||
),
|
||||
triggerUserId: _payloadText(
|
||||
payload['triggerUserId'] ?? payload['trigger_user_id'],
|
||||
),
|
||||
launchNo: _payloadText(payload['launchNo'] ?? payload['launch_no']),
|
||||
);
|
||||
}
|
||||
|
||||
double _roomRocketStatusPercent(dynamic status) {
|
||||
if (status == null) {
|
||||
return 0;
|
||||
}
|
||||
final value = status.displayPercent ?? status.energyPercent;
|
||||
if (value is num) {
|
||||
final raw = value.toDouble();
|
||||
final percent = raw > 0 && raw <= 1 ? raw * 100 : raw;
|
||||
return percent.clamp(0, 100).toDouble();
|
||||
}
|
||||
final currentEnergy = status.currentEnergy;
|
||||
final maxEnergy = status.maxEnergy ?? status.needEnergy;
|
||||
if (currentEnergy is! num || maxEnergy is! num || maxEnergy <= 0) {
|
||||
return 0;
|
||||
}
|
||||
return ((currentEnergy / maxEnergy) * 100).clamp(0, 100).toDouble();
|
||||
}
|
||||
|
||||
double _roomRocketPayloadPercent(Map<String, dynamic> payload) {
|
||||
final value = _payloadNum(
|
||||
payload['displayPercent'] ??
|
||||
payload['display_percent'] ??
|
||||
payload['energyPercent'] ??
|
||||
payload['energy_percent'] ??
|
||||
payload['percent'],
|
||||
);
|
||||
if (value != null) {
|
||||
final raw = value.toDouble();
|
||||
final percent = raw > 0 && raw <= 1 ? raw * 100 : raw;
|
||||
return percent.clamp(0, 100).toDouble();
|
||||
}
|
||||
final currentEnergy = _payloadNum(
|
||||
payload['currentEnergy'] ?? payload['current_energy'],
|
||||
);
|
||||
final maxEnergy = _payloadNum(
|
||||
payload['maxEnergy'] ??
|
||||
payload['max_energy'] ??
|
||||
payload['needEnergy'] ??
|
||||
payload['need_energy'],
|
||||
);
|
||||
if (currentEnergy == null || maxEnergy == null || maxEnergy <= 0) {
|
||||
return 0;
|
||||
}
|
||||
return ((currentEnergy / maxEnergy) * 100).clamp(0, 100).toDouble();
|
||||
}
|
||||
|
||||
int _roomRocketPayloadLevel(Map<String, dynamic> payload, int fallback) {
|
||||
final value = _payloadNum(
|
||||
payload['currentLevel'] ??
|
||||
@ -2478,25 +2331,6 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
return (value?.toInt() ?? fallback).clamp(1, 99).toInt();
|
||||
}
|
||||
|
||||
int _roomRocketPayloadNoticeSeconds(Map<String, dynamic> payload) {
|
||||
final seconds =
|
||||
_payloadNum(
|
||||
payload['countdownSeconds'] ??
|
||||
payload['countdown_seconds'] ??
|
||||
payload['launchDelaySeconds'] ??
|
||||
payload['launch_delay_seconds'] ??
|
||||
payload['delaySeconds'] ??
|
||||
payload['delay_seconds'] ??
|
||||
payload['duration_seconds'] ??
|
||||
payload['durationSeconds'],
|
||||
)?.toInt() ??
|
||||
10;
|
||||
if (seconds > 0 && seconds <= 60) {
|
||||
return seconds;
|
||||
}
|
||||
return 10;
|
||||
}
|
||||
|
||||
String _rocketIconFromPayload(Map<String, dynamic> payload) {
|
||||
return _firstNonBlank([
|
||||
_payloadAssetText(payload['rocketIconUrl']),
|
||||
@ -2508,22 +2342,6 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
]);
|
||||
}
|
||||
|
||||
String _rocketAnimationFromPayload(Map<String, dynamic> payload) {
|
||||
return _firstNonBlank([
|
||||
_payloadAssetText(payload['rocketAnimationUrl']),
|
||||
_payloadAssetText(payload['rocket_animation_url']),
|
||||
_payloadAssetText(payload['rocketSvgaUrl']),
|
||||
_payloadAssetText(payload['rocket_svga_url']),
|
||||
_payloadAssetText(payload['animationUrl']),
|
||||
_payloadAssetText(payload['animation_url']),
|
||||
_payloadAssetText(payload['launchAnimationUrl']),
|
||||
_payloadAssetText(payload['launch_animation_url']),
|
||||
_payloadAssetText(payload['svgaUrl']),
|
||||
_payloadAssetText(payload['sourceUrl']),
|
||||
_payloadAssetText(payload['source_url']),
|
||||
]);
|
||||
}
|
||||
|
||||
String _payloadRoomId(Map<String, dynamic> payload) {
|
||||
return _firstNonBlank([
|
||||
_payloadText(payload['roomId']),
|
||||
|
||||
@ -206,6 +206,9 @@ class TrtcRoomRtcEngineAdapter implements RoomRtcEngineAdapter {
|
||||
@override
|
||||
RoomRtcEngineKind get kind => RoomRtcEngineKind.trtc;
|
||||
|
||||
@override
|
||||
bool get isInRoom => _isEntered;
|
||||
|
||||
@override
|
||||
Future<void> prewarm() async {}
|
||||
|
||||
|
||||
@ -78,22 +78,28 @@ class SocialChatRoomManager extends ChangeNotifier {
|
||||
|
||||
void createNewRoom(BuildContext context, {String? customName}) async {
|
||||
SCLoadingManager.show(context: context);
|
||||
// 差异化:添加自定义名称参数(暂未使用,为未来扩展预留)
|
||||
if (customName != null) {
|
||||
// TODO: 实现自定义房间名称
|
||||
}
|
||||
await SCAccountRepository().createRoom();
|
||||
myRoom = await SCAccountRepository().myProfile();
|
||||
hasLoadedMyRoom = true;
|
||||
isMyRoomLoading = false;
|
||||
if (!context.mounted) {
|
||||
try {
|
||||
// 差异化:添加自定义名称参数(暂未使用,为未来扩展预留)
|
||||
if (customName != null) {
|
||||
// TODO: 实现自定义房间名称
|
||||
}
|
||||
await SCAccountRepository().createRoom();
|
||||
myRoom = await SCAccountRepository().myProfile();
|
||||
hasLoadedMyRoom = true;
|
||||
isMyRoomLoading = false;
|
||||
if (!context.mounted) {
|
||||
return;
|
||||
}
|
||||
SCTts.show(SCAppLocalizations.of(context)!.createRoomSuccsess);
|
||||
} catch (e) {
|
||||
isMyRoomLoading = false;
|
||||
if (context.mounted) {
|
||||
SCTts.show(e.toString());
|
||||
}
|
||||
} finally {
|
||||
notifyListeners();
|
||||
SCLoadingManager.hide();
|
||||
return;
|
||||
}
|
||||
SCTts.show(SCAppLocalizations.of(context)!.createRoomSuccsess);
|
||||
notifyListeners();
|
||||
SCLoadingManager.hide();
|
||||
}
|
||||
|
||||
void clearContributionLevelData({bool notify = true}) {
|
||||
|
||||
@ -848,6 +848,7 @@ class WearBadge {
|
||||
String? milestone,
|
||||
String? notSelectUrl,
|
||||
String? selectUrl,
|
||||
String? sourceType,
|
||||
String? type,
|
||||
String? userId,
|
||||
bool? use,
|
||||
@ -862,6 +863,7 @@ class WearBadge {
|
||||
_milestone = milestone;
|
||||
_notSelectUrl = notSelectUrl;
|
||||
_selectUrl = selectUrl;
|
||||
_sourceType = sourceType;
|
||||
_type = type;
|
||||
_userId = userId;
|
||||
}
|
||||
@ -882,7 +884,10 @@ class WearBadge {
|
||||
_milestone = _stringValue(json['milestone']);
|
||||
_notSelectUrl = _stringValue(json['notSelectUrl']);
|
||||
_selectUrl = _stringValue(json['url'] ?? json['selectUrl']);
|
||||
_type = _stringValue(json['type']);
|
||||
final rawType = _stringValue(json['type']);
|
||||
_sourceType =
|
||||
_stringValue(json['sourceType']) ?? _sourceTypeFromType(rawType);
|
||||
_type = rawType;
|
||||
_userId = _stringValue(json['userId']);
|
||||
}
|
||||
|
||||
@ -896,6 +901,7 @@ class WearBadge {
|
||||
String? _milestone;
|
||||
String? _notSelectUrl;
|
||||
String? _selectUrl;
|
||||
String? _sourceType;
|
||||
String? _type;
|
||||
String? _userId;
|
||||
|
||||
@ -910,6 +916,7 @@ class WearBadge {
|
||||
String? milestone,
|
||||
String? notSelectUrl,
|
||||
String? selectUrl,
|
||||
String? sourceType,
|
||||
String? type,
|
||||
String? userId,
|
||||
}) => WearBadge(
|
||||
@ -923,6 +930,7 @@ class WearBadge {
|
||||
milestone: milestone ?? _milestone,
|
||||
notSelectUrl: notSelectUrl ?? _notSelectUrl,
|
||||
selectUrl: selectUrl ?? _selectUrl,
|
||||
sourceType: sourceType ?? _sourceType,
|
||||
type: type ?? _type,
|
||||
userId: userId ?? _userId,
|
||||
);
|
||||
@ -947,6 +955,8 @@ class WearBadge {
|
||||
|
||||
String? get selectUrl => _selectUrl;
|
||||
|
||||
String? get sourceType => _sourceType;
|
||||
|
||||
String? get type => _type;
|
||||
|
||||
String? get userId => _userId;
|
||||
@ -963,6 +973,7 @@ class WearBadge {
|
||||
map['milestone'] = _milestone;
|
||||
map['notSelectUrl'] = _notSelectUrl;
|
||||
map['selectUrl'] = _selectUrl;
|
||||
map['sourceType'] = _sourceType;
|
||||
map['type'] = _type;
|
||||
map['userId'] = _userId;
|
||||
return map;
|
||||
@ -973,6 +984,16 @@ class WearBadge {
|
||||
return text == null || text.isEmpty ? null : text;
|
||||
}
|
||||
|
||||
static String? _sourceTypeFromType(String? type) {
|
||||
final normalized = type?.trim().toUpperCase();
|
||||
if (normalized == "VIP" ||
|
||||
normalized == "ACTIVITY" ||
|
||||
normalized == "ACHIEVEMENT") {
|
||||
return normalized;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static int? _intValue(dynamic value) {
|
||||
if (value is int) {
|
||||
return value;
|
||||
|
||||
@ -71,7 +71,7 @@ class SCUserBadgeRes {
|
||||
|
||||
static bool _isDisplayBadgePayload(Map item) {
|
||||
final type = _stringValue(item['type'])?.toUpperCase();
|
||||
if (type != null && type != "LONG" && type != "SHORT") {
|
||||
if (type != null && !_isDisplayType(type)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -84,6 +84,14 @@ class SCUserBadgeRes {
|
||||
sourceType == "ACHIEVEMENT";
|
||||
}
|
||||
|
||||
static bool _isDisplayType(String type) {
|
||||
return type == "LONG" ||
|
||||
type == "SHORT" ||
|
||||
type == "VIP" ||
|
||||
type == "ACTIVITY" ||
|
||||
type == "ACHIEVEMENT";
|
||||
}
|
||||
|
||||
static String? _stringValue(dynamic value) {
|
||||
final text = value?.toString().trim();
|
||||
return text == null || text.isEmpty ? null : text;
|
||||
|
||||
@ -1174,10 +1174,10 @@ class SCAccountRepository implements SocialChatUserRepository {
|
||||
return result;
|
||||
}
|
||||
|
||||
///app/user/badge/current
|
||||
///go/app/user/badge/current
|
||||
@override
|
||||
Future<SCUserBadgeRes> currentUserBadges() async {
|
||||
const path = "/app/user/badge/current";
|
||||
const path = "/go/app/user/badge/current";
|
||||
final result = await http.get<SCUserBadgeRes>(
|
||||
path,
|
||||
extra: const {BaseNetworkClient.silentErrorToastKey: true},
|
||||
@ -1186,11 +1186,11 @@ class SCAccountRepository implements SocialChatUserRepository {
|
||||
return result;
|
||||
}
|
||||
|
||||
///app/user/badge/other
|
||||
///go/app/user/badge/other
|
||||
@override
|
||||
Future<SCUserBadgeRes> otherUserBadges(String userId) async {
|
||||
final params = <String, dynamic>{"userId": userId};
|
||||
const path = "/app/user/badge/other";
|
||||
const path = "/go/app/user/badge/other";
|
||||
final result = await http.get<SCUserBadgeRes>(
|
||||
path,
|
||||
queryParams: params,
|
||||
|
||||
@ -37,7 +37,8 @@ class SCUserBadgeStrip extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _SCUserBadgeStripState extends State<SCUserBadgeStrip> {
|
||||
static final Map<String, Future<SCUserBadgeRes>> _badgeFutures = {};
|
||||
static const Duration _cacheTtl = Duration(minutes: 1);
|
||||
static final Map<String, _BadgeFutureCacheEntry> _badgeFutures = {};
|
||||
|
||||
Future<SCUserBadgeRes>? _future;
|
||||
|
||||
@ -64,7 +65,16 @@ class _SCUserBadgeStripState extends State<SCUserBadgeStrip> {
|
||||
AccountStorage().getCurrentUser()?.userProfile?.id?.trim() ?? "";
|
||||
final isCurrentUser = currentUserId.isNotEmpty && userId == currentUserId;
|
||||
final cacheKey = isCurrentUser ? "current:$userId" : "other:$userId";
|
||||
return _badgeFutures.putIfAbsent(cacheKey, () async {
|
||||
final now = DateTime.now();
|
||||
_badgeFutures.removeWhere(
|
||||
(_, entry) => now.difference(entry.createdAt) >= _cacheTtl,
|
||||
);
|
||||
final cacheEntry = _badgeFutures[cacheKey];
|
||||
if (cacheEntry != null &&
|
||||
now.difference(cacheEntry.createdAt) < _cacheTtl) {
|
||||
return cacheEntry.future;
|
||||
}
|
||||
final future = () async {
|
||||
try {
|
||||
return isCurrentUser
|
||||
? await SCAccountRepository().currentUserBadges()
|
||||
@ -73,7 +83,9 @@ class _SCUserBadgeStripState extends State<SCUserBadgeStrip> {
|
||||
_badgeFutures.remove(cacheKey);
|
||||
return SCUserBadgeRes.empty(userId: userId);
|
||||
}
|
||||
});
|
||||
}();
|
||||
_badgeFutures[cacheKey] = _BadgeFutureCacheEntry(future, now);
|
||||
return future;
|
||||
}
|
||||
|
||||
String get _normalizedUserId => widget.userId?.trim() ?? "";
|
||||
@ -146,7 +158,8 @@ class _SCUserBadgeStripState extends State<SCUserBadgeStrip> {
|
||||
|
||||
Widget _buildBadge(WearBadge badge, {required double badgeHeight}) {
|
||||
final type = badge.type?.trim().toUpperCase() ?? "";
|
||||
final isLongBadge = type == "LONG";
|
||||
final sourceType = badge.sourceType?.trim().toUpperCase() ?? "";
|
||||
final isLongBadge = type == "LONG" || type == "VIP" || sourceType == "VIP";
|
||||
final width =
|
||||
isLongBadge
|
||||
? (widget.longBadgeWidth ?? badgeHeight * 2.2)
|
||||
@ -180,3 +193,10 @@ class _SCUserBadgeStripState extends State<SCUserBadgeStrip> {
|
||||
return spaced;
|
||||
}
|
||||
}
|
||||
|
||||
class _BadgeFutureCacheEntry {
|
||||
const _BadgeFutureCacheEntry(this.future, this.createdAt);
|
||||
|
||||
final Future<SCUserBadgeRes> future;
|
||||
final DateTime createdAt;
|
||||
}
|
||||
|
||||
@ -1242,23 +1242,66 @@ class _MsgItemState extends State<MsgItem> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildUserNameAndLevels(SocialChatUserProfile? user) {
|
||||
return SizedBox(
|
||||
height: 23.w,
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Flexible(
|
||||
fit: FlexFit.loose,
|
||||
child: socialchatNickNameText(
|
||||
user?.userNickname ?? "",
|
||||
maxWidth: 112.w,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 13.sp,
|
||||
type: user?.getVIP()?.name ?? "",
|
||||
needScroll: (user?.userNickname?.characters.length ?? 0) > 10,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 4.w),
|
||||
getWealthLevel(
|
||||
user?.wealthLevel ?? 0,
|
||||
width: 42.w,
|
||||
height: 20.w,
|
||||
fontSize: 9.sp,
|
||||
),
|
||||
SizedBox(width: 2.w),
|
||||
getUserLevel(
|
||||
user?.charmLevel ?? 0,
|
||||
width: 42.w,
|
||||
height: 20.w,
|
||||
fontSize: 9.sp,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<WearBadge> _activeWearBadges(SocialChatUserProfile? user) {
|
||||
return user?.wearBadge?.where((item) => item.use ?? false).toList() ??
|
||||
const <WearBadge>[];
|
||||
}
|
||||
|
||||
Widget _buildMedals({
|
||||
required String? userId,
|
||||
List<WearBadge> fallbackBadges = const <WearBadge>[],
|
||||
}) {
|
||||
final hasUserId = (userId ?? "").trim().isNotEmpty;
|
||||
if (!hasUserId && fallbackBadges.isEmpty) {
|
||||
return Container();
|
||||
return SizedBox(height: 16.w);
|
||||
}
|
||||
return Expanded(
|
||||
return SizedBox(
|
||||
width: double.infinity,
|
||||
child: SCUserBadgeStrip(
|
||||
userId: userId,
|
||||
fallbackBadges: fallbackBadges,
|
||||
height: 25.w,
|
||||
badgeHeight: 25.w,
|
||||
longBadgeWidth: 55.w,
|
||||
spacing: 5.w,
|
||||
height: 16.w,
|
||||
badgeHeight: 14.w,
|
||||
longBadgeWidth: 34.w,
|
||||
spacing: 3.w,
|
||||
maxBadges: 8,
|
||||
reserveSpace: true,
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -1800,58 +1843,20 @@ class _MsgItemState extends State<MsgItem> {
|
||||
),
|
||||
SizedBox(width: 3.w),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
getWealthLevel(
|
||||
widget.msg.user?.wealthLevel ?? 0,
|
||||
width: 48.w,
|
||||
height: 23.w,
|
||||
fontSize: 10.sp,
|
||||
),
|
||||
getUserLevel(
|
||||
widget.msg.user?.charmLevel ?? 0,
|
||||
width: 48.w,
|
||||
height: 23.w,
|
||||
fontSize: 10.sp,
|
||||
),
|
||||
SizedBox(width: 3.w),
|
||||
socialchatNickNameText(
|
||||
maxWidth: 120.w,
|
||||
fontWeight: FontWeight.w500,
|
||||
widget.msg.user?.userNickname ?? "",
|
||||
fontSize: 13.sp,
|
||||
type: widget.msg.user?.getVIP()?.name ?? "",
|
||||
needScroll:
|
||||
(widget.msg.user?.userNickname?.characters.length ??
|
||||
0) >
|
||||
10,
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
widget.msg.user?.getVIP() != null
|
||||
? netImage(
|
||||
url: widget.msg.user?.getVIP()?.cover ?? "",
|
||||
width: 25.w,
|
||||
height: 25.w,
|
||||
)
|
||||
: Container(),
|
||||
_buildMedals(
|
||||
userId: widget.msg.user?.id,
|
||||
fallbackBadges:
|
||||
widget.msg.user?.wearBadge
|
||||
?.where((item) => item.use ?? false)
|
||||
.toList() ??
|
||||
const <WearBadge>[],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
child: SizedBox(
|
||||
height: 48.w,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildUserNameAndLevels(widget.msg.user),
|
||||
SizedBox(height: 2.w),
|
||||
_buildMedals(
|
||||
userId: widget.msg.user?.id,
|
||||
fallbackBadges: _activeWearBadges(widget.msg.user),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -1884,56 +1889,20 @@ class _MsgItemState extends State<MsgItem> {
|
||||
),
|
||||
SizedBox(width: 3.w),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
getWealthLevel(
|
||||
user.wealthLevel ?? 0,
|
||||
width: 48.w,
|
||||
height: 23.w,
|
||||
fontSize: 10.sp,
|
||||
),
|
||||
getUserLevel(
|
||||
user.charmLevel ?? 0,
|
||||
width: 48.w,
|
||||
height: 23.w,
|
||||
fontSize: 10.sp,
|
||||
),
|
||||
SizedBox(width: 3.w),
|
||||
socialchatNickNameText(
|
||||
maxWidth: 120.w,
|
||||
fontWeight: FontWeight.w500,
|
||||
user.userNickname ?? "",
|
||||
fontSize: 13.sp,
|
||||
type: user.getVIP()?.name ?? "",
|
||||
needScroll:
|
||||
(user.userNickname?.characters.length ?? 0) > 10,
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
user.getVIP() != null
|
||||
? netImage(
|
||||
url: user.getVIP()?.cover ?? "",
|
||||
width: 25.w,
|
||||
height: 25.w,
|
||||
)
|
||||
: Container(),
|
||||
_buildMedals(
|
||||
userId: user.id,
|
||||
fallbackBadges:
|
||||
user.wearBadge
|
||||
?.where((item) => item.use ?? false)
|
||||
.toList() ??
|
||||
const <WearBadge>[],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
child: SizedBox(
|
||||
height: 48.w,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildUserNameAndLevels(user),
|
||||
SizedBox(height: 2.w),
|
||||
_buildMedals(
|
||||
userId: user.id,
|
||||
fallbackBadges: _activeWearBadges(user),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@ -23,6 +23,8 @@ import 'package:yumi/shared/tools/sc_lk_dialog_util.dart';
|
||||
import 'package:yumi/shared/tools/sc_room_utils.dart';
|
||||
import 'package:yumi/shared/data_sources/sources/repositories/sc_user_repository_impl.dart';
|
||||
import 'package:yumi/shared/business_logic/models/res/login_res.dart';
|
||||
import 'package:yumi/shared/business_logic/models/res/room_user_card_res.dart'
|
||||
as room_card;
|
||||
import 'package:yumi/services/general/sc_app_general_manager.dart';
|
||||
import 'package:yumi/services/audio/rtc_manager.dart';
|
||||
import 'package:yumi/services/auth/user_profile_manager.dart';
|
||||
@ -34,12 +36,12 @@ import '../../../shared/business_logic/models/res/sc_user_identity_res.dart';
|
||||
import '../../../modules/chat/chat_route.dart';
|
||||
|
||||
class RoomUserInfoCard extends StatefulWidget {
|
||||
String? userId;
|
||||
final String? userId;
|
||||
|
||||
RoomUserInfoCard({super.key, this.userId});
|
||||
const RoomUserInfoCard({super.key, this.userId});
|
||||
|
||||
@override
|
||||
_RoomUserInfoCardState createState() => _RoomUserInfoCardState();
|
||||
State<RoomUserInfoCard> createState() => _RoomUserInfoCardState();
|
||||
}
|
||||
|
||||
class _RoomUserInfoCardState extends State<RoomUserInfoCard> {
|
||||
@ -180,7 +182,11 @@ class _RoomUserInfoCardState extends State<RoomUserInfoCard> {
|
||||
SizedBox(height: 6.w),
|
||||
_buildInfoChips(context, profile),
|
||||
SizedBox(height: 9.w),
|
||||
_buildBadgeStrip(profile?.id ?? widget.userId),
|
||||
_buildBadgeStrip(
|
||||
profile?.id ?? widget.userId,
|
||||
ref,
|
||||
profile,
|
||||
),
|
||||
SizedBox(height: 10.w),
|
||||
_buildActions(
|
||||
context: context,
|
||||
@ -408,9 +414,14 @@ class _RoomUserInfoCardState extends State<RoomUserInfoCard> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBadgeStrip(String? userId) {
|
||||
Widget _buildBadgeStrip(
|
||||
String? userId,
|
||||
SocialChatUserProfileManager ref,
|
||||
SocialChatUserProfile? profile,
|
||||
) {
|
||||
return SCUserBadgeStrip(
|
||||
userId: userId,
|
||||
fallbackBadges: _roomCardFallbackBadges(ref, profile),
|
||||
height: 55.w,
|
||||
badgeHeight: 23.w,
|
||||
longBadgeWidth: 58.w,
|
||||
@ -421,6 +432,46 @@ class _RoomUserInfoCardState extends State<RoomUserInfoCard> {
|
||||
);
|
||||
}
|
||||
|
||||
List<WearBadge> _roomCardFallbackBadges(
|
||||
SocialChatUserProfileManager ref,
|
||||
SocialChatUserProfile? profile,
|
||||
) {
|
||||
final wearBadges =
|
||||
profile?.wearBadge
|
||||
?.where((item) => item.use != false && _hasBadgeUrl(item))
|
||||
.toList() ??
|
||||
const <WearBadge>[];
|
||||
if (wearBadges.isNotEmpty) {
|
||||
return wearBadges;
|
||||
}
|
||||
return ref.userCardInfo?.useBadge
|
||||
?.map(_roomUseBadgeToWearBadge)
|
||||
.where(_hasBadgeUrl)
|
||||
.toList() ??
|
||||
const <WearBadge>[];
|
||||
}
|
||||
|
||||
WearBadge _roomUseBadgeToWearBadge(room_card.UseBadge badge) {
|
||||
return WearBadge(
|
||||
animationUrl: badge.animationUrl,
|
||||
badgeKey: badge.badgeKey,
|
||||
badgeLevel: badge.badgeLevel,
|
||||
badgeName: badge.badgeName,
|
||||
expireTime: badge.expireTime?.toInt(),
|
||||
id: badge.id,
|
||||
milestone: badge.milestone,
|
||||
notSelectUrl: badge.notSelectUrl,
|
||||
selectUrl: badge.selectUrl,
|
||||
type: badge.type,
|
||||
userId: badge.userId,
|
||||
use: true,
|
||||
);
|
||||
}
|
||||
|
||||
bool _hasBadgeUrl(WearBadge badge) {
|
||||
return (badge.selectUrl ?? "").trim().isNotEmpty;
|
||||
}
|
||||
|
||||
Widget _buildActions({
|
||||
required BuildContext context,
|
||||
required SocialChatUserProfileManager ref,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user