动画 以及头像框等已知问题

This commit is contained in:
NIGGER SLAYER 2026-04-14 17:21:05 +08:00
parent 905e48592e
commit 41671f9e43
34 changed files with 3762 additions and 2862 deletions

View File

@ -1,9 +1,7 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:ui' as ui; import 'dart:ui' as ui;
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_debouncer/flutter_debouncer.dart'; import 'package:flutter_debouncer/flutter_debouncer.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
@ -28,7 +26,6 @@ import 'package:yumi/shared/data_sources/sources/local/floating_screen_manager.d
import 'package:yumi/shared/business_logic/models/res/gift_res.dart'; import 'package:yumi/shared/business_logic/models/res/gift_res.dart';
import 'package:yumi/shared/business_logic/models/res/mic_res.dart'; import 'package:yumi/shared/business_logic/models/res/mic_res.dart';
import 'package:yumi/services/general/sc_app_general_manager.dart'; import 'package:yumi/services/general/sc_app_general_manager.dart';
import 'package:yumi/services/gift/gift_system_manager.dart';
import 'package:yumi/services/audio/rtc_manager.dart'; 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';
@ -81,6 +78,34 @@ class _GiftPageState extends State<GiftPage>
int giftType = 0; int giftType = 0;
Debouncer debouncer = Debouncer(); Debouncer debouncer = Debouncer();
void _giftFxLog(String message) {
debugPrint('[GiftFX][Send] $message');
}
void _handleGiftSelected(
SocialChatGiftRes? gift, {
required int nextGiftType,
}) {
checkedGift = gift;
if (gift != null &&
(gift.giftSourceUrl ?? "").isNotEmpty &&
scGiftHasFullScreenEffect(gift.special)) {
SCGiftVapSvgaManager().preload(gift.giftSourceUrl!, highPriority: true);
_giftFxLog(
'preload selected gift '
'giftId=${gift.id} '
'giftName=${gift.giftName} '
'giftSourceUrl=${gift.giftSourceUrl} '
'special=${gift.special}',
);
}
number = 1;
noShowNumber = false;
setState(() {
giftType = nextGiftType;
});
}
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@ -91,20 +116,15 @@ class _GiftPageState extends State<GiftPage>
Provider.of<SocialChatUserProfileManager>(context, listen: false).balance(); Provider.of<SocialChatUserProfileManager>(context, listen: false).balance();
_pages.add( _pages.add(
GiftTabPage("ALL", (int checkedI) { GiftTabPage("ALL", (int checkedI) {
checkedGift = null;
var all = var all =
Provider.of<SCAppGeneralManager>( Provider.of<SCAppGeneralManager>(
context, context,
listen: false, listen: false,
).giftByTab["ALL"]; ).giftByTab["ALL"];
if (all != null) { _handleGiftSelected(
checkedGift = all[checkedI]; all != null ? all[checkedI] : null,
} nextGiftType: 0,
number = 1; );
noShowNumber = false;
setState(() {
giftType = 0;
});
}), }),
); );
@ -426,7 +446,8 @@ class _GiftPageState extends State<GiftPage>
horizontal: 20.w, horizontal: 20.w,
), ),
decoration: BoxDecoration( decoration: BoxDecoration(
color: SocialChatTheme.primaryLight, color:
SocialChatTheme.primaryLight,
borderRadius: borderRadius:
BorderRadius.circular(5), BorderRadius.circular(5),
), ),
@ -743,6 +764,16 @@ class _GiftPageState extends State<GiftPage>
roomId: rtcProvider?.currenRoom?.roomProfile?.roomProfile?.id ?? "", roomId: rtcProvider?.currenRoom?.roomProfile?.roomProfile?.id ?? "",
) )
.then((result) { .then((result) {
_giftFxLog(
'giveGift success giftId=${checkedGift?.id} '
'giftName=${checkedGift?.giftName} '
'giftSourceUrl=${checkedGift?.giftSourceUrl} '
'special=${checkedGift?.special} '
'giftTab=${checkedGift?.giftTab} '
'number=$number '
'acceptUserIds=${acceptUserIds.join(",")} '
'roomId=${rtcProvider?.currenRoom?.roomProfile?.roomProfile?.id}',
);
// SCTts.show(SCAppLocalizations.of(context)!.giftGivingSuccessful); // SCTts.show(SCAppLocalizations.of(context)!.giftGivingSuccessful);
sendGiftMsg(acceptUsers); sendGiftMsg(acceptUsers);
Provider.of<SocialChatUserProfileManager>( Provider.of<SocialChatUserProfileManager>(
@ -750,12 +781,38 @@ class _GiftPageState extends State<GiftPage>
listen: false, listen: false,
).updateBalance(result); ).updateBalance(result);
}) })
.catchError((e) {}); .catchError((e) {
_giftFxLog(
'giveGift failed giftId=${checkedGift?.id} '
'giftName=${checkedGift?.giftName} '
'error=$e',
);
});
} }
void sendGiftMsg(List<MicRes> acceptUsers) { void sendGiftMsg(List<MicRes> acceptUsers) {
///IM消息 ///IM消息
for (var u in acceptUsers) { for (var u in acceptUsers) {
final special = checkedGift?.special ?? "";
final giftSourceUrl = checkedGift?.giftSourceUrl ?? "";
final hasSource = giftSourceUrl.isNotEmpty;
final hasAnimation = scGiftHasAnimationSpecial(special);
final hasGlobalGift = special.contains(SCGiftType.GLOBAL_GIFT.name);
final hasFullScreenEffect = scGiftHasFullScreenEffect(special);
_giftFxLog(
'dispatch gift msg '
'giftId=${checkedGift?.id} '
'giftName=${checkedGift?.giftName} '
'toUserId=${u.user?.id} '
'toUserName=${u.user?.userNickname} '
'giftSourceUrl=$giftSourceUrl '
'special=$special '
'hasSource=$hasSource '
'hasAnimation=$hasAnimation '
'hasGlobalGift=$hasGlobalGift '
'hasFullScreenEffect=$hasFullScreenEffect '
'effectsEnabled=${SCGlobalConfig.isGiftSpecialEffects}',
);
Provider.of<RtmProvider>( Provider.of<RtmProvider>(
navigatorKey.currentState!.context, navigatorKey.currentState!.context,
listen: false, listen: false,
@ -808,12 +865,35 @@ class _GiftPageState extends State<GiftPage>
OverlayManager().addMessage(fMsg); OverlayManager().addMessage(fMsg);
} }
if (checkedGift!.giftSourceUrl != null && checkedGift!.special != null) { if (checkedGift!.giftSourceUrl != null && checkedGift!.special != null) {
if (checkedGift!.special!.contains(SCGiftType.ANIMSCION.name) || if (scGiftHasFullScreenEffect(checkedGift!.special)) {
checkedGift!.special!.contains(SCGiftType.GLOBAL_GIFT.name)) {
if (SCGlobalConfig.isGiftSpecialEffects) { if (SCGlobalConfig.isGiftSpecialEffects) {
_giftFxLog(
'local trigger player play '
'path=${checkedGift!.giftSourceUrl} '
'giftId=${checkedGift?.id} '
'giftName=${checkedGift?.giftName}',
);
SCGiftVapSvgaManager().play(checkedGift!.giftSourceUrl!); SCGiftVapSvgaManager().play(checkedGift!.giftSourceUrl!);
} else {
_giftFxLog(
'skip local play because isGiftSpecialEffects=false '
'giftId=${checkedGift?.id}',
);
} }
} else {
_giftFxLog(
'skip local play because special does not include '
'${SCGiftType.ANIMSCION.name}/$kSCGiftAnimationSpecialAlias/${SCGiftType.GLOBAL_GIFT.name} '
'giftId=${checkedGift?.id} special=${checkedGift?.special}',
);
} }
} else {
_giftFxLog(
'skip local play because giftSourceUrl or special is null '
'giftId=${checkedGift?.id} '
'giftSourceUrl=${checkedGift?.giftSourceUrl} '
'special=${checkedGift?.special}',
);
} }
} }
} }

View File

@ -56,7 +56,7 @@ class _GiftTabPageState extends State<GiftTabPage> {
) )
: Column( : Column(
children: [ children: [
SizedBox(height: 23.w,), SizedBox(height: 23.w),
Expanded( Expanded(
child: PageView.builder( child: PageView.builder(
controller: _giftPageController, controller: _giftPageController,
@ -250,7 +250,7 @@ class _GiftTabPageState extends State<GiftTabPage> {
lineHeight: 1, lineHeight: 1,
textAlign: TextAlign.center, textAlign: TextAlign.center,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
textColor: widget.isDark ? Colors.white : Colors.black, textColor: widget.isDark ? Colors.white : Colors.black,
), ),
), ),
Row( Row(
@ -280,23 +280,29 @@ class _GiftTabPageState extends State<GiftTabPage> {
children: [ children: [
SizedBox(height: 3.w), SizedBox(height: 3.w),
// widget // widget
if (gift.special!.contains(SCGiftType.ANIMSCION.name)) if (scGiftHasFullScreenEffect(gift.special))
Image.asset( Image.asset(
_strategy.getGiftPageGiftEffectIcon(SCGiftType.ANIMSCION.name), _strategy.getGiftPageGiftEffectIcon(
SCGiftType.ANIMSCION.name,
),
width: 16.w, width: 16.w,
height: 16.w, height: 16.w,
fit: BoxFit.fill, fit: BoxFit.fill,
), ),
if (gift.special!.contains(SCGiftType.MUSIC.name)) if (gift.special!.contains(SCGiftType.MUSIC.name))
Image.asset( Image.asset(
_strategy.getGiftPageGiftMusicIcon(SCGiftType.MUSIC.name), _strategy.getGiftPageGiftMusicIcon(
SCGiftType.MUSIC.name,
),
width: 16.w, width: 16.w,
height: 16.w, height: 16.w,
fit: BoxFit.fill, fit: BoxFit.fill,
), ),
if (gift.giftTab == (SCGiftType.LUCKY_GIFT.name)) if (gift.giftTab == (SCGiftType.LUCKY_GIFT.name))
Image.asset( Image.asset(
_strategy.getGiftPageGiftLuckIcon(SCGiftType.LUCKY_GIFT.name), _strategy.getGiftPageGiftLuckIcon(
SCGiftType.LUCKY_GIFT.name,
),
width: 16.w, width: 16.w,
height: 16.w, height: 16.w,
fit: BoxFit.fill, fit: BoxFit.fill,
@ -333,7 +339,8 @@ class _GiftTabPageState extends State<GiftTabPage> {
margin: EdgeInsets.symmetric(horizontal: 4.w), margin: EdgeInsets.symmetric(horizontal: 4.w),
decoration: BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
color: _index == i ? SocialChatTheme.primaryColor : Color(0xffDADADA), color:
_index == i ? SocialChatTheme.primaryColor : Color(0xffDADADA),
), ),
), ),
); );

View File

@ -63,7 +63,11 @@ class _RoomFollowPageState
), ),
), ),
child: netImage( child: netImage(
url: roomRes.roomProfile?.roomCover ?? "", url: resolveRoomCoverUrl(
roomRes.roomProfile?.id,
roomRes.roomProfile?.roomCover,
),
defaultImg: kRoomCoverDefaultImg,
borderRadius: BorderRadius.circular(12.w), borderRadius: BorderRadius.circular(12.w),
width: 200.w, width: 200.w,
height: 200.w, height: 200.w,

View File

@ -63,7 +63,11 @@ class _SCRoomHistoryPageState
), ),
), ),
child: netImage( child: netImage(
url: roomRes.roomProfile?.roomCover ?? "", url: resolveRoomCoverUrl(
roomRes.roomProfile?.id,
roomRes.roomProfile?.roomCover,
),
defaultImg: kRoomCoverDefaultImg,
borderRadius: BorderRadius.circular(12.w), borderRadius: BorderRadius.circular(12.w),
width: 200.w, width: 200.w,
height: 200.w, height: 200.w,
@ -74,7 +78,9 @@ class _SCRoomHistoryPageState
margin: EdgeInsets.symmetric(horizontal: 1.w), margin: EdgeInsets.symmetric(horizontal: 1.w),
decoration: BoxDecoration( decoration: BoxDecoration(
image: DecorationImage( image: DecorationImage(
image: AssetImage("sc_images/index/sc_icon_index_room_brd.png"), image: AssetImage(
"sc_images/index/sc_icon_index_room_brd.png",
),
fit: BoxFit.fill, fit: BoxFit.fill,
), ),
), ),

View File

@ -33,11 +33,7 @@ class _HomeMinePageState extends SCPageListState<FollowRoomRes, SCHomeMinePage>
BusinessLogicStrategy get _strategy => SCGlobalConfig.businessLogicStrategy; BusinessLogicStrategy get _strategy => SCGlobalConfig.businessLogicStrategy;
late TabController _tabController; late TabController _tabController;
final List<Widget> _pages = [ final List<Widget> _pages = [SCRoomHistoryPage(), SCRoomFollowPage()];
SCRoomHistoryPage(),
SCRoomFollowPage(),
];
bool isLoading = false; bool isLoading = false;
final List<Widget> _tabs = []; final List<Widget> _tabs = [];
@ -124,7 +120,7 @@ class _HomeMinePageState extends SCPageListState<FollowRoomRes, SCHomeMinePage>
physics: NeverScrollableScrollPhysics(), physics: NeverScrollableScrollPhysics(),
children: _pages, children: _pages,
), ),
) ),
], ],
); );
} }
@ -137,7 +133,11 @@ class _HomeMinePageState extends SCPageListState<FollowRoomRes, SCHomeMinePage>
children: [ children: [
SizedBox(width: 10.w), SizedBox(width: 10.w),
netImage( netImage(
url: provider.myRoom?.roomCover ?? "", url: resolveRoomCoverUrl(
provider.myRoom?.id,
provider.myRoom?.roomCover,
),
defaultImg: kRoomCoverDefaultImg,
borderRadius: BorderRadius.all(Radius.circular(8.w)), borderRadius: BorderRadius.all(Radius.circular(8.w)),
width: 70.w, width: 70.w,
), ),
@ -323,7 +323,11 @@ class _HomeMinePageState extends SCPageListState<FollowRoomRes, SCHomeMinePage>
child: Stack( child: Stack(
children: [ children: [
netImage( netImage(
url: roomRes.roomProfile?.roomCover ?? "", url: resolveRoomCoverUrl(
roomRes.roomProfile?.id,
roomRes.roomProfile?.roomCover,
),
defaultImg: kRoomCoverDefaultImg,
width: 70.w, width: 70.w,
height: 70.w, height: 70.w,
fit: BoxFit.cover, fit: BoxFit.cover,
@ -407,7 +411,11 @@ class _HomeMinePageState extends SCPageListState<FollowRoomRes, SCHomeMinePage>
children: [ children: [
SizedBox(width: 20.w), SizedBox(width: 20.w),
netImage( netImage(
url: res.roomProfile?.roomCover ?? "", url: resolveRoomCoverUrl(
res.roomProfile?.id,
res.roomProfile?.roomCover,
),
defaultImg: kRoomCoverDefaultImg,
fit: BoxFit.cover, fit: BoxFit.cover,
borderRadius: BorderRadius.all(Radius.circular(12.w)), borderRadius: BorderRadius.all(Radius.circular(12.w)),
width: 85.w, width: 85.w,

View File

@ -427,7 +427,8 @@ class _HomePartyPageState extends State<SCHomePartyPage>
), ),
), ),
child: netImage( child: netImage(
url: res.roomCover ?? "", url: resolveRoomCoverUrl(res.id, res.roomCover),
defaultImg: kRoomCoverDefaultImg,
borderRadius: BorderRadius.circular(12.w), borderRadius: BorderRadius.circular(12.w),
width: 200.w, width: 200.w,
height: 200.w, height: 200.w,
@ -451,7 +452,7 @@ class _HomePartyPageState extends State<SCHomePartyPage>
builder: (_, provider, __) { builder: (_, provider, __) {
return netImage( return netImage(
url: url:
"${provider.findCountryByName(res.countryName ?? "")?.nationalFlag}", "${provider.findCountryByName(res.countryName ?? "")?.nationalFlag}",
width: 20.w, width: 20.w,
height: 13.w, height: 13.w,
borderRadius: BorderRadius.circular(2.w), borderRadius: BorderRadius.circular(2.w),
@ -503,37 +504,25 @@ class _HomePartyPageState extends State<SCHomePartyPage>
), ),
), ),
SizedBox(width: 5.w), SizedBox(width: 5.w),
(res.extValues (res.extValues?.roomSetting?.password?.isEmpty ?? false)
?.roomSetting
?.password
?.isEmpty ??
false)
? Image.asset( ? Image.asset(
"sc_images/general/sc_icon_online_user.png", "sc_images/general/sc_icon_online_user.png",
width: 14.w, width: 14.w,
height: 14.w, height: 14.w,
) )
: Image.asset( : Image.asset(
"sc_images/index/sc_icon_room_suo.png", "sc_images/index/sc_icon_room_suo.png",
width: 20.w, width: 20.w,
height: 20.w, height: 20.w,
), ),
(res.extValues (res.extValues?.roomSetting?.password?.isEmpty ?? false)
?.roomSetting
?.password
?.isEmpty ??
false)
? SizedBox(width: 3.w) ? SizedBox(width: 3.w)
: Container(height: 10.w), : Container(height: 10.w),
(res.extValues (res.extValues?.roomSetting?.password?.isEmpty ?? false)
?.roomSetting
?.password
?.isEmpty ??
false)
? text( ? text(
res.extValues?.memberQuantity ?? "0", res.extValues?.memberQuantity ?? "0",
fontSize: 12.sp, fontSize: 12.sp,
) )
: Container(height: 10.w), : Container(height: 10.w),
SizedBox(width: 10.w), SizedBox(width: 10.w),
], ],

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -51,7 +51,9 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
_floatingGiftListener; _floatingGiftListener;
_tabController.addListener(() {}); // _tabController.addListener(() {}); //
_subscription = eventBus.on<SCGiveRoomLuckPageDisposeEvent>().listen((event) { _subscription = eventBus.on<SCGiveRoomLuckPageDisposeEvent>().listen((
event,
) {
if (mounted) { if (mounted) {
Provider.of<GiftProvider>(context, listen: false).clearAllGiftData(); Provider.of<GiftProvider>(context, listen: false).clearAllGiftData();
Provider.of<GiftProvider>( Provider.of<GiftProvider>(
@ -64,6 +66,10 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
@override @override
void dispose() { void dispose() {
final rtmProvider = Provider.of<RtmProvider>(context, listen: false);
if (rtmProvider.msgFloatingGiftListener == _floatingGiftListener) {
rtmProvider.msgFloatingGiftListener = null;
}
_tabController.dispose(); // _tabController.dispose(); //
_subscription.cancel(); _subscription.cancel();
super.dispose(); super.dispose();
@ -132,7 +138,7 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
SizedBox(height: ScreenUtil().setWidth(5)), SizedBox(height: ScreenUtil().setWidth(5)),
RoomOnlineUserWidget(), RoomOnlineUserWidget(),
RoomSeatWidget(), RoomSeatWidget(),
SizedBox(height: 2.w,), SizedBox(height: 2.w),
Expanded( Expanded(
child: Stack( child: Stack(
children: [ children: [

View File

@ -452,7 +452,8 @@ class _SearchRoomListState extends State<SearchRoomList> {
), ),
), ),
child: netImage( child: netImage(
url: e.roomCover ?? "", url: resolveRoomCoverUrl(e.id, e.roomCover),
defaultImg: kRoomCoverDefaultImg,
borderRadius: BorderRadius.circular(12.w), borderRadius: BorderRadius.circular(12.w),
width: 200.w, width: 200.w,
height: 200.w, height: 200.w,

File diff suppressed because it is too large Load Diff

View File

@ -37,6 +37,7 @@ import '../../shared/business_logic/models/res/sc_is_follow_room_res.dart';
import '../../shared/business_logic/models/res/sc_room_red_packet_list_res.dart'; import '../../shared/business_logic/models/res/sc_room_red_packet_list_res.dart';
import '../../shared/business_logic/models/res/sc_room_rocket_status_res.dart'; import '../../shared/business_logic/models/res/sc_room_rocket_status_res.dart';
import '../../shared/business_logic/models/res/sc_room_theme_list_res.dart'; import '../../shared/business_logic/models/res/sc_room_theme_list_res.dart';
import '../../shared/tools/sc_room_profile_cache.dart';
import '../../ui_kit/components/sc_float_ichart.dart'; import '../../ui_kit/components/sc_float_ichart.dart';
typedef OnSoundVoiceChange = Function(num index, int volum); typedef OnSoundVoiceChange = Function(num index, int volum);
@ -49,7 +50,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
JoinRoomRes? currenRoom; JoinRoomRes? currenRoom;
/// ///
List<OnSoundVoiceChange> _onSoundVoiceChangeList = []; final List<OnSoundVoiceChange> _onSoundVoiceChangeList = [];
/// ///
Map<num, MicRes> roomWheatMap = {}; Map<num, MicRes> roomWheatMap = {};
@ -240,7 +241,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
if (num.parse(v.user!.account!) == uid) { if (num.parse(v.user!.account!) == uid) {
roomWheatMap[k]!.setVolume = voloum; roomWheatMap[k]!.setVolume = voloum;
for (var element in _onSoundVoiceChangeList) { for (var element in _onSoundVoiceChangeList) {
element?.call(k, voloum); element(k, voloum);
} }
} }
} }
@ -267,6 +268,43 @@ class RealTimeCommunicationManager extends ChangeNotifier {
int startTime = 0; int startTime = 0;
String? _preferNonEmpty(String? primary, String? fallback) {
if ((primary ?? "").trim().isNotEmpty) {
return primary;
}
if ((fallback ?? "").trim().isNotEmpty) {
return fallback;
}
return primary ?? fallback;
}
void updateCurrentRoomBasicInfo({
String? roomCover,
String? roomName,
String? roomDesc,
}) {
final currentRoomProfile = currenRoom?.roomProfile?.roomProfile;
if (currenRoom == null || currentRoomProfile == null) {
return;
}
currenRoom = currenRoom?.copyWith(
roomProfile: currenRoom?.roomProfile?.copyWith(
roomProfile: currentRoomProfile.copyWith(
roomCover: _preferNonEmpty(roomCover, currentRoomProfile.roomCover),
roomName: _preferNonEmpty(roomName, currentRoomProfile.roomName),
roomDesc: _preferNonEmpty(roomDesc, currentRoomProfile.roomDesc),
),
),
);
SCRoomProfileCache.saveRoomProfile(
roomId: currentRoomProfile.id ?? "",
roomCover: roomCover,
roomName: roomName,
roomDesc: roomDesc,
);
notifyListeners();
}
void joinVoiceRoomSession( void joinVoiceRoomSession(
BuildContext context, BuildContext context,
String roomId, { String roomId, {
@ -897,14 +935,24 @@ class RealTimeCommunicationManager extends ChangeNotifier {
Future<bool> loadRoomInfo(String roomId) async { Future<bool> loadRoomInfo(String roomId) async {
try { try {
final value = await SCChatRoomRepository().specific(roomId); final value = await SCChatRoomRepository().specific(roomId);
final currentRoomProfile = currenRoom?.roomProfile?.roomProfile;
currenRoom = currenRoom?.copyWith( currenRoom = currenRoom?.copyWith(
roomProfile: currenRoom?.roomProfile?.copyWith( roomProfile: currenRoom?.roomProfile?.copyWith(
roomCounter: value.counter, roomCounter: value.counter ?? currenRoom?.roomProfile?.roomCounter,
roomSetting: value.setting, roomSetting: value.setting ?? currenRoom?.roomProfile?.roomSetting,
roomProfile: currenRoom?.roomProfile?.roomProfile?.copyWith( roomProfile: currentRoomProfile?.copyWith(
roomCover: value.roomCover, roomCover: _preferNonEmpty(
roomName: value.roomName, value.roomCover,
roomDesc: value.roomDesc, currentRoomProfile.roomCover,
),
roomName: _preferNonEmpty(
value.roomName,
currentRoomProfile.roomName,
),
roomDesc: _preferNonEmpty(
value.roomDesc,
currentRoomProfile.roomDesc,
),
), ),
), ),
); );
@ -1073,6 +1121,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
if (index > -1) { if (index > -1) {
return seatGlobalKeyMap[index]; return seatGlobalKeyMap[index];
} }
return null;
} }
/// ///

View File

@ -16,12 +16,12 @@ import 'package:yumi/shared/data_sources/sources/local/floating_screen_manager.d
import 'package:yumi/shared/data_sources/sources/local/user_manager.dart'; import 'package:yumi/shared/data_sources/sources/local/user_manager.dart';
import 'package:yumi/services/audio/rtc_manager.dart'; import 'package:yumi/services/audio/rtc_manager.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:tencent_cloud_chat_sdk/enum/V2TimAdvancedMsgListener.dart'; import 'package:tencent_cloud_chat_sdk/enum/V2TimAdvancedMsgListener.dart';
import 'package:tencent_cloud_chat_sdk/enum/V2TimConversationListener.dart'; import 'package:tencent_cloud_chat_sdk/enum/V2TimConversationListener.dart';
import 'package:tencent_cloud_chat_sdk/enum/V2TimGroupListener.dart'; import 'package:tencent_cloud_chat_sdk/enum/V2TimGroupListener.dart';
import 'package:tencent_cloud_chat_sdk/enum/V2TimSDKListener.dart'; import 'package:tencent_cloud_chat_sdk/enum/V2TimSDKListener.dart';
import 'package:tencent_cloud_chat_sdk/enum/conversation_type.dart'; import 'package:tencent_cloud_chat_sdk/enum/conversation_type.dart';
import 'package:tencent_cloud_chat_sdk/enum/group_type.dart'; import 'package:tencent_cloud_chat_sdk/enum/group_type.dart';
import 'package:tencent_cloud_chat_sdk/enum/log_level_enum.dart'; import 'package:tencent_cloud_chat_sdk/enum/log_level_enum.dart';
import 'package:tencent_cloud_chat_sdk/enum/message_status.dart'; import 'package:tencent_cloud_chat_sdk/enum/message_status.dart';
import 'package:tencent_cloud_chat_sdk/manager/v2_tim_group_manager.dart'; import 'package:tencent_cloud_chat_sdk/manager/v2_tim_group_manager.dart';
@ -50,7 +50,7 @@ import 'package:yumi/shared/business_logic/models/res/sc_broad_cast_luck_gift_pu
import 'package:yumi/shared/business_logic/models/res/broad_cast_mic_change_push.dart'; import 'package:yumi/shared/business_logic/models/res/broad_cast_mic_change_push.dart';
import 'package:yumi/shared/business_logic/models/res/gift_res.dart'; import 'package:yumi/shared/business_logic/models/res/gift_res.dart';
import 'package:yumi/shared/business_logic/models/res/sc_public_message_page_res.dart'; import 'package:yumi/shared/business_logic/models/res/sc_public_message_page_res.dart';
import 'package:yumi/shared/business_logic/models/res/sc_room_theme_list_res.dart'; import 'package:yumi/shared/business_logic/models/res/sc_room_theme_list_res.dart';
import 'package:yumi/ui_kit/widgets/room/invite/invite_room_dialog.dart'; import 'package:yumi/ui_kit/widgets/room/invite/invite_room_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/shared/business_logic/models/res/login_res.dart'; import 'package:yumi/shared/business_logic/models/res/login_res.dart';
@ -71,6 +71,10 @@ typedef RtmProvider = RealTimeMessagingManager;
class RealTimeMessagingManager extends ChangeNotifier { class RealTimeMessagingManager extends ChangeNotifier {
BuildContext? context; BuildContext? context;
void _giftFxLog(String message) {
debugPrint('[GiftFX][RTM] $message');
}
/// ///
List<Msg> roomAllMsgList = []; List<Msg> roomAllMsgList = [];
List<Msg> roomChatMsgList = []; List<Msg> roomChatMsgList = [];
@ -113,48 +117,48 @@ class RealTimeMessagingManager extends ChangeNotifier {
Debouncer debouncer = Debouncer(); Debouncer debouncer = Debouncer();
List<V2TimConversation> conversationList = []; List<V2TimConversation> conversationList = [];
/// ///
SocialChatUserProfile? customerInfo; SocialChatUserProfile? customerInfo;
int _systemUnreadCount() { int _systemUnreadCount() {
int count = 0; int count = 0;
for (final conversationId in SCGlobalConfig.systemConversationIds) { for (final conversationId in SCGlobalConfig.systemConversationIds) {
count += conversationMap[conversationId]?.unreadCount ?? 0; count += conversationMap[conversationId]?.unreadCount ?? 0;
} }
return count; return count;
} }
V2TimConversation getPreferredSystemConversation() { V2TimConversation getPreferredSystemConversation() {
final systemConversations = final systemConversations =
conversationMap.values conversationMap.values
.where( .where(
(element) => (element) =>
SCGlobalConfig.isSystemConversationId(element.conversationID), SCGlobalConfig.isSystemConversationId(element.conversationID),
) )
.toList() .toList()
..sort((e1, e2) { ..sort((e1, e2) {
final time1 = e1.lastMessage?.timestamp ?? 0; final time1 = e1.lastMessage?.timestamp ?? 0;
final time2 = e2.lastMessage?.timestamp ?? 0; final time2 = e2.lastMessage?.timestamp ?? 0;
return time2.compareTo(time1); return time2.compareTo(time1);
}); });
if (systemConversations.isNotEmpty) { if (systemConversations.isNotEmpty) {
return systemConversations.first; return systemConversations.first;
} }
return V2TimConversation( return V2TimConversation(
type: ConversationType.V2TIM_C2C, type: ConversationType.V2TIM_C2C,
userID: SCGlobalConfig.primarySystemUserId, userID: SCGlobalConfig.primarySystemUserId,
conversationID: SCGlobalConfig.primarySystemConversationId, conversationID: SCGlobalConfig.primarySystemConversationId,
); );
} }
void getConversationList() { void getConversationList() {
List<V2TimConversation> list = conversationMap.values.toList(); List<V2TimConversation> list = conversationMap.values.toList();
list.removeWhere((element) { list.removeWhere((element) {
if (element.conversationID == "c2c_${customerInfo?.id}") { if (element.conversationID == "c2c_${customerInfo?.id}") {
return true; return true;
} }
if (element.conversationID == "c2c_atyou-newsletter") { if (element.conversationID == "c2c_atyou-newsletter") {
/// ///
clearC2CHistoryMessage(element.conversationID, false); clearC2CHistoryMessage(element.conversationID, false);
@ -165,15 +169,15 @@ class RealTimeMessagingManager extends ChangeNotifier {
} }
return false; return false;
}); });
list.sort((e1, e2) { list.sort((e1, e2) {
int time1 = e1.lastMessage?.timestamp ?? 0; int time1 = e1.lastMessage?.timestamp ?? 0;
int time2 = e2.lastMessage?.timestamp ?? 0; int time2 = e2.lastMessage?.timestamp ?? 0;
return time2.compareTo(time1); return time2.compareTo(time1);
}); });
conversationList = list; conversationList = list;
systemUnReadCount = _systemUnreadCount(); systemUnReadCount = _systemUnreadCount();
customerUnReadCount = customerUnReadCount =
conversationMap["c2c_${customerInfo?.id}"]?.unreadCount ?? 0; conversationMap["c2c_${customerInfo?.id}"]?.unreadCount ?? 0;
notifyListeners(); notifyListeners();
} }
@ -234,11 +238,11 @@ class RealTimeMessagingManager extends ChangeNotifier {
// _onRefreshConversation(conversationList); // _onRefreshConversation(conversationList);
initConversation(); initConversation();
}, },
onTotalUnreadMessageCountChanged: (int totalUnreadCount) { onTotalUnreadMessageCountChanged: (int totalUnreadCount) {
messageUnReadCount = totalUnreadCount; messageUnReadCount = totalUnreadCount;
systemUnReadCount = _systemUnreadCount(); systemUnReadCount = _systemUnreadCount();
customerUnReadCount = customerUnReadCount =
conversationMap["c2c_${customerInfo?.id}"]?.unreadCount ?? 0; conversationMap["c2c_${customerInfo?.id}"]?.unreadCount ?? 0;
allUnReadCount = allUnReadCount =
messageUnReadCount + messageUnReadCount +
notifcationUnReadCount + notifcationUnReadCount +
@ -478,16 +482,16 @@ class RealTimeMessagingManager extends ChangeNotifier {
} }
} }
} }
if (message?.userID == customerInfo?.id) { if (message?.userID == customerInfo?.id) {
if (onNewMessageCurrentConversationListener == null) { if (onNewMessageCurrentConversationListener == null) {
conversationMap["c2c_${customerInfo?.id}"]?.unreadCount = conversationMap["c2c_${customerInfo?.id}"]?.unreadCount =
(conversationMap["c2c_${customerInfo?.id}"]?.unreadCount ?? 0) + 1; (conversationMap["c2c_${customerInfo?.id}"]?.unreadCount ?? 0) + 1;
} }
} }
systemUnReadCount = _systemUnreadCount(); systemUnReadCount = _systemUnreadCount();
notifyListeners(); notifyListeners();
onNewMessageCurrentConversationListener?.call(message, msgId: msgId); onNewMessageCurrentConversationListener?.call(message, msgId: msgId);
} }
void _onRefreshConversation(List<V2TimConversation> conversations) { void _onRefreshConversation(List<V2TimConversation> conversations) {
for (V2TimConversation conversation in conversations) { for (V2TimConversation conversation in conversations) {
@ -714,7 +718,9 @@ class RealTimeMessagingManager extends ChangeNotifier {
} }
} else if (SCPathUtils.getFileType(entity.path) == "video_pic") { } else if (SCPathUtils.getFileType(entity.path) == "video_pic") {
if (entity.lengthSync() > 50000000) { if (entity.lengthSync() > 50000000) {
SCTts.show(SCAppLocalizations.of(context!)!.theVideoSizeCannotExceed); SCTts.show(
SCAppLocalizations.of(context!)!.theVideoSizeCannotExceed,
);
return; return;
} }
// //
@ -788,7 +794,10 @@ class RealTimeMessagingManager extends ChangeNotifier {
toUser?.cleanUseProps(); toUser?.cleanUseProps();
toUser?.cleanPhotos(); toUser?.cleanPhotos();
msg.needUpDataUserInfo = msg.needUpDataUserInfo =
Provider.of<RealTimeCommunicationManager>(context!, listen: false).needUpDataUserInfo; Provider.of<RealTimeCommunicationManager>(
context!,
listen: false,
).needUpDataUserInfo;
msg.user = user; msg.user = user;
msg.toUser = toUser; msg.toUser = toUser;
final textMsg = await TencentImSDKPlugin.v2TIMManager final textMsg = await TencentImSDKPlugin.v2TIMManager
@ -948,7 +957,8 @@ class RealTimeMessagingManager extends ChangeNotifier {
/// ///
var fdata = data["data"]; var fdata = data["data"];
SCFloatingMessage msg = SCFloatingMessage.fromJson(fdata); SCFloatingMessage msg = SCFloatingMessage.fromJson(fdata);
if (msg.toUserId == AccountStorage().getCurrentUser()?.userProfile?.id && if (msg.toUserId ==
AccountStorage().getCurrentUser()?.userProfile?.id &&
msg.roomId != msg.roomId !=
Provider.of<RealTimeCommunicationManager>( Provider.of<RealTimeCommunicationManager>(
context!, context!,
@ -1076,11 +1086,17 @@ class RealTimeMessagingManager extends ChangeNotifier {
} else { } else {
res = SCRoomThemeListRes(); res = SCRoomThemeListRes();
} }
Provider.of<RealTimeCommunicationManager>(context!, listen: false).updateRoomBG(res); Provider.of<RealTimeCommunicationManager>(
context!,
listen: false,
).updateRoomBG(res);
return; return;
} }
if (msg.type == SCRoomMsgType.emoticons) { if (msg.type == SCRoomMsgType.emoticons) {
Provider.of<RealTimeCommunicationManager>(context!, listen: false).starPlayEmoji(msg); Provider.of<RealTimeCommunicationManager>(
context!,
listen: false,
).starPlayEmoji(msg);
return; return;
} }
if (msg.type == SCRoomMsgType.micChange) { if (msg.type == SCRoomMsgType.micChange) {
@ -1089,7 +1105,10 @@ class RealTimeMessagingManager extends ChangeNotifier {
listen: false, listen: false,
).micChange(BroadCastMicChangePush.fromJson(data).data?.mics); ).micChange(BroadCastMicChangePush.fromJson(data).data?.mics);
} else if (msg.type == SCRoomMsgType.refreshOnlineUser) { } else if (msg.type == SCRoomMsgType.refreshOnlineUser) {
Provider.of<RealTimeCommunicationManager>(context!, listen: false).fetchOnlineUsersList(); Provider.of<RealTimeCommunicationManager>(
context!,
listen: false,
).fetchOnlineUsersList();
} else if (msg.type == SCRoomMsgType.gameLuckyGift) { } else if (msg.type == SCRoomMsgType.gameLuckyGift) {
var broadCastRes = SCBroadCastLuckGiftPush.fromJson(data); var broadCastRes = SCBroadCastLuckGiftPush.fromJson(data);
msg.gift = SocialChatGiftRes(giftPhoto: broadCastRes.data?.giftCover); msg.gift = SocialChatGiftRes(giftPhoto: broadCastRes.data?.giftCover);
@ -1168,13 +1187,57 @@ class RealTimeMessagingManager extends ChangeNotifier {
} }
} }
} else if (msg.type == SCRoomMsgType.gift) { } else if (msg.type == SCRoomMsgType.gift) {
final gift = msg.gift;
final special = gift?.special ?? "";
final giftSourceUrl = gift?.giftSourceUrl ?? "";
final hasSource = giftSourceUrl.isNotEmpty;
final hasAnimation = scGiftHasAnimationSpecial(special);
final hasGlobalGift = special.contains(SCGiftType.GLOBAL_GIFT.name);
final hasFullScreenEffect = scGiftHasFullScreenEffect(special);
_giftFxLog(
'recv gift msg '
'fromUserId=${msg.user?.id} '
'fromUserName=${msg.user?.userNickname} '
'toUserId=${msg.toUser?.id} '
'toUserName=${msg.toUser?.userNickname} '
'giftId=${gift?.id} '
'giftName=${gift?.giftName} '
'giftSourceUrl=$giftSourceUrl '
'special=$special '
'hasSource=$hasSource '
'hasAnimation=$hasAnimation '
'hasGlobalGift=$hasGlobalGift '
'hasFullScreenEffect=$hasFullScreenEffect '
'effectsEnabled=${SCGlobalConfig.isGiftSpecialEffects}',
);
if (msg.gift!.giftSourceUrl != null && msg.gift!.special != null) { if (msg.gift!.giftSourceUrl != null && msg.gift!.special != null) {
if (msg.gift!.special!.contains(SCGiftType.ANIMSCION.name) || if (scGiftHasFullScreenEffect(msg.gift!.special)) {
msg.gift!.special!.contains(SCGiftType.GLOBAL_GIFT.name)) {
if (SCGlobalConfig.isGiftSpecialEffects) { if (SCGlobalConfig.isGiftSpecialEffects) {
_giftFxLog(
'trigger player play path=${msg.gift!.giftSourceUrl} '
'giftId=${msg.gift?.id} giftName=${msg.gift?.giftName}',
);
SCGiftVapSvgaManager().play(msg.gift!.giftSourceUrl!); SCGiftVapSvgaManager().play(msg.gift!.giftSourceUrl!);
} else {
_giftFxLog(
'skip player play because isGiftSpecialEffects=false '
'giftId=${msg.gift?.id}',
);
} }
} else {
_giftFxLog(
'skip player play because special does not include '
'${SCGiftType.ANIMSCION.name}/$kSCGiftAnimationSpecialAlias/${SCGiftType.GLOBAL_GIFT.name} '
'giftId=${msg.gift?.id} special=${msg.gift?.special}',
);
} }
} else {
_giftFxLog(
'skip player play because giftSourceUrl or special is null '
'giftId=${msg.gift?.id} '
'giftSourceUrl=${msg.gift?.giftSourceUrl} '
'special=${msg.gift?.special}',
);
} }
if (Provider.of<RealTimeCommunicationManager>( if (Provider.of<RealTimeCommunicationManager>(
context!, context!,
@ -1225,7 +1288,10 @@ class RealTimeMessagingManager extends ChangeNotifier {
return; return;
} else if (msg.type == SCRoomMsgType.roomRoleChange) { } else if (msg.type == SCRoomMsgType.roomRoleChange) {
/// ///
Provider.of<RealTimeCommunicationManager>(context!, listen: false).retrieveMicrophoneList(); Provider.of<RealTimeCommunicationManager>(
context!,
listen: false,
).retrieveMicrophoneList();
if (msg.toUser?.id == if (msg.toUser?.id ==
AccountStorage().getCurrentUser()?.userProfile?.id) { AccountStorage().getCurrentUser()?.userProfile?.id) {
Provider.of<RealTimeCommunicationManager>( Provider.of<RealTimeCommunicationManager>(
@ -1389,13 +1455,13 @@ class RealTimeMessagingManager extends ChangeNotifier {
notifyListeners(); notifyListeners();
} }
void updateSystemCount(int count) { void updateSystemCount(int count) {
for (final conversationId in SCGlobalConfig.systemConversationIds) { for (final conversationId in SCGlobalConfig.systemConversationIds) {
conversationMap[conversationId]?.unreadCount = 0; conversationMap[conversationId]?.unreadCount = 0;
} }
systemUnReadCount = 0; systemUnReadCount = 0;
notifyListeners(); notifyListeners();
} }
void updateCustomerCount(int count) { void updateCustomerCount(int count) {
conversationMap["c2c_${customerInfo?.id}"]?.unreadCount = 0; conversationMap["c2c_${customerInfo?.id}"]?.unreadCount = 0;

View File

@ -1,272 +1,258 @@
import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart';
import 'package:flutter/material.dart'; import 'package:yumi/shared/data_sources/sources/repositories/sc_config_repository_imp.dart';
import 'package:flutter_svga/flutter_svga.dart'; import 'package:yumi/shared/data_sources/sources/repositories/sc_room_repository_imp.dart';
import 'package:yumi/shared/data_sources/sources/local/file_cache_manager.dart'; import 'package:yumi/shared/tools/sc_gift_vap_svga_manager.dart';
import 'package:yumi/shared/data_sources/sources/repositories/sc_config_repository_imp.dart'; import 'package:yumi/shared/data_sources/models/country_mode.dart';
import 'package:yumi/shared/data_sources/sources/repositories/sc_room_repository_imp.dart'; import 'package:yumi/shared/business_logic/models/res/sc_banner_leaderboard_res.dart';
import 'package:yumi/shared/tools/sc_path_utils.dart'; import 'package:yumi/shared/business_logic/models/res/country_res.dart';
import 'package:yumi/shared/tools/sc_gift_vap_svga_manager.dart'; import 'package:yumi/shared/business_logic/models/res/gift_res.dart';
import 'package:yumi/shared/data_sources/models/country_mode.dart'; import 'package:yumi/shared/business_logic/models/res/sc_index_banner_res.dart';
import 'package:yumi/shared/business_logic/models/res/sc_banner_leaderboard_res.dart'; import 'package:yumi/shared/business_logic/models/res/sc_room_emoji_res.dart';
import 'package:yumi/shared/business_logic/models/res/country_res.dart';
import 'package:yumi/shared/business_logic/models/res/gift_res.dart'; import '../../shared/data_sources/models/enum/sc_banner_type.dart';
import 'package:yumi/shared/business_logic/models/res/sc_index_banner_res.dart'; import '../../shared/data_sources/models/enum/sc_gift_type.dart';
import 'package:yumi/shared/business_logic/models/res/sc_room_emoji_res.dart';
class SCAppGeneralManager extends ChangeNotifier {
import '../../shared/data_sources/models/enum/sc_banner_type.dart'; List<CountryMode> countryModeList = [];
Map<String, List<Country>> _countryMap = {};
class SCAppGeneralManager extends ChangeNotifier { Map<String, Country> _countryByNameMap = {};
List<CountryMode> countryModeList = []; bool _isFetchingCountryList = false;
Map<String, List<Country>> _countryMap = {};
Map<String, Country> _countryByNameMap = {}; ///
bool _isFetchingCountryList = false; List<SocialChatGiftRes> giftResList = [];
Map<String, SocialChatGiftRes> _giftByIdMap = {};
///
List<SocialChatGiftRes> giftResList = []; Map<String, List<SocialChatGiftRes>> giftByTab = {};
Map<String, SocialChatGiftRes> _giftByIdMap = {}; Map<String, List<SocialChatGiftRes>> activityGiftByTab = {};
Map<String, List<SocialChatGiftRes>> giftByTab = {}; ///
Map<String, List<SocialChatGiftRes>> activityGiftByTab = {}; Country? selectCountryInfo;
/// List<SocialChatGiftRes> activityList = [];
Country? selectCountryInfo;
SCAppGeneralManager() {
List<SocialChatGiftRes> activityList = []; fetchCountryList();
giftList(); // 使
SCAppGeneralManager() { giftActivityList();
fetchCountryList(); // giftBackpack();
giftList(); // 使 // configLevel();
giftActivityList(); }
// giftBackpack();
// configLevel(); ///
} Future<void> fetchCountryList() async {
if (_isFetchingCountryList) {
/// return;
Future<void> fetchCountryList() async { }
if (_isFetchingCountryList) { if (countryModeList.isNotEmpty) {
return; clearCountrySelection();
} notifyListeners();
if (countryModeList.isNotEmpty) { return;
clearCountrySelection(); }
notifyListeners();
return; _isFetchingCountryList = true;
} try {
await Future.delayed(Duration(milliseconds: 550));
_isFetchingCountryList = true; final result = await SCConfigRepositoryImp().loadCountry();
try { _countryMap.clear();
await Future.delayed(Duration(milliseconds: 550)); _countryByNameMap.clear();
final result = await SCConfigRepositoryImp().loadCountry(); countryModeList.clear();
_countryMap.clear();
_countryByNameMap.clear(); for (final c in result.openCountry ?? const <Country>[]) {
countryModeList.clear(); final prefix = c.aliasName?.substring(0, 1) ?? "";
if (prefix.isNotEmpty) {
for (final c in result.openCountry ?? const <Country>[]) { final countryList = _countryMap[prefix] ?? <Country>[];
final prefix = c.aliasName?.substring(0, 1) ?? ""; countryList.add(c.copyWith());
if (prefix.isNotEmpty) { _countryMap[prefix] = countryList;
final countryList = _countryMap[prefix] ?? <Country>[]; }
countryList.add(c.copyWith()); if ((c.countryName ?? "").isNotEmpty) {
_countryMap[prefix] = countryList; _countryByNameMap[c.countryName!] = c;
} }
if ((c.countryName ?? "").isNotEmpty) { }
_countryByNameMap[c.countryName!] = c;
} _countryMap.forEach((k, v) {
} countryModeList.add(CountryMode(k, v));
});
_countryMap.forEach((k, v) {
countryModeList.add(CountryMode(k, v)); ///
}); countryModeList.sort((a, b) => a.prefix.compareTo(b.prefix));
notifyListeners();
/// } catch (_) {
countryModeList.sort((a, b) => a.prefix.compareTo(b.prefix)); notifyListeners();
notifyListeners(); } finally {
} catch (_) { _isFetchingCountryList = false;
notifyListeners(); }
} finally { }
_isFetchingCountryList = false;
} ///
} Country? findCountryByName(String countryName) {
return _countryByNameMap[countryName];
/// }
Country? findCountryByName(String countryName) {
return _countryByNameMap[countryName]; ///
} Country? chooseCountry(int subIndex, int cIndex) {
var selectCm = countryModeList[subIndex].prefixCountrys[cIndex];
/// if (selectCountryInfo != null) {
Country? chooseCountry(int subIndex, int cIndex) { selectCountryInfo = null;
var selectCm = countryModeList[subIndex].prefixCountrys[cIndex]; } else {
if (selectCountryInfo != null) { selectCountryInfo = selectCm;
selectCountryInfo = null; }
} else { return selectCountryInfo;
selectCountryInfo = selectCm; }
}
return selectCountryInfo; void updateCurrentCountry(Country? countryInfo) {
} selectCountryInfo = countryInfo;
notifyListeners();
void updateCurrentCountry(Country? countryInfo) { }
selectCountryInfo = countryInfo;
notifyListeners(); void clearCountrySelection() {
} selectCountryInfo = null;
}
void clearCountrySelection() {
selectCountryInfo = null; void openDrawer(BuildContext context) {
} Scaffold.of(context).openDrawer();
}
void openDrawer(BuildContext context) {
Scaffold.of(context).openDrawer(); void closeDrawer(BuildContext context) {
} Scaffold.of(context).closeDrawer();
}
void closeDrawer(BuildContext context) {
Scaffold.of(context).closeDrawer(); void giftList({bool includeCustomized = true}) async {
} try {
giftResList = await SCChatRoomRepository().giftList();
void giftList({bool includeCustomized = true}) async { giftByTab.clear();
try { for (var gift in giftResList) {
giftResList = await SCChatRoomRepository().giftList(); giftByTab[gift.giftTab];
giftByTab.clear(); var gmap = giftByTab[gift.giftTab];
for (var gift in giftResList) { gmap ??= [];
giftByTab[gift.giftTab]; gmap.add(gift);
var gmap = giftByTab[gift.giftTab]; giftByTab[gift.giftTab!] = gmap;
gmap ??= []; var gAllMap = giftByTab["ALL"];
gmap.add(gift); gAllMap ??= [];
giftByTab[gift.giftTab!] = gmap; if (gift.giftTab != "NSCIONAL_FLAG" &&
var gAllMap = giftByTab["ALL"]; gift.giftTab != "ACTIVITY" &&
gAllMap ??= []; gift.giftTab != "LUCKY_GIFT" &&
if (gift.giftTab != "NSCIONAL_FLAG" && gift.giftTab != "CP" &&
gift.giftTab != "ACTIVITY" && gift.giftTab != "CUSTOMIZED" &&
gift.giftTab != "LUCKY_GIFT" && gift.giftTab != "MAGIC") {
gift.giftTab != "CP" && gAllMap.add(gift);
gift.giftTab != "CUSTOMIZED" && }
gift.giftTab != "MAGIC") { giftByTab["ALL"] = gAllMap;
gAllMap.add(gift); _giftByIdMap[gift.id!] = gift;
} }
giftByTab["ALL"] = gAllMap;
_giftByIdMap[gift.id!] = gift; if (includeCustomized) {
} giftByTab["CUSTOMIZED"] ??= [];
if (includeCustomized) { ///
giftByTab["CUSTOMIZED"] ??= []; giftByTab["CUSTOMIZED"]?.insert(0, SocialChatGiftRes(id: "-1000"));
} else {
/// giftByTab.remove("CUSTOMIZED");
giftByTab["CUSTOMIZED"]?.insert(0, SocialChatGiftRes(id: "-1000")); }
} else {
giftByTab.remove("CUSTOMIZED"); notifyListeners();
}
///
notifyListeners(); downLoad(giftResList);
} catch (e) {}
/// }
downLoad(giftResList);
} catch (e) {} void giftActivityList() async {
} try {
activityList.clear();
void giftActivityList() async { activityList = await SCChatRoomRepository().giftActivityList();
try { activityGiftByTab.clear();
activityList.clear(); for (var gift in activityList) {
activityList = await SCChatRoomRepository().giftActivityList(); var gmap = activityGiftByTab["${gift.activityId}"];
activityGiftByTab.clear(); gmap ??= [];
for (var gift in activityList) { gmap.add(gift);
var gmap = activityGiftByTab["${gift.activityId}"]; activityGiftByTab["${gift.activityId}"] = gmap;
gmap ??= []; }
gmap.add(gift); notifyListeners();
activityGiftByTab["${gift.activityId}"] = gmap; } catch (e) {}
} }
notifyListeners();
} catch (e) {} ///
} void giftBackpack() async {
try {
/// var result = await SCChatRoomRepository().giftBackpack();
void giftBackpack() async { } catch (e) {}
try { }
var result = await SCChatRoomRepository().giftBackpack();
} catch (e) {} ///id获取礼物
} SocialChatGiftRes? getGiftById(String id) {
return _giftByIdMap[id];
///id获取礼物 }
SocialChatGiftRes? getGiftById(String id) {
return _giftByIdMap[id]; void downLoad(List<SocialChatGiftRes> giftResList) {
} for (var gift in giftResList) {
final giftSourceUrl = gift.giftSourceUrl ?? "";
void downLoad(List<SocialChatGiftRes> giftResList) { if (giftSourceUrl.isEmpty) {
for (var gift in giftResList) { continue;
if (gift.giftSourceUrl != null) { }
if (SCPathUtils.getFileExtension(gift.giftSourceUrl!).toLowerCase() == if (!scGiftHasFullScreenEffect(gift.special)) {
".svga") { continue;
/// }
if (!SCGiftVapSvgaManager().videoItemCache.containsKey( SCGiftVapSvgaManager().preload(giftSourceUrl);
gift.giftSourceUrl, }
)) { }
SVGAParser.shared.decodeFromURL(gift.giftSourceUrl!).then((entity) {
entity.autorelease = false; List<SCIndexBannerRes> homeBanners = [];
SCGiftVapSvgaManager().videoItemCache[gift.giftSourceUrl!] = List<SCIndexBannerRes> exploreBanners = [];
entity; List<SCIndexBannerRes> roomBanners = [];
}); List<SCIndexBannerRes> gameBanners = [];
}
} else { ///banner
if ((gift.giftSourceUrl ?? "").isNotEmpty) { void loadMainBanner() async {
FileCacheManager.getInstance().getFile(url: gift.giftSourceUrl!); try {
} var banners = await SCConfigRepositoryImp().getBanner();
} homeBanners.clear();
} roomBanners.clear();
} gameBanners.clear();
} exploreBanners.clear();
for (var v in banners) {
List<SCIndexBannerRes> homeBanners = []; if ((v.displayPosition ?? "").contains(
List<SCIndexBannerRes> exploreBanners = []; SCBannerType.EXPLORE_PAGE.name,
List<SCIndexBannerRes> roomBanners = []; )) {
List<SCIndexBannerRes> gameBanners = []; exploreBanners.add(v);
}
///banner if ((v.displayPosition ?? "").contains(SCBannerType.ROOM.name)) {
void loadMainBanner() async { roomBanners.add(v);
try { }
var banners = await SCConfigRepositoryImp().getBanner();
homeBanners.clear(); if ((v.displayPosition ?? "").contains(SCBannerType.HOME_ALERT.name)) {
roomBanners.clear(); homeBanners.add(v);
gameBanners.clear(); }
exploreBanners.clear(); if ((v.displayPosition ?? "").contains(SCBannerType.GAME.name)) {
for (var v in banners) { gameBanners.add(v);
if ((v.displayPosition ?? "").contains( }
SCBannerType.EXPLORE_PAGE.name, }
)) { notifyListeners();
exploreBanners.add(v); } catch (e) {}
} }
if ((v.displayPosition ?? "").contains(SCBannerType.ROOM.name)) {
roomBanners.add(v); Map<String, List<Emojis>> emojiByTab = {};
}
///emoji表情
if ((v.displayPosition ?? "").contains(SCBannerType.HOME_ALERT.name)) { void emojiAll() async {
homeBanners.add(v); var roomEmojis = await SCChatRoomRepository().emojiAll();
} for (var value in roomEmojis) {
if ((v.displayPosition ?? "").contains(SCBannerType.GAME.name)) { emojiByTab[value.groupName!] = value.emojis ?? [];
gameBanners.add(v); }
} notifyListeners();
} }
notifyListeners();
} catch (e) {} ///
} void configLevel() {
SCConfigRepositoryImp().configLevel();
Map<String, List<Emojis>> emojiByTab = {}; }
///emoji表情 SCBannerLeaderboardRes? appLeaderResult;
void emojiAll() async {
var roomEmojis = await SCChatRoomRepository().emojiAll(); ///
for (var value in roomEmojis) { void appLeaderboard() async {
emojiByTab[value.groupName!] = value.emojis ?? []; try {
} appLeaderResult = await SCChatRoomRepository().appLeaderboard();
notifyListeners(); notifyListeners();
} } catch (e) {}
}
/// }
void configLevel() {
SCConfigRepositoryImp().configLevel();
}
SCBannerLeaderboardRes? appLeaderResult;
///
void appLeaderboard() async {
try {
appLeaderResult = await SCChatRoomRepository().appLeaderboard();
notifyListeners();
} catch (e) {}
}
}

View File

@ -11,6 +11,9 @@ class GiftAnimationManager extends ChangeNotifier {
// //
Map<int, LGiftModel?> giftMap = {0: null, 1: null, 2: null, 3: null}; Map<int, LGiftModel?> giftMap = {0: null, 1: null, 2: null, 3: null};
bool get _controllersReady =>
animationControllerList.length >= giftMap.length;
void enqueueGiftAnimation(LGiftModel giftModel) { void enqueueGiftAnimation(LGiftModel giftModel) {
pendingAnimationsQueue.add(giftModel); pendingAnimationsQueue.add(giftModel);
proceedToNextAnimation(); proceedToNextAnimation();
@ -18,7 +21,7 @@ class GiftAnimationManager extends ChangeNotifier {
/// ///
proceedToNextAnimation() { proceedToNextAnimation() {
if (pendingAnimationsQueue.isEmpty) { if (pendingAnimationsQueue.isEmpty || !_controllersReady) {
return; return;
} }
var playGift = pendingAnimationsQueue.first; var playGift = pendingAnimationsQueue.first;
@ -45,6 +48,7 @@ class GiftAnimationManager extends ChangeNotifier {
void attachAnimationControllers(List<LGiftScrollingScreenAnimsBean> anins) { void attachAnimationControllers(List<LGiftScrollingScreenAnimsBean> anins) {
animationControllerList = anins; animationControllerList = anins;
proceedToNextAnimation();
} }
void cleanupAnimationResources() { void cleanupAnimationResources() {

View File

@ -21,7 +21,7 @@ class SocialChatRoomManager extends ChangeNotifier {
void updateMyRoomInfo(SCEditRoomInfoRes roomInfo) { void updateMyRoomInfo(SCEditRoomInfoRes roomInfo) {
if (myRoom != null && myRoom!.id == roomInfo.id) { if (myRoom != null && myRoom!.id == roomInfo.id) {
if (roomInfo.roomCover != null) { if ((roomInfo.roomCover ?? "").trim().isNotEmpty) {
myRoom?.setRoomCover = roomInfo.roomCover!; myRoom?.setRoomCover = roomInfo.roomCover!;
} }
if (roomInfo.roomName != null) { if (roomInfo.roomName != null) {

View File

@ -1,6 +1,7 @@
import 'package:yumi/shared/data_sources/sources/local/user_manager.dart'; import 'package:yumi/shared/data_sources/sources/local/user_manager.dart';
import '../../../data_sources/models/enum/sc_props_type.dart'; import '../../../data_sources/models/enum/sc_props_type.dart';
/// token : "" /// token : ""
/// userCredential : {"expireTime":0,"releaseTime":0,"sign":"","sysOrigin":"","userId":0,"version":""} /// userCredential : {"expireTime":0,"releaseTime":0,"sign":"","sysOrigin":"","userId":0,"version":""}
/// userProfile : {"account":"","accountStatus":"","age":0,"bornDay":0,"bornMonth":0,"bornYear":0,"countryCode":"","countryId":0,"countryName":"","createTime":0,"del":false,"freezingTime":0,"id":0,"originSys":"","ownSpecialId":{"account":"","expiredTime":0},"sameRegion":false,"sysOriginChild":"","useProps":[{"expireTime":0,"propsResources":{"amount":0.0,"code":"","cover":"","expand":"","id":0,"name":"","sourceUrl":"","type":""},"userId":0}],"userAvatar":"","userNickname":"","userSex":0,"wearBadge":[{"animationUrl":"","badgeKey":"","badgeLevel":0,"badgeName":"","expireTime":0,"id":0,"milestone":0,"notSelectUrl":"","selectUrl":"","type":"","userId":0}]} /// userProfile : {"account":"","accountStatus":"","age":0,"bornDay":0,"bornMonth":0,"bornYear":0,"countryCode":"","countryId":0,"countryName":"","createTime":0,"del":false,"freezingTime":0,"id":0,"originSys":"","ownSpecialId":{"account":"","expiredTime":0},"sameRegion":false,"sysOriginChild":"","useProps":[{"expireTime":0,"propsResources":{"amount":0.0,"code":"","cover":"","expand":"","id":0,"name":"","sourceUrl":"","type":""},"userId":0}],"userAvatar":"","userNickname":"","userSex":0,"wearBadge":[{"animationUrl":"","badgeKey":"","badgeLevel":0,"badgeName":"","expireTime":0,"id":0,"milestone":0,"notSelectUrl":"","selectUrl":"","type":"","userId":0}]}
@ -214,7 +215,7 @@ class SocialChatUserProfile {
_useProps?.add(UseProps.fromJson(v)); _useProps?.add(UseProps.fromJson(v));
}); });
} }
_userAvatar = json['userAvatar']; _userAvatar = json['userAvatar'] ?? json['avatar'];
_roles = json['roles']; _roles = json['roles'];
_userNickname = json['userNickname']; _userNickname = json['userNickname'];
_hobby = json['hobby']; _hobby = json['hobby'];
@ -462,6 +463,7 @@ class SocialChatUserProfile {
void cleanWearBadge() { void cleanWearBadge() {
_wearBadge?.clear(); _wearBadge?.clear();
} }
void cleanUseProps() { void cleanUseProps() {
_useProps?.clear(); _useProps?.clear();
} }

View File

@ -53,7 +53,7 @@ class SCEditRoomInfoRes {
} }
SCEditRoomInfoRes.fromJson(dynamic json) { SCEditRoomInfoRes.fromJson(dynamic json) {
_id = json['id']; _id = json['id'] ?? json['roomId'];
_roomAccount = json['roomAccount']; _roomAccount = json['roomAccount'];
_userId = json['userId']; _userId = json['userId'];
_roomCover = json['roomCover'] ?? json['cover']; _roomCover = json['roomCover'] ?? json['cover'];

View File

@ -1,8 +1,15 @@
enum SCGiftType{ enum SCGiftType { ANIMSCION, MUSIC, GLOBAL_GIFT, LUCKY_GIFT, MAGIC, CP }
ANIMSCION,
MUSIC, const String kSCGiftAnimationSpecialAlias = 'ANIMATION';
GLOBAL_GIFT,
LUCKY_GIFT, bool scGiftHasAnimationSpecial(String? special) {
MAGIC, final value = special ?? '';
CP, return value.contains(SCGiftType.ANIMSCION.name) ||
} value.contains(kSCGiftAnimationSpecialAlias);
}
bool scGiftHasFullScreenEffect(String? special) {
final value = special ?? '';
return scGiftHasAnimationSpecial(value) ||
value.contains(SCGiftType.GLOBAL_GIFT.name);
}

View File

@ -1,26 +1,29 @@
import 'dart:io'; import 'dart:io';
import 'package:yumi/shared/business_logic/repositories/general_repository.dart'; import 'package:flutter/foundation.dart';
import 'package:yumi/shared/data_sources/sources/remote/net/api.dart'; import 'package:yumi/shared/business_logic/repositories/general_repository.dart';
import 'package:yumi/shared/data_sources/sources/remote/net/network_client.dart'; import 'package:yumi/shared/data_sources/sources/remote/net/api.dart';
import 'package:yumi/shared/data_sources/sources/remote/net/network_client.dart';
class SCGeneralRepositoryImp implements SocialChatGeneralRepository {
static SCGeneralRepositoryImp? _instance; class SCGeneralRepositoryImp implements SocialChatGeneralRepository {
static SCGeneralRepositoryImp? _instance;
SCGeneralRepositoryImp._internal();
SCGeneralRepositoryImp._internal();
factory SCGeneralRepositoryImp() {
return _instance ??= SCGeneralRepositoryImp._internal(); factory SCGeneralRepositoryImp() {
} return _instance ??= SCGeneralRepositoryImp._internal();
}
///external/oss/upload ///external/oss/upload
@override @override
Future<String> upload(File image) async { Future<String> upload(File image) async {
String filePath = image.path; String filePath = image.path;
var name = var name = filePath.substring(
filePath.substring(filePath.lastIndexOf("/") + 1, filePath.length); filePath.lastIndexOf("/") + 1,
FormData formData = FormData.fromMap( filePath.length,
{"file": await MultipartFile.fromFile(filePath, filename: name)},
); );
FormData formData = FormData.fromMap({
"file": await MultipartFile.fromFile(filePath, filename: name),
});
final response = await http.dio.post( final response = await http.dio.post(
"91458261d1ab337ad7b1da3610dd496cf7f58dbb0fecacfbdfe516c4892b1694", "91458261d1ab337ad7b1da3610dd496cf7f58dbb0fecacfbdfe516c4892b1694",
data: formData, data: formData,
@ -28,15 +31,15 @@ class SCGeneralRepositoryImp implements SocialChatGeneralRepository {
contentType: 'multipart/form-data', contentType: 'multipart/form-data',
sendTimeout: const Duration(seconds: 60), sendTimeout: const Duration(seconds: 60),
receiveTimeout: const Duration(seconds: 60), receiveTimeout: const Duration(seconds: 60),
extra: { extra: {TimeOutInterceptor.timeoutSecondsKey: 60},
TimeOutInterceptor.timeoutSecondsKey: 60,
},
), ),
); );
final baseResponse = ResponseData<String>.fromJson( final baseResponse = ResponseData<String>.fromJson(
response.data as Map<String, dynamic>, response.data as Map<String, dynamic>,
fromJsonT: (json) => json as String, fromJsonT: (json) => json as String,
); );
debugPrint("[OSS Upload] response.data: ${response.data}");
debugPrint("[OSS Upload] parsed file url: ${baseResponse.body ?? ""}");
return baseResponse.body ?? ""; return baseResponse.body ?? "";
} }
} }

View File

@ -34,6 +34,7 @@ import 'package:yumi/shared/business_logic/models/res/sc_violation_handle_res.da
import 'package:yumi/shared/business_logic/repositories/room_repository.dart'; import 'package:yumi/shared/business_logic/repositories/room_repository.dart';
import 'package:yumi/services/audio/rtm_manager.dart'; import 'package:yumi/services/audio/rtm_manager.dart';
import 'package:yumi/shared/data_sources/sources/remote/net/network_client.dart'; import 'package:yumi/shared/data_sources/sources/remote/net/network_client.dart';
import 'package:yumi/shared/tools/sc_room_profile_cache.dart';
class SCChatRoomRepository implements SocialChatRoomRepository { class SCChatRoomRepository implements SocialChatRoomRepository {
static SCChatRoomRepository? _instance; static SCChatRoomRepository? _instance;
@ -55,9 +56,10 @@ class SCChatRoomRepository implements SocialChatRoomRepository {
"363603111e51beac2d183014dd29b81ced652239ec13178acd7001096cab7429", "363603111e51beac2d183014dd29b81ced652239ec13178acd7001096cab7429",
queryParams: queryParams, queryParams: queryParams,
fromJson: fromJson:
(json) => (json as List).map((e) => SocialChatRoomRes.fromJson(e)).toList(), (json) =>
(json as List).map((e) => SocialChatRoomRes.fromJson(e)).toList(),
); );
return result; return result.map(SCRoomProfileCache.applyToSocialChatRoomRes).toList();
} }
///live/mic/list ///live/mic/list
@ -84,7 +86,7 @@ class SCChatRoomRepository implements SocialChatRoomRepository {
queryParams: queryParams, queryParams: queryParams,
fromJson: (json) => MyRoomRes.fromJson(json), fromJson: (json) => MyRoomRes.fromJson(json),
); );
return result; return SCRoomProfileCache.applyToMyRoomRes(result);
} }
///room/relation/status ///room/relation/status
@ -115,7 +117,9 @@ class SCChatRoomRepository implements SocialChatRoomRepository {
///activity/room-contribution-activity ///activity/room-contribution-activity
@override @override
Future<SCRoomContributeLevelRes> roomContributionActivity(String roomId) async { Future<SCRoomContributeLevelRes> roomContributionActivity(
String roomId,
) async {
Map<String, dynamic> queryParams = {}; Map<String, dynamic> queryParams = {};
queryParams["roomId"] = roomId; queryParams["roomId"] = roomId;
final result = await http.get( final result = await http.get(
@ -191,7 +195,10 @@ class SCChatRoomRepository implements SocialChatRoomRepository {
"a01ade8fbb604c0904557a9483fcc4d9", "a01ade8fbb604c0904557a9483fcc4d9",
queryParams: {"roomId": roomId}, queryParams: {"roomId": roomId},
fromJson: fromJson:
(json) => (json as List).map((e) => SocialChatUserProfile.fromJson(e)).toList(), (json) =>
(json as List)
.map((e) => SocialChatUserProfile.fromJson(e))
.toList(),
); );
return result; return result;
} }
@ -213,7 +220,8 @@ class SCChatRoomRepository implements SocialChatRoomRepository {
final result = await http.get<List<SocialChatGiftRes>>( final result = await http.get<List<SocialChatGiftRes>>(
"3a613b7450f8fd9082b988bd21df454d", "3a613b7450f8fd9082b988bd21df454d",
fromJson: fromJson:
(json) => (json as List).map((e) => SocialChatGiftRes.fromJson(e)).toList(), (json) =>
(json as List).map((e) => SocialChatGiftRes.fromJson(e)).toList(),
); );
return result; return result;
} }
@ -317,9 +325,10 @@ class SCChatRoomRepository implements SocialChatRoomRepository {
"363603111e51beac2d183014dd29b81cce9dda0f9cd8c267f6e4fd3f05bda090", "363603111e51beac2d183014dd29b81cce9dda0f9cd8c267f6e4fd3f05bda090",
queryParams: {"roomAccount": roomAccount}, queryParams: {"roomAccount": roomAccount},
fromJson: fromJson:
(json) => (json as List).map((e) => SocialChatRoomRes.fromJson(e)).toList(), (json) =>
(json as List).map((e) => SocialChatRoomRes.fromJson(e)).toList(),
); );
return result; return result.map(SCRoomProfileCache.applyToSocialChatRoomRes).toList();
} }
///room/blacklist/join ///room/blacklist/join
@ -500,7 +509,9 @@ class SCChatRoomRepository implements SocialChatRoomRepository {
queryParams: parm, queryParams: parm,
fromJson: fromJson:
(json) => (json) =>
(json as List).map((e) => SocialChatRoomMemberRes.fromJson(e)).toList(), (json as List)
.map((e) => SocialChatRoomMemberRes.fromJson(e))
.toList(),
); );
return result; return result;
} }
@ -584,7 +595,9 @@ class SCChatRoomRepository implements SocialChatRoomRepository {
"48ab0afa0e4305070eaa82edc3fc3996", "48ab0afa0e4305070eaa82edc3fc3996",
fromJson: fromJson:
(json) => (json) =>
(json as List).map((e) => SocialChatGiftBackpackRes.fromJson(e)).toList(), (json as List)
.map((e) => SocialChatGiftBackpackRes.fromJson(e))
.toList(),
); );
return result; return result;
} }
@ -775,7 +788,8 @@ class SCChatRoomRepository implements SocialChatRoomRepository {
final result = await http.get<List<SocialChatGiftRes>>( final result = await http.get<List<SocialChatGiftRes>>(
"60296415428765e04263c733bfc8428c397bb180e31856d94bf25d85eb342787", "60296415428765e04263c733bfc8428c397bb180e31856d94bf25d85eb342787",
fromJson: fromJson:
(json) => (json as List).map((e) => SocialChatGiftRes.fromJson(e)).toList(), (json) =>
(json as List).map((e) => SocialChatGiftRes.fromJson(e)).toList(),
); );
return result; return result;
} }
@ -815,11 +829,4 @@ class SCChatRoomRepository implements SocialChatRoomRepository {
); );
return result; return result;
} }
} }

View File

@ -3,7 +3,8 @@ import 'package:yumi/shared/business_logic/models/res/sc_gold_record_res.dart';
import 'package:yumi/shared/business_logic/models/res/sc_prop_coupon_list_res.dart'; import 'package:yumi/shared/business_logic/models/res/sc_prop_coupon_list_res.dart';
import 'package:yumi/shared/business_logic/models/res/sc_prop_coupon_record_list_res.dart'; import 'package:yumi/shared/business_logic/models/res/sc_prop_coupon_record_list_res.dart';
import 'package:yumi/shared/business_logic/models/res/sc_public_message_page_res.dart'; import 'package:yumi/shared/business_logic/models/res/sc_public_message_page_res.dart';
import 'package:yumi/shared/business_logic/models/res/room_res.dart' hide WearBadge; import 'package:yumi/shared/business_logic/models/res/room_res.dart'
hide WearBadge;
import 'package:yumi/shared/business_logic/models/res/sc_rtc_token_res.dart'; import 'package:yumi/shared/business_logic/models/res/sc_rtc_token_res.dart';
import 'package:yumi/shared/business_logic/models/res/sc_task_list_res.dart'; import 'package:yumi/shared/business_logic/models/res/sc_task_list_res.dart';
import 'package:yumi/shared/business_logic/models/res/sc_user_counter_res.dart'; import 'package:yumi/shared/business_logic/models/res/sc_user_counter_res.dart';
@ -13,9 +14,12 @@ import 'package:yumi/shared/business_logic/models/res/sc_user_red_packet_grab_re
import 'package:yumi/shared/business_logic/models/res/sc_user_red_packet_send_res.dart'; import 'package:yumi/shared/business_logic/models/res/sc_user_red_packet_send_res.dart';
import 'package:yumi/shared/business_logic/models/req/sc_mobile_auth_cmd.dart'; import 'package:yumi/shared/business_logic/models/req/sc_mobile_auth_cmd.dart';
import 'package:yumi/shared/business_logic/models/res/sc_edit_room_info_res.dart'; import 'package:yumi/shared/business_logic/models/res/sc_edit_room_info_res.dart';
import 'package:yumi/shared/business_logic/models/res/follow_room_res.dart' hide WearBadge; import 'package:yumi/shared/business_logic/models/res/follow_room_res.dart'
import 'package:yumi/shared/business_logic/models/res/follow_user_res.dart' hide WearBadge; hide WearBadge;
import 'package:yumi/shared/business_logic/models/res/join_room_res.dart' hide WearBadge; import 'package:yumi/shared/business_logic/models/res/follow_user_res.dart'
hide WearBadge;
import 'package:yumi/shared/business_logic/models/res/join_room_res.dart'
hide WearBadge;
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/business_logic/models/res/message_friend_user_res.dart'; import 'package:yumi/shared/business_logic/models/res/message_friend_user_res.dart';
import 'package:yumi/shared/business_logic/models/res/my_room_res.dart'; import 'package:yumi/shared/business_logic/models/res/my_room_res.dart';
@ -24,6 +28,7 @@ import 'package:yumi/shared/business_logic/models/res/sc_sign_in_res.dart';
import 'package:yumi/shared/business_logic/models/res/sc_violation_handle_res.dart'; import 'package:yumi/shared/business_logic/models/res/sc_violation_handle_res.dart';
import 'package:yumi/shared/business_logic/repositories/user_repository.dart'; import 'package:yumi/shared/business_logic/repositories/user_repository.dart';
import 'package:yumi/shared/data_sources/sources/remote/net/network_client.dart'; import 'package:yumi/shared/data_sources/sources/remote/net/network_client.dart';
import 'package:yumi/shared/tools/sc_room_profile_cache.dart';
class SCAccountRepository implements SocialChatUserRepository { class SCAccountRepository implements SocialChatUserRepository {
static SCAccountRepository? _instance; static SCAccountRepository? _instance;
@ -83,7 +88,10 @@ class SCAccountRepository implements SocialChatUserRepository {
///auth/account/login/channel ///auth/account/login/channel
@override @override
Future<SocialChatLoginRes> loginForChannel(String authType, String openId) async { Future<SocialChatLoginRes> loginForChannel(
String authType,
String openId,
) async {
final result = await http.post<SocialChatLoginRes>( final result = await http.post<SocialChatLoginRes>(
"e64f27b9ba6b37881120f4584a5444a5531a33eb137749a5decad584f58aaa44", "e64f27b9ba6b37881120f4584a5444a5531a33eb137749a5decad584f58aaa44",
data: {"authType": authType, "openId": openId}, data: {"authType": authType, "openId": openId},
@ -106,7 +114,7 @@ class SCAccountRepository implements SocialChatUserRepository {
(json) => (json) =>
(json as List).map((e) => FollowRoomRes.fromJson(e)).toList(), (json as List).map((e) => FollowRoomRes.fromJson(e)).toList(),
); );
return result; return result.map(SCRoomProfileCache.applyToFollowRoomRes).toList();
} }
///room/relation/joined ///room/relation/joined
@ -118,7 +126,7 @@ class SCAccountRepository implements SocialChatUserRepository {
(json) => (json) =>
(json as List).map((e) => FollowRoomRes.fromJson(e)).toList(), (json as List).map((e) => FollowRoomRes.fromJson(e)).toList(),
); );
return result; return result.map(SCRoomProfileCache.applyToFollowRoomRes).toList();
} }
///room/relation/trace ///room/relation/trace
@ -135,7 +143,7 @@ class SCAccountRepository implements SocialChatUserRepository {
(json) => (json) =>
(json as List).map((e) => FollowRoomRes.fromJson(e)).toList(), (json as List).map((e) => FollowRoomRes.fromJson(e)).toList(),
); );
return result; return result.map(SCRoomProfileCache.applyToFollowRoomRes).toList();
} }
///room/profile ///room/profile
@ -145,7 +153,7 @@ class SCAccountRepository implements SocialChatUserRepository {
"1e41384e55cbd6c2374608b129e2ed27", "1e41384e55cbd6c2374608b129e2ed27",
fromJson: (json) => MyRoomRes.fromJson(json), fromJson: (json) => MyRoomRes.fromJson(json),
); );
return result; return SCRoomProfileCache.applyToMyRoomRes(result);
} }
///room/live-voice/create ///room/live-voice/create
@ -179,7 +187,7 @@ class SCAccountRepository implements SocialChatUserRepository {
data: param, data: param,
fromJson: (json) => JoinRoomRes.fromJson(json), fromJson: (json) => JoinRoomRes.fromJson(json),
); );
return result; return SCRoomProfileCache.applyToJoinRoomRes(result);
} }
///live/user/heartbeat ///live/user/heartbeat
@ -235,13 +243,21 @@ class SCAccountRepository implements SocialChatUserRepository {
"1e41384e55cbd6c2374608b129e2ed27", "1e41384e55cbd6c2374608b129e2ed27",
data: { data: {
"id": roomId, "id": roomId,
"roomId": roomId,
"roomCover": roomCover, "roomCover": roomCover,
"cover": roomCover,
"roomName": roomName, "roomName": roomName,
"roomDesc": roomDesc, "roomDesc": roomDesc,
"event": event, "event": event,
}, },
fromJson: (json) => SCEditRoomInfoRes.fromJson(json), fromJson: (json) => SCEditRoomInfoRes.fromJson(json),
); );
await SCRoomProfileCache.saveRoomProfile(
roomId: roomId,
roomCover: roomCover,
roomName: roomName,
roomDesc: roomDesc,
);
return result; return result;
} }
@ -431,10 +447,10 @@ class SCAccountRepository implements SocialChatUserRepository {
parm["personalPhotos"] = personalPhotos; parm["personalPhotos"] = personalPhotos;
} }
final result = await http.put<SocialChatUserProfile>( final result = await http.put<SocialChatUserProfile>(
"2fa1d4f56bb726558904ce2a50b83f96365e62b6aa808acde7b56d2d5945fbe4", "2fa1d4f56bb726558904ce2a50b83f96365e62b6aa808acde7b56d2d5945fbe4",
data: parm, data: parm,
fromJson: (json) => SocialChatUserProfile.fromJson(json), fromJson: (json) => SocialChatUserProfile.fromJson(json),
); );
return result; return result;
} }
@ -535,7 +551,6 @@ class SCAccountRepository implements SocialChatUserRepository {
return result; return result;
} }
///user/user-level/consumption/exp ///user/user-level/consumption/exp
@override @override
Future<SCUserLevelExpRes> userLevelConsumptionExp( Future<SCUserLevelExpRes> userLevelConsumptionExp(
@ -757,7 +772,8 @@ class SCAccountRepository implements SocialChatUserRepository {
final result = await http.get<List<SCTaskListRes>>( final result = await http.get<List<SCTaskListRes>>(
"6e87048da643aa0388a9e46ea391daff574aff257ce7e668d08f4caccd1c6232", "6e87048da643aa0388a9e46ea391daff574aff257ce7e668d08f4caccd1c6232",
fromJson: fromJson:
(json) => (json as List).map((e) => SCTaskListRes.fromJson(e)).toList(), (json) =>
(json as List).map((e) => SCTaskListRes.fromJson(e)).toList(),
); );
return result; return result;
} }
@ -915,7 +931,6 @@ class SCAccountRepository implements SocialChatUserRepository {
return result; return result;
} }
///user/cp-relationship/create-apply ///user/cp-relationship/create-apply
@override @override
Future<bool> cpRelationshipSendApply(String acceptApplyUserId) async { Future<bool> cpRelationshipSendApply(String acceptApplyUserId) async {
@ -1086,7 +1101,6 @@ class SCAccountRepository implements SocialChatUserRepository {
return result; return result;
} }
///user/vip/ability/update ///user/vip/ability/update
@override @override
Future<bool> userVipAbilityUpdate({ Future<bool> userVipAbilityUpdate({

View File

@ -1,305 +1,567 @@
import 'package:flutter/animation.dart'; import 'dart:async';
import 'package:flutter/cupertino.dart'; import 'dart:collection';
import 'package:flutter_svga/flutter_svga.dart'; import 'dart:io';
import 'package:yumi/app/constants/sc_global_config.dart';
import 'package:yumi/shared/tools/sc_path_utils.dart'; import 'package:flutter/cupertino.dart';
import 'package:yumi/shared/data_sources/sources/local/file_cache_manager.dart'; import 'package:flutter_svga/flutter_svga.dart';
import 'package:tancent_vap/utils/constant.dart'; import 'package:yumi/app/constants/sc_global_config.dart';
import 'package:tancent_vap/widgets/vap_view.dart'; import 'package:yumi/shared/tools/sc_path_utils.dart';
import 'package:yumi/shared/data_sources/sources/local/file_cache_manager.dart';
import 'package:yumi/shared/data_sources/sources/local/data_persistence.dart'; import 'package:tancent_vap/utils/constant.dart';
import 'package:tancent_vap/widgets/vap_view.dart';
class SCGiftVapSvgaManager {
Map<String, MovieEntity> videoItemCache = {}; import 'package:yumi/shared/data_sources/sources/local/data_persistence.dart';
static SCGiftVapSvgaManager? _inst;
class SCGiftVapSvgaManager {
SCGiftVapSvgaManager._internal(); Map<String, MovieEntity> videoItemCache = {};
static SCGiftVapSvgaManager? _inst;
factory SCGiftVapSvgaManager() => _inst ??= SCGiftVapSvgaManager._internal(); static const int _maxPreloadConcurrency = 1;
final SCPriorityQueue<SCVapTask> _tq = SCPriorityQueue<SCVapTask>( SCGiftVapSvgaManager._internal();
(a, b) => a.compareTo(b), // SCVapTask compareTo
); factory SCGiftVapSvgaManager() => _inst ??= SCGiftVapSvgaManager._internal();
VapController? _rgc;
SVGAAnimationController? _rsc; final SCPriorityQueue<SCVapTask> _tq = SCPriorityQueue<SCVapTask>(
bool _play = false; (a, b) => a.compareTo(b), // SCVapTask compareTo
bool _dis = false; );
VapController? _rgc;
bool _pause = false; SVGAAnimationController? _rsc;
bool _play = false;
// bool _dis = false;
bool _mute = false; SCVapTask? _currentTask;
final Queue<String> _preloadQueue = Queue<String>();
void setMute(bool muteMusic) { final Set<String> _queuedPreloadPaths = <String>{};
_mute = muteMusic; final Map<String, Future<MovieEntity>> _svgaLoadTasks = {};
DataPersistence.setPlayGiftMusic(_mute); final Map<String, Future<String>> _playablePathTasks = {};
} final Map<String, String> _playablePathCache = {};
int _activePreloadCount = 0;
bool getMute() {
return _mute; bool _pause = false;
}
//
// bool _mute = false;
void bindVapCtrl(VapController vapController) {
_mute = DataPersistence.getPlayGiftMusic(); void setMute(bool muteMusic) {
_dis = false; _mute = muteMusic;
_rgc = vapController; DataPersistence.setPlayGiftMusic(_mute);
_rgc?.setAnimListener( }
onVideoStart: () {
}, bool getMute() {
onVideoComplete: () { return _mute;
_hcs(); }
},
onFailed: (code, type, msg) { void _log(String message) {
_hcs(); debugPrint('[GiftFX][Player] $message');
}, }
);
} bool _needsSvgaController(String path) {
return SCPathUtils.getFileExtension(path).toLowerCase() == ".svga";
void bindSvgaCtrl(SVGAAnimationController svgaController) { }
_dis = false;
_rsc = svgaController; bool _isControllerReady(SCVapTask task) {
_rsc?.addStatusListener((AnimationStatus status) { if (_needsSvgaController(task.path)) {
if (status.isCompleted) { return _rsc != null;
_rsc?.reset(); }
_play = false; return _rgc != null;
_pn(); }
}
}); bool _isPlayableFileReady(String path) {
} final cachedPath = _playablePathCache[path];
if (cachedPath == null || cachedPath.isEmpty) {
// return false;
void play( }
String path, { return File(cachedPath).existsSync();
int priority = 0, }
Map<String, VAPContent>? customResources,
int type = 0, bool _isPreloadedOrLoading(String path) {
}) { if (_needsSvgaController(path)) {
if (path.isEmpty) { return videoItemCache.containsKey(path) ||
return; _svgaLoadTasks.containsKey(path);
} }
if (SCGlobalConfig.sdkInt > SCGlobalConfig.maxSdkNoAnim) { final pathType = SCPathUtils.getPathType(path);
if (_dis) return; if (pathType == PathType.asset || pathType == PathType.file) {
final task = SCVapTask( return true;
path: path, }
priority: priority, return _isPlayableFileReady(path) || _playablePathTasks.containsKey(path);
customResources: customResources, }
);
Future<void> preload(String path, {bool highPriority = false}) async {
_tq.add(task); if (path.isEmpty || _dis || _isPreloadedOrLoading(path)) {
if (!_play) { return;
_pn(); }
} if (highPriority) {
} _log('high priority preload path=$path');
} await _warmupPath(path);
return;
// }
Future<void> _pn() async { if (_queuedPreloadPaths.contains(path)) {
if (_pause) { return;
return; }
} _preloadQueue.add(path);
if (_dis || _tq.isEmpty || _play) return; _queuedPreloadPaths.add(path);
_log('enqueue preload path=$path queue=${_preloadQueue.length}');
final task = _tq.removeFirst(); _drainPreloadQueue();
_play = true; }
try {
final pathType = SCPathUtils.getPathType(task.path); void _drainPreloadQueue() {
if (pathType == PathType.asset) { if (_dis ||
await _pa(task); _pause ||
} else if (pathType == PathType.file) { _play ||
await _pf(task); _activePreloadCount >= _maxPreloadConcurrency ||
} else if (pathType == PathType.network) { _preloadQueue.isEmpty) {
await _pnw(task); return;
} }
} catch (e, s) { final path = _preloadQueue.removeFirst();
print('VAP_SVGA播放失败: $e\n$s'); _queuedPreloadPaths.remove(path);
} finally { _activePreloadCount++;
// _log(
// if (!_dis) { 'start preload path=$path active=$_activePreloadCount '
// _pn(); 'queueRemaining=${_preloadQueue.length}',
// } );
} _warmupPath(path).whenComplete(() {
} _activePreloadCount--;
_log(
// 'finish preload path=$path active=$_activePreloadCount '
Future<void> _pa(SCVapTask task) async { 'queueRemaining=${_preloadQueue.length}',
if (_dis) return; );
if (SCPathUtils.getFileExtension(task.path).toLowerCase() == ".svga") { _drainPreloadQueue();
MovieEntity entity; });
if (videoItemCache.containsKey(task.path)) { }
entity = videoItemCache[task.path]!;
} else { Future<void> _warmupPath(String path) async {
entity = await SVGAParser.shared.decodeFromAssets(task.path); if (_needsSvgaController(path)) {
videoItemCache[task.path] = entity; await _loadSvgaEntity(path);
} return;
_rsc?.videoItem = entity; }
_rsc?.reset(); await _ensurePlayableFilePath(path);
_rsc?.forward(); }
} else {
if (task.customResources != null) { Future<MovieEntity> _loadSvgaEntity(String path) async {
task.customResources?.forEach((k, v) async { final cached = videoItemCache[path];
await _rgc?.setVapTagContent(k, v); if (cached != null) {
}); return cached;
} }
await _rgc?.playAsset(task.path); final loadingTask = _svgaLoadTasks[path];
} if (loadingTask != null) {
} return loadingTask;
}
// final future = () async {
Future<void> _pf(SCVapTask task) async { final pathType = SCPathUtils.getPathType(path);
if (_dis || _rgc == null) return; late final MovieEntity entity;
await _rgc?.setMute(_mute); if (pathType == PathType.asset) {
if (task.customResources != null) { entity = await SVGAParser.shared.decodeFromAssets(path);
task.customResources?.forEach((k, v) async { } else if (pathType == PathType.network) {
await _rgc?.setVapTagContent(k, v); entity = await SVGAParser.shared.decodeFromURL(path);
}); } else if (pathType == PathType.file) {
} final bytes = await File(path).readAsBytes();
await _rgc!.playFile(task.path); entity = await SVGAParser.shared.decodeFromBuffer(bytes);
} } else {
throw Exception('Unsupported SVGA path: $path');
// }
Future<void> _pnw(SCVapTask task) async { entity.autorelease = false;
if (_dis) return; videoItemCache[path] = entity;
if (SCPathUtils.getFileExtension(task.path).toLowerCase() == ".svga") { return entity;
MovieEntity? entity; }();
if (videoItemCache.containsKey(task.path)) { _svgaLoadTasks[path] = future;
entity = videoItemCache[task.path]!; try {
entity.autorelease = false; return await future;
} else { } finally {
try { _svgaLoadTasks.remove(path);
entity = await SVGAParser.shared.decodeFromURL(task.path); }
entity.autorelease = false; }
videoItemCache[task.path] = entity;
} catch (e) { Future<String> _ensurePlayableFilePath(String path) async {
_play = false; final pathType = SCPathUtils.getPathType(path);
print('svga解析出错$e'); if (pathType == PathType.asset || pathType == PathType.file) {
} return path;
} }
if (entity != null) { final cachedPath = _playablePathCache[path];
_rsc?.videoItem = entity; if (cachedPath != null &&
_rsc?.reset(); cachedPath.isNotEmpty &&
_rsc?.forward(); File(cachedPath).existsSync()) {
} return cachedPath;
} else { }
final file = await FileCacheManager.getInstance().getFile(url: task.path); final loadingTask = _playablePathTasks[path];
if (file != null && !_dis) { if (loadingTask != null) {
await _pf( return loadingTask;
SCVapTask( }
path: file.path, final future = () async {
priority: task.priority, final file = await FileCacheManager.getInstance().getFile(url: path);
customResources: task.customResources, _playablePathCache[path] = file.path;
), return file.path;
); }();
} _playablePathTasks[path] = future;
} try {
} return await future;
} finally {
// _playablePathTasks.remove(path);
void _hcs() { }
if (_rgc != null && !_dis) { }
_play = false;
// void _scheduleNextTask({Duration delay = Duration.zero}) {
Future.delayed(const Duration(milliseconds: 50), _pn); if (_dis) {
} return;
} }
if (delay == Duration.zero) {
// Future.microtask(_pn);
void pauseAnim() { return;
_pause = true; }
} Future.delayed(delay, _pn);
}
//
void resumeAnim() { void _finishCurrentTask({Duration delay = Duration.zero}) {
_pause = false; if (_dis) {
_pn(); return;
} }
_play = false;
// _currentTask = null;
void dispose() { _scheduleNextTask(delay: delay);
_dis = true; if (delay == Duration.zero) {
_play = false; Future.microtask(_drainPreloadQueue);
_tq.clear(); } else {
_rgc?.stop(); Future.delayed(delay, _drainPreloadQueue);
_rgc?.dispose(); }
_rgc = null; }
_rsc?.stop();
_rsc?.dispose(); //
_rsc = null; void bindVapCtrl(VapController vapController) {
} _mute = DataPersistence.getPlayGiftMusic();
_dis = false;
// _rgc = vapController;
void clearTasks() { _log(
_play = false; 'bindVapCtrl hasVapCtrl=${_rgc != null} '
_tq.clear(); 'hasSvgaCtrl=${_rsc != null} queue=${_tq.length} mute=$_mute',
_pause = false; );
} _rgc?.setAnimListener(
} onVideoStart: () {
_log('vap onVideoStart path=${_currentTask?.path}');
// },
// 1. SCVapTask Comparable onVideoComplete: () {
_log('vap onVideoComplete path=${_currentTask?.path}');
class SCVapTask implements Comparable<SCVapTask> { _hcs();
final String path; },
final int type; onFailed: (code, type, msg) {
final int priority; _log(
final Map<String, VAPContent>? customResources; 'vap onFailed path=${_currentTask?.path} code=$code type=$type msg=$msg',
final int _seq; );
_hcs();
static int _nextSeq = 0; },
);
SCVapTask({ _scheduleNextTask();
required this.path, _drainPreloadQueue();
this.priority = 0, }
this.customResources,
this.type = 0, void bindSvgaCtrl(SVGAAnimationController svgaController) {
}) : _seq = _nextSeq++; _dis = false;
_rsc = svgaController;
@override _log(
int compareTo(SCVapTask other) { 'bindSvgaCtrl hasSvgaCtrl=${_rsc != null} '
// 'hasVapCtrl=${_rgc != null} queue=${_tq.length}',
int priorityComparison = other.priority.compareTo(priority); );
if (priorityComparison != 0) { _rsc?.addStatusListener((AnimationStatus status) {
return priorityComparison; if (status.isCompleted) {
} _log('svga completed path=${_currentTask?.path}');
// _rsc?.reset();
return _seq.compareTo(other._seq); _play = false;
} _currentTask = null;
_pn();
@override }
String toString() { });
return 'SCVapTask{path: $path, priority: $priority, seq: $_seq}'; _scheduleNextTask();
} _drainPreloadQueue();
} }
// //
class SCPriorityQueue<E> { void play(
final List<E> _els = []; String path, {
final Comparator<E> _cmp; int priority = 0,
Map<String, VAPContent>? customResources,
SCPriorityQueue(this._cmp); int type = 0,
}) {
void add(E element) { if (path.isEmpty) {
_els.add(element); _log('play ignored because path is empty');
_els.sort(_cmp); return;
} }
_log(
E removeFirst() { 'play request path=$path ext=${SCPathUtils.getFileExtension(path)} '
if (isEmpty) throw StateError("No elements"); 'priority=$priority type=$type '
return _els.removeAt(0); 'sdkInt=${SCGlobalConfig.sdkInt} maxSdkNoAnim=${SCGlobalConfig.maxSdkNoAnim} '
} 'disposed=$_dis playing=$_play queueBefore=${_tq.length} '
'hasSvgaCtrl=${_rsc != null} hasVapCtrl=${_rgc != null}',
E? get first => isEmpty ? null : _els.first; );
if (SCGlobalConfig.sdkInt > SCGlobalConfig.maxSdkNoAnim) {
bool get isEmpty => _els.isEmpty; if (_dis) {
_log('play ignored because manager is disposed path=$path');
bool get isNotEmpty => _els.isNotEmpty; return;
}
int get length => _els.length; final task = SCVapTask(
path: path,
void clear() => _els.clear(); priority: priority,
customResources: customResources,
List<E> get unorderedElements => List.from(_els); );
// Iterable _tq.add(task);
Iterator<E> get iterator => _els.iterator; _log('task enqueued path=$path queueAfter=${_tq.length}');
} if (!_play) {
_pn();
}
} else {
_log(
'play ignored because sdkInt <= maxSdkNoAnim '
'sdkInt=${SCGlobalConfig.sdkInt} maxSdkNoAnim=${SCGlobalConfig.maxSdkNoAnim}',
);
}
}
//
Future<void> _pn() async {
if (_pause) {
_log('skip _pn because paused queue=${_tq.length}');
return;
}
if (_dis || _tq.isEmpty || _play) return;
final task = _tq.first;
if (task == null || !_isControllerReady(task)) {
_log(
'controller not ready for path=${task?.path} '
'needSvga=${task != null ? _needsSvgaController(task.path) : "unknown"} '
'hasSvgaCtrl=${_rsc != null} hasVapCtrl=${_rgc != null} '
'queue=${_tq.length}',
);
return;
}
_tq.removeFirst();
_play = true;
_currentTask = task;
try {
final pathType = SCPathUtils.getPathType(task.path);
_log(
'start task path=${task.path} '
'pathType=$pathType '
'queueRemaining=${_tq.length} '
'needSvga=${_needsSvgaController(task.path)}',
);
if (pathType == PathType.asset) {
await _pa(task);
} else if (pathType == PathType.file) {
await _pf(task);
} else if (pathType == PathType.network) {
await _pnw(task);
} else {
debugPrint('VAP_SVGA不支持的路径类型: ${task.path}');
_finishCurrentTask();
}
} catch (e, s) {
debugPrint('VAP_SVGA播放失败: $e\n$s');
_finishCurrentTask();
}
}
//
Future<void> _pa(SCVapTask task) async {
if (_dis) return;
if (SCPathUtils.getFileExtension(task.path).toLowerCase() == ".svga") {
_log('play asset svga path=${task.path}');
final entity = await _loadSvgaEntity(task.path);
_log('use prepared asset svga path=${task.path}');
_rsc?.videoItem = entity;
_rsc?.reset();
_rsc?.forward();
_log('forward asset svga path=${task.path}');
} else {
_log('play asset vap/mp4 path=${task.path}');
if (task.customResources != null) {
task.customResources?.forEach((k, v) async {
await _rgc?.setVapTagContent(k, v);
});
}
await _rgc?.playAsset(task.path);
}
}
//
Future<void> _pf(SCVapTask task) async {
if (_dis) {
return;
}
if (SCPathUtils.getFileExtension(task.path).toLowerCase() == ".svga") {
final entity = await _loadSvgaEntity(task.path);
_log('play local svga file path=${task.path}');
_rsc?.videoItem = entity;
_rsc?.reset();
_rsc?.forward();
return;
}
if (_rgc == null) {
_log('skip playFile because vap controller is null path=${task.path}');
_finishCurrentTask();
return;
}
_log('play local vap/mp4 file path=${task.path}');
await _rgc?.setMute(_mute);
if (task.customResources != null) {
task.customResources?.forEach((k, v) async {
await _rgc?.setVapTagContent(k, v);
});
}
await _rgc!.playFile(task.path);
}
//
Future<void> _pnw(SCVapTask task) async {
if (_dis) return;
if (SCPathUtils.getFileExtension(task.path).toLowerCase() == ".svga") {
_log('play network svga path=${task.path}');
late final MovieEntity entity;
try {
entity = await _loadSvgaEntity(task.path);
} catch (e) {
debugPrint('svga解析出错$e');
_finishCurrentTask();
return;
}
_log('use prepared network svga path=${task.path}');
_rsc?.videoItem = entity;
_rsc?.reset();
_rsc?.forward();
_log('forward network svga path=${task.path}');
} else {
_log('download network vap/mp4 path=${task.path}');
final playablePath = await _ensurePlayableFilePath(task.path);
if (!_dis) {
_log('use prepared network vap/mp4 local path=$playablePath');
await _pf(
SCVapTask(
path: playablePath,
priority: task.priority,
customResources: task.customResources,
),
);
}
}
}
//
void _hcs() {
if (_rgc != null && !_dis) {
_log('finish vap task path=${_currentTask?.path}');
_finishCurrentTask(delay: const Duration(milliseconds: 50));
}
}
//
void pauseAnim() {
_pause = true;
_log('pauseAnim queue=${_tq.length}');
}
//
void resumeAnim() {
_pause = false;
_log('resumeAnim queue=${_tq.length}');
_pn();
}
//
void dispose() {
_log('dispose queue=${_tq.length} currentPath=${_currentTask?.path}');
_dis = true;
_play = false;
_currentTask = null;
_tq.clear();
_preloadQueue.clear();
_queuedPreloadPaths.clear();
_activePreloadCount = 0;
_rgc?.stop();
_rgc?.dispose();
_rgc = null;
_rsc?.stop();
_rsc?.dispose();
_rsc = null;
}
//
void clearTasks() {
_log(
'clearTasks queueBefore=${_tq.length} currentPath=${_currentTask?.path}',
);
_play = false;
_currentTask = null;
_tq.clear();
_preloadQueue.clear();
_queuedPreloadPaths.clear();
_activePreloadCount = 0;
_pause = false;
}
}
//
// 1. SCVapTask Comparable
class SCVapTask implements Comparable<SCVapTask> {
final String path;
final int type;
final int priority;
final Map<String, VAPContent>? customResources;
final int _seq;
static int _nextSeq = 0;
SCVapTask({
required this.path,
this.priority = 0,
this.customResources,
this.type = 0,
}) : _seq = _nextSeq++;
@override
int compareTo(SCVapTask other) {
//
int priorityComparison = other.priority.compareTo(priority);
if (priorityComparison != 0) {
return priorityComparison;
}
//
return _seq.compareTo(other._seq);
}
@override
String toString() {
return 'SCVapTask{path: $path, priority: $priority, seq: $_seq}';
}
}
//
class SCPriorityQueue<E> {
final List<E> _els = [];
final Comparator<E> _cmp;
SCPriorityQueue(this._cmp);
void add(E element) {
_els.add(element);
_els.sort(_cmp);
}
E removeFirst() {
if (isEmpty) throw StateError("No elements");
return _els.removeAt(0);
}
E? get first => isEmpty ? null : _els.first;
bool get isEmpty => _els.isEmpty;
bool get isNotEmpty => _els.isNotEmpty;
int get length => _els.length;
void clear() => _els.clear();
List<E> get unorderedElements => List.from(_els);
// Iterable
Iterator<E> get iterator => _els.iterator;
}

View File

@ -52,6 +52,7 @@ class SCPathUtils {
'.gif', '.gif',
'.mp4', '.mp4',
'.vap', '.vap',
'.svga',
].contains(ext); ].contains(ext);
return isDirectAsset && isSupportedAsset; return isDirectAsset && isSupportedAsset;
@ -74,6 +75,7 @@ class SCPathUtils {
'.mp4', '.mp4',
'.mov', '.mov',
'.avi', '.avi',
'.svga',
'.png', '.png',
'.jpg', '.jpg',
'.txt', '.txt',
@ -132,7 +134,6 @@ class SCPathUtils {
return false; return false;
} }
static bool fileTypeIsPic(String filePath) { static bool fileTypeIsPic(String filePath) {
String extension = path.extension(filePath).toLowerCase(); String extension = path.extension(filePath).toLowerCase();

View File

@ -0,0 +1,146 @@
import 'dart:convert';
import 'package:yumi/shared/business_logic/models/res/follow_room_res.dart'
as follow;
import 'package:yumi/shared/business_logic/models/res/join_room_res.dart'
as join;
import 'package:yumi/shared/business_logic/models/res/my_room_res.dart';
import 'package:yumi/shared/business_logic/models/res/room_res.dart';
import 'package:yumi/shared/data_sources/sources/local/data_persistence.dart';
import 'package:yumi/shared/data_sources/sources/local/user_manager.dart';
class SCRoomProfileCache {
static const String _prefix = "room_profile_override";
static String _key(String roomId) {
final userId =
AccountStorage().getCurrentUser()?.userProfile?.id ?? "guest";
return "${_prefix}_${userId}_$roomId";
}
static String? preferNonEmpty(String? primary, String? fallback) {
if ((primary ?? "").trim().isNotEmpty) {
return primary;
}
if ((fallback ?? "").trim().isNotEmpty) {
return fallback;
}
return primary ?? fallback;
}
static String? preferCachedValue(String? cached, String? remote) {
if ((cached ?? "").trim().isNotEmpty) {
return cached;
}
return preferNonEmpty(remote, cached);
}
static Map<String, String> getRoomProfile(String roomId) {
if (roomId.trim().isEmpty) {
return {};
}
final raw = DataPersistence.getString(_key(roomId));
if (raw.isEmpty) {
return {};
}
try {
final data = jsonDecode(raw);
if (data is Map) {
return data.map(
(key, value) => MapEntry(key.toString(), value?.toString() ?? ""),
);
}
} catch (_) {}
return {};
}
static Future<void> saveRoomProfile({
required String roomId,
String? roomCover,
String? roomName,
String? roomDesc,
}) async {
if (roomId.trim().isEmpty) {
return;
}
final current = getRoomProfile(roomId);
final payload = <String, String>{
"roomCover": preferNonEmpty(roomCover, current["roomCover"]) ?? "",
"roomName": preferNonEmpty(roomName, current["roomName"]) ?? "",
"roomDesc": preferNonEmpty(roomDesc, current["roomDesc"]) ?? "",
};
await DataPersistence.setString(_key(roomId), jsonEncode(payload));
}
static SocialChatRoomRes applyToSocialChatRoomRes(SocialChatRoomRes room) {
final cache = getRoomProfile(room.id ?? "");
if (cache.isEmpty) {
return room;
}
return room.copyWith(
roomCover: preferCachedValue(cache["roomCover"], room.roomCover),
roomName: preferCachedValue(cache["roomName"], room.roomName),
roomDesc: preferCachedValue(cache["roomDesc"], room.roomDesc),
);
}
static follow.FollowRoomRes applyToFollowRoomRes(follow.FollowRoomRes room) {
final roomProfile = room.roomProfile;
if (roomProfile == null) {
return room;
}
final cache = getRoomProfile(roomProfile.id ?? "");
if (cache.isEmpty) {
return room;
}
return room.copyWith(
roomProfile: roomProfile.copyWith(
roomCover: preferCachedValue(cache["roomCover"], roomProfile.roomCover),
roomName: preferCachedValue(cache["roomName"], roomProfile.roomName),
roomDesc: preferCachedValue(cache["roomDesc"], roomProfile.roomDesc),
),
);
}
static MyRoomRes applyToMyRoomRes(MyRoomRes room) {
final cache = getRoomProfile(room.id ?? "");
if (cache.isEmpty) {
return room;
}
return room.copyWith(
roomCover: preferCachedValue(cache["roomCover"], room.roomCover),
roomName: preferCachedValue(cache["roomName"], room.roomName),
roomDesc: preferCachedValue(cache["roomDesc"], room.roomDesc),
);
}
static join.JoinRoomRes applyToJoinRoomRes(join.JoinRoomRes room) {
final roomProfile = room.roomProfile;
final basicRoomProfile = roomProfile?.roomProfile;
if (roomProfile == null || basicRoomProfile == null) {
return room;
}
final cache = getRoomProfile(basicRoomProfile.id ?? "");
if (cache.isEmpty) {
return room;
}
return room.copyWith(
roomProfile: roomProfile.copyWith(
roomProfile: basicRoomProfile.copyWith(
roomCover: preferCachedValue(
cache["roomCover"],
basicRoomProfile.roomCover,
),
roomName: preferCachedValue(
cache["roomName"],
basicRoomProfile.roomName,
),
roomDesc: preferCachedValue(
cache["roomDesc"],
basicRoomProfile.roomDesc,
),
),
),
);
}
}

View File

@ -11,9 +11,18 @@ import 'package:yumi/ui_kit/widgets/headdress/headdress_widget.dart';
import 'package:yumi/app/constants/sc_global_config.dart'; import 'package:yumi/app/constants/sc_global_config.dart';
import 'package:yumi/app/constants/sc_screen.dart'; import 'package:yumi/app/constants/sc_screen.dart';
import 'package:yumi/shared/tools/sc_path_utils.dart'; import 'package:yumi/shared/tools/sc_path_utils.dart';
import 'package:yumi/shared/tools/sc_room_profile_cache.dart';
import '../../shared/data_sources/models/enum/sc_room_roles_type.dart'; import '../../shared/data_sources/models/enum/sc_room_roles_type.dart';
const String kRoomCoverDefaultImg = "sc_images/general/sc_no_data.png";
String resolveRoomCoverUrl(String? roomId, String? roomCover) {
final cache = SCRoomProfileCache.getRoomProfile(roomId ?? "");
return SCRoomProfileCache.preferCachedValue(cache["roomCover"], roomCover) ??
"";
}
Widget head({ Widget head({
required String url, required String url,
required double width, required double width,
@ -26,52 +35,99 @@ Widget head({
bool showDefault = true, bool showDefault = true,
bool isRoom = false, bool isRoom = false,
}) { }) {
final bool hasPictureHeaddress =
headdress != null &&
headdress.isNotEmpty &&
SCPathUtils.fileTypeIsPic2(headdress);
final double avatarScale =
hasPictureHeaddress
? 0.64
: (headdress != null && headdress.isNotEmpty ? 0.72 : 1);
final double avatarWidth = width * avatarScale;
final double avatarHeight = (height ?? width) * avatarScale;
Widget avatar = SizedBox(
width: avatarWidth,
height: avatarHeight,
child: netImage(
url: url,
width: avatarWidth,
height: avatarHeight,
fit: fit,
noDefaultImg: !showDefault,
defaultImg: "sc_images/general/sc_icon_avar_defalt.png",
),
);
if (shape == BoxShape.circle) {
avatar = ClipOval(child: avatar);
if (border != null) {
avatar = Container(
width: avatarWidth,
height: avatarHeight,
decoration: ShapeDecoration(shape: CircleBorder(side: border.top)),
clipBehavior: Clip.antiAlias,
child: avatar,
);
}
} else if (borderRadius != null) {
avatar = ClipRRect(borderRadius: borderRadius, child: avatar);
if (border != null) {
avatar = Container(
width: avatarWidth,
height: avatarHeight,
decoration: BoxDecoration(border: border, borderRadius: borderRadius),
clipBehavior: Clip.antiAlias,
child: avatar,
);
}
} else if (border != null) {
avatar = Container(
width: avatarWidth,
height: avatarHeight,
decoration: BoxDecoration(border: border),
clipBehavior: Clip.antiAlias,
child: avatar,
);
}
return RepaintBoundary( return RepaintBoundary(
child: Stack( child: Stack(
alignment: Alignment.center, alignment: Alignment.center,
children: [ children: [
Container( avatar,
padding: EdgeInsets.all(width * 0.12), hasPictureHeaddress
width: width, ? ColorFiltered(
height: height ?? width, colorFilter: const ColorFilter.matrix([
child: ExtendedImage.network( 1,
url, 0,
width: width, 0,
height: height ?? width, 0,
fit: fit, 0,
cache: true, 0,
shape: shape, 1,
border: border, 0,
clearMemoryCacheWhenDispose: false, 0,
clearMemoryCacheIfFailed: true, 0,
borderRadius: borderRadius, 0,
loadStateChanged: (ExtendedImageState state) { 0,
if (state.extendedImageLoadState == LoadState.completed) { 1,
return ExtendedRawImage( 0,
image: state.extendedImageInfo?.image, 0,
fit: fit, -1,
); -1,
} else if (state.extendedImageLoadState == LoadState.loading) { -1,
if (showDefault) { 3,
return ExtendedImage.asset( 0,
shape: shape, ]),
"sc_images/general/sc_icon_loading.png", child: netImage(
fit: BoxFit.cover, url: headdress,
); width: width,
} height: height ?? width,
return Container(); fit: BoxFit.contain,
} else { noDefaultImg: true,
return ExtendedImage.asset( ),
shape: shape, )
"sc_images/general/sc_icon_avar_defalt.png",
fit: BoxFit.cover,
);
}
},
),
),
headdress != null && SCPathUtils.fileTypeIsPic2(headdress)
? netImage(url: headdress, width: width, height: width)
: Container(), : Container(),
headdress != null && headdress != null &&
SCPathUtils.getFileExtension(headdress).toLowerCase() == SCPathUtils.getFileExtension(headdress).toLowerCase() ==
@ -86,7 +142,8 @@ Widget head({
) )
: Container(), : Container(),
headdress != null && headdress != null &&
SCPathUtils.getFileExtension(headdress).toLowerCase() == ".mp4" && SCPathUtils.getFileExtension(headdress).toLowerCase() ==
".mp4" &&
SCGlobalConfig.sdkInt > SCGlobalConfig.maxSdkNoAnim SCGlobalConfig.sdkInt > SCGlobalConfig.maxSdkNoAnim
? SizedBox( ? SizedBox(
width: width, width: width,
@ -301,7 +358,7 @@ searchWidget({
"sc_images/index/sc_icon_serach2.png", "sc_images/index/sc_icon_serach2.png",
width: 20.w, width: 20.w,
color: serachIconColor ?? Color(0xff18F2B1), color: serachIconColor ?? Color(0xff18F2B1),
height: 20.w height: 20.w,
), ),
SizedBox(width: width(6)), SizedBox(width: width(6)),
_buildPhoneInput( _buildPhoneInput(
@ -343,7 +400,10 @@ _buildPhoneInput(
cursorColor: SocialChatTheme.primaryColor, cursorColor: SocialChatTheme.primaryColor,
decoration: InputDecoration( decoration: InputDecoration(
hintText: tint, hintText: tint,
hintStyle: TextStyle(color: SocialChatTheme.textSecondary, fontSize: hintSize ?? sp(14)), hintStyle: TextStyle(
color: SocialChatTheme.textSecondary,
fontSize: hintSize ?? sp(14),
),
counterText: '', counterText: '',
isDense: true, isDense: true,
filled: false, filled: false,

View File

@ -12,211 +12,215 @@ import 'package:yumi/shared/business_logic/models/res/join_room_res.dart';
import 'package:yumi/services/audio/rtc_manager.dart'; import 'package:yumi/services/audio/rtc_manager.dart';
import 'package:yumi/app/constants/sc_screen.dart'; import 'package:yumi/app/constants/sc_screen.dart';
import 'package:yumi/ui_kit/theme/socialchat_theme.dart'; import 'package:yumi/ui_kit/theme/socialchat_theme.dart';
// //
Offset kDefaultFloatOffset = Offset( Offset kDefaultFloatOffset = Offset(
ScreenUtil().screenWidth - width(90), ScreenUtil().screenWidth - width(90),
ScreenUtil().screenHeight - height(120), ScreenUtil().screenHeight - height(120),
); );
class SCFloatIchart { class SCFloatIchart {
/// ///
static final SCFloatIchart _SCFloatIchart = SCFloatIchart._internal(); //1 static final SCFloatIchart _SCFloatIchart = SCFloatIchart._internal(); //1
factory SCFloatIchart() { factory SCFloatIchart() {
return _SCFloatIchart; return _SCFloatIchart;
} }
SCFloatIchart._internal(); SCFloatIchart._internal();
bool _inserted = false; bool _inserted = false;
OverlayEntry? overlayEntry; OverlayEntry? overlayEntry;
BuildContext? context; BuildContext? context;
Offset offset = kDefaultFloatOffset; Offset offset = kDefaultFloatOffset;
show() { show() {
if (overlayEntry != null) { if (overlayEntry != null) {
remove(); // remove(); //
} }
var overlayState = Overlay.of(context!); var overlayState = Overlay.of(context!);
overlayEntry = OverlayEntry( overlayEntry = OverlayEntry(
builder: (context) { builder: (context) {
if (SCNavigatorUtils.inLoginPage) { if (SCNavigatorUtils.inLoginPage) {
remove(); remove();
return Container(); return Container();
} else { } else {
return buildToastLayout(); return buildToastLayout();
} }
}, },
); );
overlayState.insert(overlayEntry!); overlayState.insert(overlayEntry!);
_inserted = true; // _inserted = true; //
} }
bool isShow() { bool isShow() {
return _inserted; return _inserted;
} }
remove() { remove() {
if (overlayEntry != null && overlayEntry!.mounted) { if (overlayEntry != null && overlayEntry!.mounted) {
if (_inserted) { if (_inserted) {
overlayEntry?.remove(); overlayEntry?.remove();
_inserted = false; _inserted = false;
} }
} }
} }
LayoutBuilder buildToastLayout() { LayoutBuilder buildToastLayout() {
Timer? timer; Timer? timer;
return LayoutBuilder( return LayoutBuilder(
builder: (context, constraints) { builder: (context, constraints) {
return Stack( return Stack(
children: <Widget>[ children: <Widget>[
Positioned( Positioned(
left: offset.dx, left: offset.dx,
top: offset.dy, top: offset.dy,
child: GestureDetector( child: GestureDetector(
//child的位置 //child的位置
onPanUpdate: (details) { onPanUpdate: (details) {
var localPosition = details.delta; var localPosition = details.delta;
var dx = (offset.dx + localPosition.dx).clamp( var dx = (offset.dx + localPosition.dx).clamp(
0.0, 0.0,
ScreenUtil().screenWidth - width(90), ScreenUtil().screenWidth - width(90),
); );
var dy = (offset.dy + localPosition.dy).clamp( var dy = (offset.dy + localPosition.dy).clamp(
0.0, 0.0,
ScreenUtil().screenHeight - height(55) - kToolbarHeight, ScreenUtil().screenHeight - height(55) - kToolbarHeight,
); );
offset = Offset(dx, dy); offset = Offset(dx, dy);
overlayEntry!.markNeedsBuild(); overlayEntry!.markNeedsBuild();
}, },
//child贴边悬浮 //child贴边悬浮
onPanEnd: (details) { onPanEnd: (details) {
var oldPosition = offset.dx; var oldPosition = offset.dx;
var targets = var targets =
offset.dx + width(80) > (ScreenUtil().screenWidth / 2) offset.dx + width(80) > (ScreenUtil().screenWidth / 2)
? ScreenUtil().screenWidth - width(90) ? ScreenUtil().screenWidth - width(90)
: 0; : 0;
timer = Timer.periodic(Duration(milliseconds: 1), (t) { timer = Timer.periodic(Duration(milliseconds: 1), (t) {
if (targets > 0) { if (targets > 0) {
oldPosition++; oldPosition++;
} else { } else {
oldPosition--; oldPosition--;
} }
offset = Offset(oldPosition.toDouble(), offset.dy); offset = Offset(oldPosition.toDouble(), offset.dy);
overlayEntry!.markNeedsBuild(); overlayEntry!.markNeedsBuild();
if (oldPosition < 0 || if (oldPosition < 0 ||
oldPosition > ScreenUtil().screenWidth - width(90)) { oldPosition > ScreenUtil().screenWidth - width(90)) {
timer?.cancel(); timer?.cancel();
} }
}); });
}, },
child: Container( child: Container(
child: float(), child: float(),
margin: EdgeInsets.only(bottom: height(kToolbarHeight)), margin: EdgeInsets.only(bottom: height(kToolbarHeight)),
), ),
), ),
), ),
], ],
); );
}, },
); );
} }
Widget float() { Widget float() {
JoinRoomRes? room = JoinRoomRes? room =
Provider.of<RealTimeCommunicationManager>( Provider.of<RealTimeCommunicationManager>(
context!, context!,
listen: false, listen: false,
).currenRoom; ).currenRoom;
// User user = Provider.of<RealTimeCommunicationManager>(context, listen: false).maiMap[0]; // User user = Provider.of<RealTimeCommunicationManager>(context, listen: false).maiMap[0];
SocialChatLoginRes? user = AccountStorage().getCurrentUser(); SocialChatLoginRes? user = AccountStorage().getCurrentUser();
return FloatRoomWindow(room: room, user: user, remove: remove); return FloatRoomWindow(room: room, user: user, remove: remove);
} }
void init(BuildContext context) { void init(BuildContext context) {
this.context = context; this.context = context;
} }
} }
class FloatRoomWindow extends StatefulWidget { class FloatRoomWindow extends StatefulWidget {
final JoinRoomRes? room; final JoinRoomRes? room;
final SocialChatLoginRes? user; final SocialChatLoginRes? user;
final Function remove; final Function remove;
const FloatRoomWindow({Key? key, this.room, this.user, required this.remove}) const FloatRoomWindow({Key? key, this.room, this.user, required this.remove})
: super(key: key); : super(key: key);
@override @override
_FloatRoomWindowState createState() => _FloatRoomWindowState(); _FloatRoomWindowState createState() => _FloatRoomWindowState();
} }
class _FloatRoomWindowState extends State<FloatRoomWindow> { class _FloatRoomWindowState extends State<FloatRoomWindow> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
} }
@override @override
void dispose() { void dispose() {
super.dispose(); super.dispose();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SCDebounceWidget( return SCDebounceWidget(
onTap: () { onTap: () {
SCRoomUtils.openCurrentRoom(context); SCRoomUtils.openCurrentRoom(context);
}, },
child: Container( child: Container(
height: 55.w, height: 55.w,
width: 90.w, width: 90.w,
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8.w), borderRadius: BorderRadius.circular(8.w),
color: SocialChatTheme.primaryLight, color: SocialChatTheme.primaryLight,
), ),
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[ children: <Widget>[
SizedBox(width: 8.w), SizedBox(width: 8.w),
Stack( Stack(
alignment: Alignment.center, alignment: Alignment.center,
children: [ children: [
netImage( netImage(
url: widget.room?.roomProfile?.roomProfile?.roomCover ?? "", url: resolveRoomCoverUrl(
width: 40.w, widget.room?.roomProfile?.roomProfile?.id,
height: 40.w, widget.room?.roomProfile?.roomProfile?.roomCover,
borderRadius: BorderRadius.circular(8.w), ),
), defaultImg: kRoomCoverDefaultImg,
Image.asset( width: 40.w,
"sc_images/general/sc_icon_online_user.png", height: 40.w,
width: 15.w, borderRadius: BorderRadius.circular(8.w),
height: 15.w, ),
), Image.asset(
], "sc_images/general/sc_icon_online_user.png",
), width: 15.w,
SizedBox(width: width(5)), height: 15.w,
SCDebounceWidget( ),
onTap: () { ],
Provider.of<RealTimeCommunicationManager>( ),
context, SizedBox(width: width(5)),
listen: false, SCDebounceWidget(
).exitCurrentVoiceRoomSession(false); onTap: () {
Timer(Duration(milliseconds: 550), () { Provider.of<RealTimeCommunicationManager>(
widget.remove.call(); context,
}); listen: false,
}, ).exitCurrentVoiceRoomSession(false);
child: Padding( Timer(Duration(milliseconds: 550), () {
padding: EdgeInsets.all(5.w), widget.remove.call();
child: Image.asset( });
"sc_images/index/sc_icon_room_flot_close.png", },
width: 20.w, child: Padding(
height: 20.w, padding: EdgeInsets.all(5.w),
), child: Image.asset(
), "sc_images/index/sc_icon_room_flot_close.png",
), width: 20.w,
], height: 20.w,
), ),
), ),
); ),
} ],
} ),
),
);
}
}

View File

@ -17,10 +17,15 @@ class _VapPlusSvgaPlayerState extends State<VapPlusSvgaPlayer>
with TickerProviderStateMixin { with TickerProviderStateMixin {
late SVGAAnimationController _svgaController; late SVGAAnimationController _svgaController;
void _giftFxLog(String message) {
debugPrint('[GiftFX][Widget] $message');
}
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_svgaController = SVGAAnimationController(vsync: this); _svgaController = SVGAAnimationController(vsync: this);
_giftFxLog('initState tag=${widget.tag}');
if (widget.tag == "room_gift") { if (widget.tag == "room_gift") {
SCGiftVapSvgaManager().bindSvgaCtrl(_svgaController); SCGiftVapSvgaManager().bindSvgaCtrl(_svgaController);
} }
@ -28,31 +33,43 @@ class _VapPlusSvgaPlayerState extends State<VapPlusSvgaPlayer>
@override @override
void dispose() { void dispose() {
_giftFxLog('dispose tag=${widget.tag}');
SCGiftVapSvgaManager().dispose(); SCGiftVapSvgaManager().dispose();
super.dispose(); super.dispose();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Stack( return IgnorePointer(
children: [ child: RepaintBoundary(
Positioned.fill( child: Stack(
child: IgnorePointer( children: [
// VapView可以通过外层包Container(), Positioned.fill(
// VapView can set the width and height through the outer package Container() to limit the width and height of the pop-up video child: RepaintBoundary(
child: VapView( child: VapView(
scaleType: ScaleType.centerCrop, scaleType: ScaleType.fitCenter,
onViewCreated: (controller) { onViewCreated: (controller) {
if (widget.tag == "room_gift") { _giftFxLog('onViewCreated tag=${widget.tag}');
SCGiftVapSvgaManager().bindVapCtrl(controller); if (widget.tag == "room_gift") {
// VapManager().play("https://res.cloudinary.com/dkmchpua1/video/upload/v1737624783/vcg9co6yyfqsadgety1n.mp4"); SCGiftVapSvgaManager().bindVapCtrl(controller);
} // VapManager().play("https://res.cloudinary.com/dkmchpua1/video/upload/v1737624783/vcg9co6yyfqsadgety1n.mp4");
}, }
},
),
),
), ),
), Positioned.fill(
child: RepaintBoundary(
child: SVGAImage(
_svgaController,
fit: BoxFit.contain,
allowDrawingOverflow: false,
),
),
),
],
), ),
Positioned.fill(child: SVGAImage(_svgaController, fit: BoxFit.fill)), ),
],
); );
} }
} }

View File

@ -125,7 +125,7 @@ class _FloatingGiftScreenWidgetState extends State<FloatingGiftScreenWidget>
SCRoomUtils.goRoom( SCRoomUtils.goRoom(
widget.message.roomId!, widget.message.roomId!,
navigatorKey.currentState!.context, navigatorKey.currentState!.context,
fromFloting: true fromFloting: true,
); );
} }
}, },
@ -157,7 +157,7 @@ class _FloatingGiftScreenWidgetState extends State<FloatingGiftScreenWidget>
padding: EdgeInsets.symmetric(horizontal: 10.w), padding: EdgeInsets.symmetric(horizontal: 10.w),
decoration: BoxDecoration( decoration: BoxDecoration(
image: DecorationImage( image: DecorationImage(
image: AssetImage("sc_images/room/sc_icon_gift_flosc_bg.png"), image: AssetImage("sc_images/room/sc_icon_gift_float_bg.png"),
fit: BoxFit.fill, fit: BoxFit.fill,
), ),
), ),
@ -278,18 +278,18 @@ class _FloatingGiftScreenWidgetState extends State<FloatingGiftScreenWidget>
String _getLuckGiftFloatBg() { String _getLuckGiftFloatBg() {
if ((widget.message.coins ?? 0) > 4999 && if ((widget.message.coins ?? 0) > 4999 &&
(widget.message.coins ?? 0) < 10000) { (widget.message.coins ?? 0) < 10000) {
return "sc_images/room/sc_icon_luck_gift_flosc_bg1.png"; return "sc_images/room/sc_icon_luck_gift_float_bg1.png";
} else if ((widget.message.coins ?? 0) > 9999 && } else if ((widget.message.coins ?? 0) > 9999 &&
(widget.message.coins ?? 0) < 75000) { (widget.message.coins ?? 0) < 75000) {
return "sc_images/room/sc_icon_luck_gift_flosc_bg2.png"; return "sc_images/room/sc_icon_luck_gift_float_bg2.png";
} else if ((widget.message.coins ?? 0) > 74999 && } else if ((widget.message.coins ?? 0) > 74999 &&
(widget.message.coins ?? 0) < 150000) { (widget.message.coins ?? 0) < 150000) {
return "sc_images/room/sc_icon_luck_gift_flosc_bg3.png"; return "sc_images/room/sc_icon_luck_gift_float_bg3.png";
} else if ((widget.message.coins ?? 0) > 149999 && } else if ((widget.message.coins ?? 0) > 149999 &&
(widget.message.coins ?? 0) < 1500000) { (widget.message.coins ?? 0) < 1500000) {
return "sc_images/room/sc_icon_luck_gift_flosc_bg4.png"; return "sc_images/room/sc_icon_luck_gift_float_bg4.png";
} else if ((widget.message.coins ?? 0) > 1499999) { } else if ((widget.message.coins ?? 0) > 1499999) {
return "sc_images/room/sc_icon_luck_gift_flosc_bg5.png"; return "sc_images/room/sc_icon_luck_gift_float_bg5.png";
} }
return ""; return "";

View File

@ -127,7 +127,7 @@ class _FloatingLuckGiftScreenWidgetState
SCRoomUtils.goRoom( SCRoomUtils.goRoom(
widget.message.roomId!, widget.message.roomId!,
navigatorKey.currentState!.context, navigatorKey.currentState!.context,
fromFloting: true fromFloting: true,
); );
} }
}, },
@ -161,7 +161,7 @@ class _FloatingLuckGiftScreenWidgetState
flipX: SCGlobalConfig.lang == "ar" ? true : false, // flipX: SCGlobalConfig.lang == "ar" ? true : false, //
flipY: false, // false flipY: false, // false
child: Image.asset( child: Image.asset(
"sc_images/room/sc_icon_luck_gift_flosc_n_bg.png", "sc_images/room/sc_icon_luck_gift_float_n_bg.png",
fit: BoxFit.fill, fit: BoxFit.fill,
), ),
), ),
@ -322,7 +322,7 @@ class _FloatingLuckGiftScreenWidgetState
SCRoomUtils.goRoom( SCRoomUtils.goRoom(
widget.message.roomId!, widget.message.roomId!,
navigatorKey.currentState!.context, navigatorKey.currentState!.context,
fromFloting: true fromFloting: true,
); );
} }
}, },
@ -354,7 +354,7 @@ class _FloatingLuckGiftScreenWidgetState
padding: EdgeInsets.symmetric(horizontal: 15.w), padding: EdgeInsets.symmetric(horizontal: 15.w),
decoration: BoxDecoration( decoration: BoxDecoration(
image: DecorationImage( image: DecorationImage(
image: AssetImage("sc_images/room/sc_icon_gift_flosc_bg.png"), image: AssetImage("sc_images/room/sc_icon_gift_float_bg.png"),
fit: BoxFit.fill, fit: BoxFit.fill,
), ),
), ),

View File

@ -47,13 +47,19 @@ class _RoomHeadWidgetState extends State<RoomHeadWidget> {
alignment: Alignment.center, alignment: Alignment.center,
children: [ children: [
netImage( netImage(
url: url: resolveRoomCoverUrl(
provider provider
.currenRoom .currenRoom
?.roomProfile ?.roomProfile
?.roomProfile ?.roomProfile
?.roomCover ?? ?.id,
"", provider
.currenRoom
?.roomProfile
?.roomProfile
?.roomCover,
),
defaultImg: kRoomCoverDefaultImg,
width: 28.w, width: 28.w,
height: 28.w, height: 28.w,
borderRadius: BorderRadius.all( borderRadius: BorderRadius.all(

View File

@ -1,173 +1,176 @@
import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart';
import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:provider/provider.dart';
import 'package:yumi/app/constants/sc_global_config.dart';
import 'package:provider/provider.dart'; import 'package:yumi/ui_kit/components/sc_compontent.dart';
import 'package:yumi/ui_kit/components/text/sc_text.dart';
import 'package:yumi/ui_kit/components/sc_compontent.dart'; import 'package:yumi/shared/tools/sc_lk_dialog_util.dart';
import 'package:yumi/ui_kit/components/text/sc_text.dart'; import 'package:yumi/services/room/rc_room_manager.dart';
import 'package:yumi/shared/tools/sc_lk_dialog_util.dart'; import 'package:yumi/services/audio/rtc_manager.dart';
import 'package:yumi/services/room/rc_room_manager.dart'; import 'package:yumi/modules/room/online/room_online_page.dart';
import 'package:yumi/services/audio/rtc_manager.dart'; import 'package:yumi/modules/room/rank/room_gift_rank_page.dart';
import 'package:yumi/modules/room/online/room_online_page.dart';
import 'package:yumi/modules/room/rank/room_gift_rank_page.dart'; class RoomOnlineUserWidget extends StatefulWidget {
const RoomOnlineUserWidget({super.key});
class RoomOnlineUserWidget extends StatefulWidget {
@override @override
_RoomOnlineUserWidgetState createState() => _RoomOnlineUserWidgetState(); State<RoomOnlineUserWidget> createState() => _RoomOnlineUserWidgetState();
} }
class _RoomOnlineUserWidgetState extends State<RoomOnlineUserWidget> { class _RoomOnlineUserWidgetState extends State<RoomOnlineUserWidget> {
@override void _openRoomOnlinePage(RtcProvider ref) {
Widget build(BuildContext context) { showBottomInBottomDialog(
return Consumer<RtcProvider>( context,
builder: (context, ref, child) { RoomOnlinePage(roomId: ref.currenRoom?.roomProfile?.roomProfile?.id),
return Row( );
children: [ }
_buildExperience(),
SizedBox(width: 15.w), @override
ref.onlineUsers.isNotEmpty Widget build(BuildContext context) {
? Expanded( return Consumer<RtcProvider>(
child: Row( builder: (context, ref, child) {
mainAxisSize: MainAxisSize.min, return Row(
children: [ children: [
Spacer(), _buildExperience(),
SizedBox( SizedBox(width: 15.w),
height: 25.w, ref.onlineUsers.isNotEmpty
child: Stack( ? Expanded(
clipBehavior: Clip.none, child: Row(
children: [ mainAxisSize: MainAxisSize.min,
SingleChildScrollView( children: [
scrollDirection: Axis.horizontal, Spacer(),
child: Row( GestureDetector(
mainAxisSize: MainAxisSize.min, behavior: HitTestBehavior.opaque,
children: List.generate( onTap: () => _openRoomOnlinePage(ref),
ref.onlineUsers.length, child: Row(
(index) { mainAxisSize: MainAxisSize.min,
return Transform.translate( children: [
offset: Offset(-3.w * index, 0), SizedBox(
child: Padding( height: 25.w,
padding: EdgeInsets.only(right: 0.w), child: Stack(
child: netImage( clipBehavior: Clip.none,
url: children: [
ref SingleChildScrollView(
.onlineUsers[index] scrollDirection: Axis.horizontal,
.userAvatar ?? child: Row(
"", mainAxisSize: MainAxisSize.min,
width: 23.w, children: List.generate(
height: 23.w, ref.onlineUsers.length,
shape: BoxShape.circle, (index) {
border: Border.all( return Transform.translate(
color: Colors.white, offset: Offset(-3.w * index, 0),
width: 1.w, child: Padding(
), padding: EdgeInsets.only(
), right: 0.w,
), ),
); child: netImage(
}, url:
), ref
), .onlineUsers[index]
), .userAvatar ??
], "",
), width: 23.w,
), height: 23.w,
GestureDetector( shape: BoxShape.circle,
child: Container( border: Border.all(
padding: EdgeInsets.symmetric( color: Colors.white,
horizontal: 3.w, width: 1.w,
vertical: 3.w, ),
), ),
child: Column( ),
children: [ );
Image.asset( },
"sc_images/room/sc_icon_online_peple.png", ),
width: 12.w, ),
height: 12.sp, ),
), ],
text( ),
"${ref.onlineUsers.length}", ),
fontSize: 9.sp, Container(
), padding: EdgeInsets.symmetric(
], horizontal: 3.w,
), vertical: 3.w,
), ),
onTap: () { child: Column(
showBottomInBottomDialog( children: [
context, Image.asset(
RoomOnlinePage( "sc_images/room/sc_icon_online_peple.png",
roomId: width: 12.w,
ref height: 12.sp,
.currenRoom ),
?.roomProfile text(
?.roomProfile "${ref.onlineUsers.length}",
?.id, fontSize: 9.sp,
), ),
); ],
}, ),
), ),
SizedBox(width: 5.w,) ],
], ),
), ),
) SizedBox(width: 5.w),
: Container(), ],
], ),
); )
}, : Container(),
); ],
} );
},
/// );
_buildExperience() { }
return Consumer<SocialChatRoomManager>(
builder: (context, ref, child) { ///
return GestureDetector( _buildExperience() {
child: Container( return Consumer<SocialChatRoomManager>(
width: 90.w, builder: (context, ref, child) {
height: 27.w, return GestureDetector(
decoration: BoxDecoration( child: Container(
color: Colors.white10, width: 90.w,
borderRadius: BorderRadiusDirectional.only( height: 27.w,
topEnd: Radius.circular(20.w), decoration: BoxDecoration(
bottomEnd: Radius.circular(20.w), color: Colors.white10,
), borderRadius: BorderRadiusDirectional.only(
), topEnd: Radius.circular(20.w),
alignment: AlignmentDirectional.center, bottomEnd: Radius.circular(20.w),
child: Row( ),
mainAxisAlignment: MainAxisAlignment.center, ),
children: [ alignment: AlignmentDirectional.center,
Image.asset( child: Row(
"sc_images/room/sc_icon_room_contribute.png", mainAxisAlignment: MainAxisAlignment.center,
width: 18.w, children: [
height: 18.w, Image.asset(
), "sc_images/room/sc_icon_room_contribute.png",
SizedBox(width: 5.w), width: 18.w,
text( height: 18.w,
"${ref.roomContributeLevelRes?.thisWeekIntegral ?? 0}", ),
fontSize: 13.sp, SizedBox(width: 5.w),
textColor: Colors.orangeAccent, text(
fontWeight: FontWeight.w600, "${ref.roomContributeLevelRes?.thisWeekIntegral ?? 0}",
), fontSize: 13.sp,
Icon( textColor: Colors.orangeAccent,
Icons.chevron_right, fontWeight: FontWeight.w600,
size: 13.w, ),
color: Colors.orangeAccent, Icon(
), Icons.chevron_right,
], size: 13.w,
), color: Colors.orangeAccent,
), ),
onTap: () { ],
SmartDialog.show( ),
tag: "showRoomGiftRankPage", ),
alignment: Alignment.bottomCenter, onTap: () {
animationType: SmartAnimationType.fade, SmartDialog.show(
builder: (_) { tag: "showRoomGiftRankPage",
return RoomGiftRankPage(); alignment: Alignment.bottomCenter,
}, animationType: SmartAnimationType.fade,
); builder: (_) {
}, return RoomGiftRankPage();
); },
}, );
); },
} );
} },
);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

View File

@ -5,7 +5,20 @@
## 已完成模块 ## 已完成模块
- 创建并持续维护进度跟踪文件。 - 创建并持续维护进度跟踪文件。
- 已继续排查语言房 gift 动画链路:确认送礼后会同时走本地房间消息、滚屏礼物条和大额礼物全局飘屏三条路径,并修复动画管理器在控制器尚未绑定完成时提前消费队列导致后续动画不再播放的问题。
- 已定位并修复语言房礼物飘屏资源引用错误:代码里误写 `sc_icon_gift_flosc_bg` / `sc_icon_luck_gift_flosc_*`,实际资源文件名为 `float`,导致点击送礼后飘屏背景图加载失败,相关动画无法正常显示。
- 已继续定位语言房礼物特效不播放的根因:后端礼物配置返回的特效标记为 `ANIMATION`,而前端历史代码只识别拼写错误的 `ANIMSCION`,导致送礼后直接跳过全屏/半屏特效播放;现已改为同时兼容两种标记。
- 已继续优化语言房礼物特效播放性能:将礼物资源预热改为“后台串行预加载 + 选中礼物高优先级预加载 + 播放阶段复用同一解码/下载任务”,避免送礼时重复下载或重复解码同一个 `.svga/.mp4`;同时为全屏播放器增加 `RepaintBoundary`,并改为按原始比例渲染、关闭越界绘制,减少整页重绘和过度放大带来的卡顿。
- 已修复首页房间封面显示链路:兼容接口 `roomCover/cover` 双字段,并补齐编辑房间成功后的封面内存回写。 - 已修复首页房间封面显示链路:兼容接口 `roomCover/cover` 双字段,并补齐编辑房间成功后的封面内存回写。
- 已继续修复房间设置保存封面后丢失的问题:房间保存接口改为兼容提交 `id/roomId``roomCover/cover`,并在保存成功后优先保留刚提交的封面、名称、公告,避免被空响应覆盖。
- 已新增按房间 ID 的本地房间资料缓存兜底:首页、我的房间、历史/关注、搜索和再次进入房间都会优先回填最近一次本地保存的封面;同时已将房间封面空态图替换为新的 `sc_no_data.png`
- 已确认房间封面上传接口与 `PUT /room/profile` 保存接口都返回了正确图片地址,并修正首页/重进房间的取值优先级,避免被列表接口短时间返回的旧封面或无效封面重新覆盖。
- 已继续完善个人主页头像与滚动表现:顶部小头像保持纯头像,主头像恢复头像框叠加;同时将个人主页背景并入可滚动 header修复滑动时内容与背景不同步的问题。
- 已进一步统一头像框方案:共享头像组件改为“头像居中 + 头像框包边”结构,并对这类静态头像框资源增加白底去除处理,不再简单把头像框整张盖在头像上;个人主页主头像与房间麦位头像现已同步采用这一展示方式。
- 已修复静态头像框透明区域误变黑的问题:调整头像框白底透明化公式后,进入房间时不再出现只有头像附近有颜色、其余房间背景被黑块覆盖的现象。
- 已修复个人主页首屏黑屏与内容区多余圆角问题:为个人页补充稳定的底图/底色占位,避免进入页面时先出现黑屏;同时移除资料内容区顶部多余圆角。
- 已将个人主页首屏占位升级为骨架屏进入个人页时会先按真实版式展示头像、昵称、计数区、Tab 和资料内容的骨架块,不再先露默认背景等待约 1 秒。
- 已优化语言房顶部成员入口的点击范围:右上角在线成员区域现已将左侧头像堆叠区与右侧成员图标统一为同一点击热区,用户点击头像区也可直接打开成员列表页。
- 完成仓库结构、依赖引用、资源体积和 APK 组成的第一轮排查。 - 完成仓库结构、依赖引用、资源体积和 APK 组成的第一轮排查。
- 移除 4 个当前未在业务层直接使用的插件依赖:`loading_indicator_view_plus``social_sharing_plus``flutter_foreground_task``on_audio_query`,并清理相关平台声明。 - 移除 4 个当前未在业务层直接使用的插件依赖:`loading_indicator_view_plus``social_sharing_plus``flutter_foreground_task``on_audio_query`,并清理相关平台声明。
- 修补 `image_cropper 5.0.1` Android 兼容问题,切换到本地 path 依赖以恢复构建。 - 修补 `image_cropper 5.0.1` Android 兼容问题,切换到本地 path 依赖以恢复构建。
@ -50,12 +63,32 @@
## 已改动文件 ## 已改动文件
- `需求进度.md` - `需求进度.md`
- `lib/shared/data_sources/models/enum/sc_gift_type.dart`
- `lib/shared/tools/sc_gift_vap_svga_manager.dart`
- `lib/services/general/sc_app_general_manager.dart`
- `lib/ui_kit/widgets/room/floating/floating_gift_screen_widget.dart`
- `lib/ui_kit/widgets/room/floating/floating_luck_gift_screen_widget.dart`
- `lib/services/room/rc_room_manager.dart` - `lib/services/room/rc_room_manager.dart`
- `lib/services/audio/rtc_manager.dart`
- `lib/modules/room/edit/room_edit_page.dart`
- `lib/shared/data_sources/sources/repositories/sc_user_repository_impl.dart`
- `lib/shared/data_sources/sources/repositories/sc_room_repository_imp.dart`
- `lib/shared/tools/sc_room_profile_cache.dart`
- `lib/shared/business_logic/models/res/room_res.dart` - `lib/shared/business_logic/models/res/room_res.dart`
- `lib/shared/business_logic/models/res/follow_room_res.dart` - `lib/shared/business_logic/models/res/follow_room_res.dart`
- `lib/shared/business_logic/models/res/my_room_res.dart` - `lib/shared/business_logic/models/res/my_room_res.dart`
- `lib/shared/business_logic/models/res/sc_edit_room_info_res.dart` - `lib/shared/business_logic/models/res/sc_edit_room_info_res.dart`
- `lib/shared/business_logic/models/res/join_room_res.dart` - `lib/shared/business_logic/models/res/join_room_res.dart`
- `lib/ui_kit/components/sc_compontent.dart`
- `lib/ui_kit/components/sc_float_ichart.dart`
- `lib/ui_kit/widgets/room/room_head_widget.dart`
- `lib/modules/room/detail/room_detail_page.dart`
- `lib/modules/home/popular/party/sc_home_party_page.dart`
- `lib/modules/home/popular/follow/sc_room_follow_page.dart`
- `lib/modules/home/popular/history/sc_room_history_page.dart`
- `lib/modules/home/popular/mine/sc_home_mine_page.dart`
- `lib/modules/search/sc_search_page.dart`
- `sc_images/general/sc_no_data.png`
- `.gitignore` - `.gitignore`
- `android/key.properties` - `android/key.properties`
- `pubspec.yaml` - `pubspec.yaml`
@ -81,8 +114,21 @@
- `assets/` - `assets/`
## 已验证结果 ## 已验证结果
- 已通过代码与资源目录交叉核对确认:房内礼物飘屏当前报错 `Unable to load asset: "sc_images/room/sc_icon_gift_flosc_bg.png"` 的根因是资源名拼写错误,而不是消息未下发;工程内实际存在的资源文件为 `sc_icon_gift_float_bg.png``sc_icon_luck_gift_float_bg1~5.png``sc_icon_luck_gift_float_n_bg.png`
- 已确认当前“点击赠送没有动画”至少包含一类前端资源问题:大额礼物/幸运礼物飘屏 widget 已进入渲染,但因为背景图 asset 路径写错而无法显示;全屏 `SVGA/VAP` 特效是否播放仍取决于具体礼物是否带有 `giftSourceUrl`,且 `special` 包含 `ANIMSCION/ANIMATION/GLOBAL_GIFT` 之一。
- 已完成一轮针对卡顿的代码级优化:礼物列表现在只会对真正需要全屏特效的礼物做受控预热;用户选中礼物时会立即高优先级预热该礼物;播放器播放时优先复用内存里的 `MovieEntity` 或同一路径的在途任务,不再重复发起网络下载/重复解码;渲染层也已改为独立重绘边界以减少房间整页跟随重绘。
- 已通过 GiftFX 调试日志确认:示例礼物 `阿拉伯舞蹈` 实际返回 `giftSourceUrl=.svga``special=ANIMATION`,此前前端因为不识别该标记而输出 `skip local play because special does not include ANIMSCION/GLOBAL_GIFT`;本轮已补齐 `ANIMATION` 兼容判断。
- 当前工作区存在用户已有未提交改动,后续处理均避开覆盖。 - 当前工作区存在用户已有未提交改动,后续处理均避开覆盖。
- 首页 `party/follow/history/mine` 以及房间详情使用的房间封面模型已兼容 `roomCover``cover` 两种返回字段,上传封面后不会再因为字段名不一致掉回空占位。 - 首页 `party/follow/history/mine` 以及房间详情使用的房间封面模型已兼容 `roomCover``cover` 两种返回字段,上传封面后不会再因为字段名不一致掉回空占位。
- 房间设置保存封面时,前端现在会同时兼容提交和读取两套字段,并在 `PUT /room/profile` 响应或紧接着的房间详情刷新返回空封面时保留刚上传的封面,不再被空值冲掉。
- 已新增本地房间封面兜底缓存:即使服务端短时间仍返回空封面,首页列表、我的房间、搜索结果、房间头部、房间详情和再次进入房间也会继续显示最近一次本地成功保存的封面。
- 已通过实际请求日志确认:`POST /external/oss/upload``PUT /room/profile` 和随后 `GET /room/profile/specific` 都已返回正确封面 URL本轮问题的根因已收敛为首页/列表链路仍可能使用接口短时间回传的旧值,因此本地成功保存的封面现已提升为更高优先级。
- 房间封面空态图已切换为新的 `sc_images/general/sc_no_data.png`,其资源尺寸为 `288x288`
- 房间资料卡与个人主页使用的用户资料链路已重新对齐:个人页现兼容 `avatar` 别名;顶部小头像不叠加头像框,但主头像会继续叠加头像框资源,且个人页背景已随 header 一起滚动,不再出现内容先滑动、背景停留不动的现象。
- 头像框显示逻辑已改为“框在外、头像在内”的常见样式;针对当前这类带白底的静态头像框资源,已增加白底透明化处理,因此个人主页主头像与房间麦位头像都不会再留下明显白边。
- 静态头像框的透明区域现已保持透明,不会再被错误算成黑色;房间麦位头像、个人主页主头像和其它复用 `head()` 的头像组件进入房间后不会再出现大块黑底遮罩。
- 个人主页现在在数据加载前也会先展示默认背景和底色不会再闪黑“About me / Giftwall” 内容区顶部边缘已改为直角,不再额外出现一个圆角缺口。
- 个人主页当前已接入贴合页面结构的骨架屏,占位期间不会先看到背景图单独显示;资料接口返回后再无缝切到真实内容,首屏观感更平滑。
- 目录体积排查显示:`build/``4.7G``.dart_tool/``347M``ios/``250M``sc_images/``48M``local_packages/``6.1M` - 目录体积排查显示:`build/``4.7G``.dart_tool/``347M``ios/``250M``sc_images/``48M``local_packages/``6.1M`
- 当前包体过大的主要原因已经确认: - 当前包体过大的主要原因已经确认:
- universal APK 带入了 `arm64-v8a``armeabi-v7a``x86_64` 三套 ABI。 - universal APK 带入了 `arm64-v8a``armeabi-v7a``x86_64` 三套 ABI。