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

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:ui' as ui;
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_debouncer/flutter_debouncer.dart';
import 'package:flutter_screenutil/flutter_screenutil.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/mic_res.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/rtm_manager.dart';
import 'package:yumi/services/auth/user_profile_manager.dart';
@ -81,6 +78,34 @@ class _GiftPageState extends State<GiftPage>
int giftType = 0;
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
void initState() {
super.initState();
@ -91,20 +116,15 @@ class _GiftPageState extends State<GiftPage>
Provider.of<SocialChatUserProfileManager>(context, listen: false).balance();
_pages.add(
GiftTabPage("ALL", (int checkedI) {
checkedGift = null;
var all =
Provider.of<SCAppGeneralManager>(
context,
listen: false,
).giftByTab["ALL"];
if (all != null) {
checkedGift = all[checkedI];
}
number = 1;
noShowNumber = false;
setState(() {
giftType = 0;
});
_handleGiftSelected(
all != null ? all[checkedI] : null,
nextGiftType: 0,
);
}),
);
@ -426,7 +446,8 @@ class _GiftPageState extends State<GiftPage>
horizontal: 20.w,
),
decoration: BoxDecoration(
color: SocialChatTheme.primaryLight,
color:
SocialChatTheme.primaryLight,
borderRadius:
BorderRadius.circular(5),
),
@ -743,6 +764,16 @@ class _GiftPageState extends State<GiftPage>
roomId: rtcProvider?.currenRoom?.roomProfile?.roomProfile?.id ?? "",
)
.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);
sendGiftMsg(acceptUsers);
Provider.of<SocialChatUserProfileManager>(
@ -750,12 +781,38 @@ class _GiftPageState extends State<GiftPage>
listen: false,
).updateBalance(result);
})
.catchError((e) {});
.catchError((e) {
_giftFxLog(
'giveGift failed giftId=${checkedGift?.id} '
'giftName=${checkedGift?.giftName} '
'error=$e',
);
});
}
void sendGiftMsg(List<MicRes> acceptUsers) {
///IM消息
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>(
navigatorKey.currentState!.context,
listen: false,
@ -808,12 +865,35 @@ class _GiftPageState extends State<GiftPage>
OverlayManager().addMessage(fMsg);
}
if (checkedGift!.giftSourceUrl != null && checkedGift!.special != null) {
if (checkedGift!.special!.contains(SCGiftType.ANIMSCION.name) ||
checkedGift!.special!.contains(SCGiftType.GLOBAL_GIFT.name)) {
if (scGiftHasFullScreenEffect(checkedGift!.special)) {
if (SCGlobalConfig.isGiftSpecialEffects) {
_giftFxLog(
'local trigger player play '
'path=${checkedGift!.giftSourceUrl} '
'giftId=${checkedGift?.id} '
'giftName=${checkedGift?.giftName}',
);
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(
children: [
SizedBox(height: 23.w,),
SizedBox(height: 23.w),
Expanded(
child: PageView.builder(
controller: _giftPageController,
@ -250,7 +250,7 @@ class _GiftTabPageState extends State<GiftTabPage> {
lineHeight: 1,
textAlign: TextAlign.center,
fontWeight: FontWeight.w600,
textColor: widget.isDark ? Colors.white : Colors.black,
textColor: widget.isDark ? Colors.white : Colors.black,
),
),
Row(
@ -280,23 +280,29 @@ class _GiftTabPageState extends State<GiftTabPage> {
children: [
SizedBox(height: 3.w),
// widget
if (gift.special!.contains(SCGiftType.ANIMSCION.name))
if (scGiftHasFullScreenEffect(gift.special))
Image.asset(
_strategy.getGiftPageGiftEffectIcon(SCGiftType.ANIMSCION.name),
_strategy.getGiftPageGiftEffectIcon(
SCGiftType.ANIMSCION.name,
),
width: 16.w,
height: 16.w,
fit: BoxFit.fill,
),
if (gift.special!.contains(SCGiftType.MUSIC.name))
Image.asset(
_strategy.getGiftPageGiftMusicIcon(SCGiftType.MUSIC.name),
_strategy.getGiftPageGiftMusicIcon(
SCGiftType.MUSIC.name,
),
width: 16.w,
height: 16.w,
fit: BoxFit.fill,
),
if (gift.giftTab == (SCGiftType.LUCKY_GIFT.name))
Image.asset(
_strategy.getGiftPageGiftLuckIcon(SCGiftType.LUCKY_GIFT.name),
_strategy.getGiftPageGiftLuckIcon(
SCGiftType.LUCKY_GIFT.name,
),
width: 16.w,
height: 16.w,
fit: BoxFit.fill,
@ -333,7 +339,8 @@ class _GiftTabPageState extends State<GiftTabPage> {
margin: EdgeInsets.symmetric(horizontal: 4.w),
decoration: BoxDecoration(
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(
url: roomRes.roomProfile?.roomCover ?? "",
url: resolveRoomCoverUrl(
roomRes.roomProfile?.id,
roomRes.roomProfile?.roomCover,
),
defaultImg: kRoomCoverDefaultImg,
borderRadius: BorderRadius.circular(12.w),
width: 200.w,
height: 200.w,

View File

@ -63,7 +63,11 @@ class _SCRoomHistoryPageState
),
),
child: netImage(
url: roomRes.roomProfile?.roomCover ?? "",
url: resolveRoomCoverUrl(
roomRes.roomProfile?.id,
roomRes.roomProfile?.roomCover,
),
defaultImg: kRoomCoverDefaultImg,
borderRadius: BorderRadius.circular(12.w),
width: 200.w,
height: 200.w,
@ -74,7 +78,9 @@ class _SCRoomHistoryPageState
margin: EdgeInsets.symmetric(horizontal: 1.w),
decoration: BoxDecoration(
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,
),
),

View File

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

View File

@ -427,7 +427,8 @@ class _HomePartyPageState extends State<SCHomePartyPage>
),
),
child: netImage(
url: res.roomCover ?? "",
url: resolveRoomCoverUrl(res.id, res.roomCover),
defaultImg: kRoomCoverDefaultImg,
borderRadius: BorderRadius.circular(12.w),
width: 200.w,
height: 200.w,
@ -451,7 +452,7 @@ class _HomePartyPageState extends State<SCHomePartyPage>
builder: (_, provider, __) {
return netImage(
url:
"${provider.findCountryByName(res.countryName ?? "")?.nationalFlag}",
"${provider.findCountryByName(res.countryName ?? "")?.nationalFlag}",
width: 20.w,
height: 13.w,
borderRadius: BorderRadius.circular(2.w),
@ -503,37 +504,25 @@ class _HomePartyPageState extends State<SCHomePartyPage>
),
),
SizedBox(width: 5.w),
(res.extValues
?.roomSetting
?.password
?.isEmpty ??
false)
(res.extValues?.roomSetting?.password?.isEmpty ?? false)
? Image.asset(
"sc_images/general/sc_icon_online_user.png",
width: 14.w,
height: 14.w,
)
"sc_images/general/sc_icon_online_user.png",
width: 14.w,
height: 14.w,
)
: Image.asset(
"sc_images/index/sc_icon_room_suo.png",
width: 20.w,
height: 20.w,
),
(res.extValues
?.roomSetting
?.password
?.isEmpty ??
false)
"sc_images/index/sc_icon_room_suo.png",
width: 20.w,
height: 20.w,
),
(res.extValues?.roomSetting?.password?.isEmpty ?? false)
? SizedBox(width: 3.w)
: Container(height: 10.w),
(res.extValues
?.roomSetting
?.password
?.isEmpty ??
false)
(res.extValues?.roomSetting?.password?.isEmpty ?? false)
? text(
res.extValues?.memberQuantity ?? "0",
fontSize: 12.sp,
)
res.extValues?.memberQuantity ?? "0",
fontSize: 12.sp,
)
: Container(height: 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;
_tabController.addListener(() {}); //
_subscription = eventBus.on<SCGiveRoomLuckPageDisposeEvent>().listen((event) {
_subscription = eventBus.on<SCGiveRoomLuckPageDisposeEvent>().listen((
event,
) {
if (mounted) {
Provider.of<GiftProvider>(context, listen: false).clearAllGiftData();
Provider.of<GiftProvider>(
@ -64,6 +66,10 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
@override
void dispose() {
final rtmProvider = Provider.of<RtmProvider>(context, listen: false);
if (rtmProvider.msgFloatingGiftListener == _floatingGiftListener) {
rtmProvider.msgFloatingGiftListener = null;
}
_tabController.dispose(); //
_subscription.cancel();
super.dispose();
@ -132,7 +138,7 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
SizedBox(height: ScreenUtil().setWidth(5)),
RoomOnlineUserWidget(),
RoomSeatWidget(),
SizedBox(height: 2.w,),
SizedBox(height: 2.w),
Expanded(
child: Stack(
children: [

View File

@ -452,7 +452,8 @@ class _SearchRoomListState extends State<SearchRoomList> {
),
),
child: netImage(
url: e.roomCover ?? "",
url: resolveRoomCoverUrl(e.id, e.roomCover),
defaultImg: kRoomCoverDefaultImg,
borderRadius: BorderRadius.circular(12.w),
width: 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_rocket_status_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';
typedef OnSoundVoiceChange = Function(num index, int volum);
@ -49,7 +50,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
JoinRoomRes? currenRoom;
///
List<OnSoundVoiceChange> _onSoundVoiceChangeList = [];
final List<OnSoundVoiceChange> _onSoundVoiceChangeList = [];
///
Map<num, MicRes> roomWheatMap = {};
@ -240,7 +241,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
if (num.parse(v.user!.account!) == uid) {
roomWheatMap[k]!.setVolume = voloum;
for (var element in _onSoundVoiceChangeList) {
element?.call(k, voloum);
element(k, voloum);
}
}
}
@ -267,6 +268,43 @@ class RealTimeCommunicationManager extends ChangeNotifier {
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(
BuildContext context,
String roomId, {
@ -897,14 +935,24 @@ class RealTimeCommunicationManager extends ChangeNotifier {
Future<bool> loadRoomInfo(String roomId) async {
try {
final value = await SCChatRoomRepository().specific(roomId);
final currentRoomProfile = currenRoom?.roomProfile?.roomProfile;
currenRoom = currenRoom?.copyWith(
roomProfile: currenRoom?.roomProfile?.copyWith(
roomCounter: value.counter,
roomSetting: value.setting,
roomProfile: currenRoom?.roomProfile?.roomProfile?.copyWith(
roomCover: value.roomCover,
roomName: value.roomName,
roomDesc: value.roomDesc,
roomCounter: value.counter ?? currenRoom?.roomProfile?.roomCounter,
roomSetting: value.setting ?? currenRoom?.roomProfile?.roomSetting,
roomProfile: currentRoomProfile?.copyWith(
roomCover: _preferNonEmpty(
value.roomCover,
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) {
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/services/audio/rtc_manager.dart';
import 'package:provider/provider.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/V2TimGroupListener.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/group_type.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/V2TimGroupListener.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/group_type.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/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/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_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/room_msg_item.dart';
import 'package:yumi/shared/business_logic/models/res/login_res.dart';
@ -71,6 +71,10 @@ typedef RtmProvider = RealTimeMessagingManager;
class RealTimeMessagingManager extends ChangeNotifier {
BuildContext? context;
void _giftFxLog(String message) {
debugPrint('[GiftFX][RTM] $message');
}
///
List<Msg> roomAllMsgList = [];
List<Msg> roomChatMsgList = [];
@ -113,48 +117,48 @@ class RealTimeMessagingManager extends ChangeNotifier {
Debouncer debouncer = Debouncer();
List<V2TimConversation> conversationList = [];
///
SocialChatUserProfile? customerInfo;
int _systemUnreadCount() {
int count = 0;
for (final conversationId in SCGlobalConfig.systemConversationIds) {
count += conversationMap[conversationId]?.unreadCount ?? 0;
}
return count;
}
V2TimConversation getPreferredSystemConversation() {
final systemConversations =
conversationMap.values
.where(
(element) =>
SCGlobalConfig.isSystemConversationId(element.conversationID),
)
.toList()
..sort((e1, e2) {
final time1 = e1.lastMessage?.timestamp ?? 0;
final time2 = e2.lastMessage?.timestamp ?? 0;
return time2.compareTo(time1);
});
if (systemConversations.isNotEmpty) {
return systemConversations.first;
}
return V2TimConversation(
type: ConversationType.V2TIM_C2C,
userID: SCGlobalConfig.primarySystemUserId,
conversationID: SCGlobalConfig.primarySystemConversationId,
);
}
void getConversationList() {
List<V2TimConversation> list = conversationMap.values.toList();
list.removeWhere((element) {
if (element.conversationID == "c2c_${customerInfo?.id}") {
return true;
}
///
SocialChatUserProfile? customerInfo;
int _systemUnreadCount() {
int count = 0;
for (final conversationId in SCGlobalConfig.systemConversationIds) {
count += conversationMap[conversationId]?.unreadCount ?? 0;
}
return count;
}
V2TimConversation getPreferredSystemConversation() {
final systemConversations =
conversationMap.values
.where(
(element) =>
SCGlobalConfig.isSystemConversationId(element.conversationID),
)
.toList()
..sort((e1, e2) {
final time1 = e1.lastMessage?.timestamp ?? 0;
final time2 = e2.lastMessage?.timestamp ?? 0;
return time2.compareTo(time1);
});
if (systemConversations.isNotEmpty) {
return systemConversations.first;
}
return V2TimConversation(
type: ConversationType.V2TIM_C2C,
userID: SCGlobalConfig.primarySystemUserId,
conversationID: SCGlobalConfig.primarySystemConversationId,
);
}
void getConversationList() {
List<V2TimConversation> list = conversationMap.values.toList();
list.removeWhere((element) {
if (element.conversationID == "c2c_${customerInfo?.id}") {
return true;
}
if (element.conversationID == "c2c_atyou-newsletter") {
///
clearC2CHistoryMessage(element.conversationID, false);
@ -165,15 +169,15 @@ class RealTimeMessagingManager extends ChangeNotifier {
}
return false;
});
list.sort((e1, e2) {
int time1 = e1.lastMessage?.timestamp ?? 0;
int time2 = e2.lastMessage?.timestamp ?? 0;
return time2.compareTo(time1);
});
conversationList = list;
systemUnReadCount = _systemUnreadCount();
customerUnReadCount =
conversationMap["c2c_${customerInfo?.id}"]?.unreadCount ?? 0;
list.sort((e1, e2) {
int time1 = e1.lastMessage?.timestamp ?? 0;
int time2 = e2.lastMessage?.timestamp ?? 0;
return time2.compareTo(time1);
});
conversationList = list;
systemUnReadCount = _systemUnreadCount();
customerUnReadCount =
conversationMap["c2c_${customerInfo?.id}"]?.unreadCount ?? 0;
notifyListeners();
}
@ -234,11 +238,11 @@ class RealTimeMessagingManager extends ChangeNotifier {
// _onRefreshConversation(conversationList);
initConversation();
},
onTotalUnreadMessageCountChanged: (int totalUnreadCount) {
messageUnReadCount = totalUnreadCount;
systemUnReadCount = _systemUnreadCount();
customerUnReadCount =
conversationMap["c2c_${customerInfo?.id}"]?.unreadCount ?? 0;
onTotalUnreadMessageCountChanged: (int totalUnreadCount) {
messageUnReadCount = totalUnreadCount;
systemUnReadCount = _systemUnreadCount();
customerUnReadCount =
conversationMap["c2c_${customerInfo?.id}"]?.unreadCount ?? 0;
allUnReadCount =
messageUnReadCount +
notifcationUnReadCount +
@ -478,16 +482,16 @@ class RealTimeMessagingManager extends ChangeNotifier {
}
}
}
if (message?.userID == customerInfo?.id) {
if (onNewMessageCurrentConversationListener == null) {
conversationMap["c2c_${customerInfo?.id}"]?.unreadCount =
(conversationMap["c2c_${customerInfo?.id}"]?.unreadCount ?? 0) + 1;
}
}
systemUnReadCount = _systemUnreadCount();
notifyListeners();
onNewMessageCurrentConversationListener?.call(message, msgId: msgId);
}
if (message?.userID == customerInfo?.id) {
if (onNewMessageCurrentConversationListener == null) {
conversationMap["c2c_${customerInfo?.id}"]?.unreadCount =
(conversationMap["c2c_${customerInfo?.id}"]?.unreadCount ?? 0) + 1;
}
}
systemUnReadCount = _systemUnreadCount();
notifyListeners();
onNewMessageCurrentConversationListener?.call(message, msgId: msgId);
}
void _onRefreshConversation(List<V2TimConversation> conversations) {
for (V2TimConversation conversation in conversations) {
@ -714,7 +718,9 @@ class RealTimeMessagingManager extends ChangeNotifier {
}
} else if (SCPathUtils.getFileType(entity.path) == "video_pic") {
if (entity.lengthSync() > 50000000) {
SCTts.show(SCAppLocalizations.of(context!)!.theVideoSizeCannotExceed);
SCTts.show(
SCAppLocalizations.of(context!)!.theVideoSizeCannotExceed,
);
return;
}
//
@ -788,7 +794,10 @@ class RealTimeMessagingManager extends ChangeNotifier {
toUser?.cleanUseProps();
toUser?.cleanPhotos();
msg.needUpDataUserInfo =
Provider.of<RealTimeCommunicationManager>(context!, listen: false).needUpDataUserInfo;
Provider.of<RealTimeCommunicationManager>(
context!,
listen: false,
).needUpDataUserInfo;
msg.user = user;
msg.toUser = toUser;
final textMsg = await TencentImSDKPlugin.v2TIMManager
@ -948,7 +957,8 @@ class RealTimeMessagingManager extends ChangeNotifier {
///
var fdata = data["data"];
SCFloatingMessage msg = SCFloatingMessage.fromJson(fdata);
if (msg.toUserId == AccountStorage().getCurrentUser()?.userProfile?.id &&
if (msg.toUserId ==
AccountStorage().getCurrentUser()?.userProfile?.id &&
msg.roomId !=
Provider.of<RealTimeCommunicationManager>(
context!,
@ -1076,11 +1086,17 @@ class RealTimeMessagingManager extends ChangeNotifier {
} else {
res = SCRoomThemeListRes();
}
Provider.of<RealTimeCommunicationManager>(context!, listen: false).updateRoomBG(res);
Provider.of<RealTimeCommunicationManager>(
context!,
listen: false,
).updateRoomBG(res);
return;
}
if (msg.type == SCRoomMsgType.emoticons) {
Provider.of<RealTimeCommunicationManager>(context!, listen: false).starPlayEmoji(msg);
Provider.of<RealTimeCommunicationManager>(
context!,
listen: false,
).starPlayEmoji(msg);
return;
}
if (msg.type == SCRoomMsgType.micChange) {
@ -1089,7 +1105,10 @@ class RealTimeMessagingManager extends ChangeNotifier {
listen: false,
).micChange(BroadCastMicChangePush.fromJson(data).data?.mics);
} 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) {
var broadCastRes = SCBroadCastLuckGiftPush.fromJson(data);
msg.gift = SocialChatGiftRes(giftPhoto: broadCastRes.data?.giftCover);
@ -1168,13 +1187,57 @@ class RealTimeMessagingManager extends ChangeNotifier {
}
}
} 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!.special!.contains(SCGiftType.ANIMSCION.name) ||
msg.gift!.special!.contains(SCGiftType.GLOBAL_GIFT.name)) {
if (scGiftHasFullScreenEffect(msg.gift!.special)) {
if (SCGlobalConfig.isGiftSpecialEffects) {
_giftFxLog(
'trigger player play path=${msg.gift!.giftSourceUrl} '
'giftId=${msg.gift?.id} giftName=${msg.gift?.giftName}',
);
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>(
context!,
@ -1225,7 +1288,10 @@ class RealTimeMessagingManager extends ChangeNotifier {
return;
} else if (msg.type == SCRoomMsgType.roomRoleChange) {
///
Provider.of<RealTimeCommunicationManager>(context!, listen: false).retrieveMicrophoneList();
Provider.of<RealTimeCommunicationManager>(
context!,
listen: false,
).retrieveMicrophoneList();
if (msg.toUser?.id ==
AccountStorage().getCurrentUser()?.userProfile?.id) {
Provider.of<RealTimeCommunicationManager>(
@ -1389,13 +1455,13 @@ class RealTimeMessagingManager extends ChangeNotifier {
notifyListeners();
}
void updateSystemCount(int count) {
for (final conversationId in SCGlobalConfig.systemConversationIds) {
conversationMap[conversationId]?.unreadCount = 0;
}
systemUnReadCount = 0;
notifyListeners();
}
void updateSystemCount(int count) {
for (final conversationId in SCGlobalConfig.systemConversationIds) {
conversationMap[conversationId]?.unreadCount = 0;
}
systemUnReadCount = 0;
notifyListeners();
}
void updateCustomerCount(int count) {
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_svga/flutter_svga.dart';
import 'package:yumi/shared/data_sources/sources/local/file_cache_manager.dart';
import 'package:yumi/shared/data_sources/sources/repositories/sc_config_repository_imp.dart';
import 'package:yumi/shared/data_sources/sources/repositories/sc_room_repository_imp.dart';
import 'package:yumi/shared/tools/sc_path_utils.dart';
import 'package:yumi/shared/tools/sc_gift_vap_svga_manager.dart';
import 'package:yumi/shared/data_sources/models/country_mode.dart';
import 'package:yumi/shared/business_logic/models/res/sc_banner_leaderboard_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 'package:yumi/shared/business_logic/models/res/sc_index_banner_res.dart';
import 'package:yumi/shared/business_logic/models/res/sc_room_emoji_res.dart';
import '../../shared/data_sources/models/enum/sc_banner_type.dart';
class SCAppGeneralManager extends ChangeNotifier {
List<CountryMode> countryModeList = [];
Map<String, List<Country>> _countryMap = {};
Map<String, Country> _countryByNameMap = {};
bool _isFetchingCountryList = false;
///
List<SocialChatGiftRes> giftResList = [];
Map<String, SocialChatGiftRes> _giftByIdMap = {};
Map<String, List<SocialChatGiftRes>> giftByTab = {};
Map<String, List<SocialChatGiftRes>> activityGiftByTab = {};
///
Country? selectCountryInfo;
List<SocialChatGiftRes> activityList = [];
SCAppGeneralManager() {
fetchCountryList();
giftList(); // 使
giftActivityList();
// giftBackpack();
// configLevel();
}
///
Future<void> fetchCountryList() async {
if (_isFetchingCountryList) {
return;
}
if (countryModeList.isNotEmpty) {
clearCountrySelection();
notifyListeners();
return;
}
_isFetchingCountryList = true;
try {
await Future.delayed(Duration(milliseconds: 550));
final result = await SCConfigRepositoryImp().loadCountry();
_countryMap.clear();
_countryByNameMap.clear();
countryModeList.clear();
for (final c in result.openCountry ?? const <Country>[]) {
final prefix = c.aliasName?.substring(0, 1) ?? "";
if (prefix.isNotEmpty) {
final countryList = _countryMap[prefix] ?? <Country>[];
countryList.add(c.copyWith());
_countryMap[prefix] = countryList;
}
if ((c.countryName ?? "").isNotEmpty) {
_countryByNameMap[c.countryName!] = c;
}
}
_countryMap.forEach((k, v) {
countryModeList.add(CountryMode(k, v));
});
///
countryModeList.sort((a, b) => a.prefix.compareTo(b.prefix));
notifyListeners();
} catch (_) {
notifyListeners();
} finally {
_isFetchingCountryList = false;
}
}
///
Country? findCountryByName(String countryName) {
return _countryByNameMap[countryName];
}
///
Country? chooseCountry(int subIndex, int cIndex) {
var selectCm = countryModeList[subIndex].prefixCountrys[cIndex];
if (selectCountryInfo != null) {
selectCountryInfo = null;
} else {
selectCountryInfo = selectCm;
}
return selectCountryInfo;
}
void updateCurrentCountry(Country? countryInfo) {
selectCountryInfo = countryInfo;
notifyListeners();
}
void clearCountrySelection() {
selectCountryInfo = null;
}
void openDrawer(BuildContext context) {
Scaffold.of(context).openDrawer();
}
void closeDrawer(BuildContext context) {
Scaffold.of(context).closeDrawer();
}
void giftList({bool includeCustomized = true}) async {
try {
giftResList = await SCChatRoomRepository().giftList();
giftByTab.clear();
for (var gift in giftResList) {
giftByTab[gift.giftTab];
var gmap = giftByTab[gift.giftTab];
gmap ??= [];
gmap.add(gift);
giftByTab[gift.giftTab!] = gmap;
var gAllMap = giftByTab["ALL"];
gAllMap ??= [];
if (gift.giftTab != "NSCIONAL_FLAG" &&
gift.giftTab != "ACTIVITY" &&
gift.giftTab != "LUCKY_GIFT" &&
gift.giftTab != "CP" &&
gift.giftTab != "CUSTOMIZED" &&
gift.giftTab != "MAGIC") {
gAllMap.add(gift);
}
giftByTab["ALL"] = gAllMap;
_giftByIdMap[gift.id!] = gift;
}
if (includeCustomized) {
giftByTab["CUSTOMIZED"] ??= [];
///
giftByTab["CUSTOMIZED"]?.insert(0, SocialChatGiftRes(id: "-1000"));
} else {
giftByTab.remove("CUSTOMIZED");
}
notifyListeners();
///
downLoad(giftResList);
} catch (e) {}
}
void giftActivityList() async {
try {
activityList.clear();
activityList = await SCChatRoomRepository().giftActivityList();
activityGiftByTab.clear();
for (var gift in activityList) {
var gmap = activityGiftByTab["${gift.activityId}"];
gmap ??= [];
gmap.add(gift);
activityGiftByTab["${gift.activityId}"] = gmap;
}
notifyListeners();
} catch (e) {}
}
///
void giftBackpack() async {
try {
var result = await SCChatRoomRepository().giftBackpack();
} catch (e) {}
}
///id获取礼物
SocialChatGiftRes? getGiftById(String id) {
return _giftByIdMap[id];
}
void downLoad(List<SocialChatGiftRes> giftResList) {
for (var gift in giftResList) {
if (gift.giftSourceUrl != null) {
if (SCPathUtils.getFileExtension(gift.giftSourceUrl!).toLowerCase() ==
".svga") {
///
if (!SCGiftVapSvgaManager().videoItemCache.containsKey(
gift.giftSourceUrl,
)) {
SVGAParser.shared.decodeFromURL(gift.giftSourceUrl!).then((entity) {
entity.autorelease = false;
SCGiftVapSvgaManager().videoItemCache[gift.giftSourceUrl!] =
entity;
});
}
} else {
if ((gift.giftSourceUrl ?? "").isNotEmpty) {
FileCacheManager.getInstance().getFile(url: gift.giftSourceUrl!);
}
}
}
}
}
List<SCIndexBannerRes> homeBanners = [];
List<SCIndexBannerRes> exploreBanners = [];
List<SCIndexBannerRes> roomBanners = [];
List<SCIndexBannerRes> gameBanners = [];
///banner
void loadMainBanner() async {
try {
var banners = await SCConfigRepositoryImp().getBanner();
homeBanners.clear();
roomBanners.clear();
gameBanners.clear();
exploreBanners.clear();
for (var v in banners) {
if ((v.displayPosition ?? "").contains(
SCBannerType.EXPLORE_PAGE.name,
)) {
exploreBanners.add(v);
}
if ((v.displayPosition ?? "").contains(SCBannerType.ROOM.name)) {
roomBanners.add(v);
}
if ((v.displayPosition ?? "").contains(SCBannerType.HOME_ALERT.name)) {
homeBanners.add(v);
}
if ((v.displayPosition ?? "").contains(SCBannerType.GAME.name)) {
gameBanners.add(v);
}
}
notifyListeners();
} catch (e) {}
}
Map<String, List<Emojis>> emojiByTab = {};
///emoji表情
void emojiAll() async {
var roomEmojis = await SCChatRoomRepository().emojiAll();
for (var value in roomEmojis) {
emojiByTab[value.groupName!] = value.emojis ?? [];
}
notifyListeners();
}
///
void configLevel() {
SCConfigRepositoryImp().configLevel();
}
SCBannerLeaderboardRes? appLeaderResult;
///
void appLeaderboard() async {
try {
appLeaderResult = await SCChatRoomRepository().appLeaderboard();
notifyListeners();
} catch (e) {}
}
}
import 'package:flutter/material.dart';
import 'package:yumi/shared/data_sources/sources/repositories/sc_config_repository_imp.dart';
import 'package:yumi/shared/data_sources/sources/repositories/sc_room_repository_imp.dart';
import 'package:yumi/shared/tools/sc_gift_vap_svga_manager.dart';
import 'package:yumi/shared/data_sources/models/country_mode.dart';
import 'package:yumi/shared/business_logic/models/res/sc_banner_leaderboard_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 'package:yumi/shared/business_logic/models/res/sc_index_banner_res.dart';
import 'package:yumi/shared/business_logic/models/res/sc_room_emoji_res.dart';
import '../../shared/data_sources/models/enum/sc_banner_type.dart';
import '../../shared/data_sources/models/enum/sc_gift_type.dart';
class SCAppGeneralManager extends ChangeNotifier {
List<CountryMode> countryModeList = [];
Map<String, List<Country>> _countryMap = {};
Map<String, Country> _countryByNameMap = {};
bool _isFetchingCountryList = false;
///
List<SocialChatGiftRes> giftResList = [];
Map<String, SocialChatGiftRes> _giftByIdMap = {};
Map<String, List<SocialChatGiftRes>> giftByTab = {};
Map<String, List<SocialChatGiftRes>> activityGiftByTab = {};
///
Country? selectCountryInfo;
List<SocialChatGiftRes> activityList = [];
SCAppGeneralManager() {
fetchCountryList();
giftList(); // 使
giftActivityList();
// giftBackpack();
// configLevel();
}
///
Future<void> fetchCountryList() async {
if (_isFetchingCountryList) {
return;
}
if (countryModeList.isNotEmpty) {
clearCountrySelection();
notifyListeners();
return;
}
_isFetchingCountryList = true;
try {
await Future.delayed(Duration(milliseconds: 550));
final result = await SCConfigRepositoryImp().loadCountry();
_countryMap.clear();
_countryByNameMap.clear();
countryModeList.clear();
for (final c in result.openCountry ?? const <Country>[]) {
final prefix = c.aliasName?.substring(0, 1) ?? "";
if (prefix.isNotEmpty) {
final countryList = _countryMap[prefix] ?? <Country>[];
countryList.add(c.copyWith());
_countryMap[prefix] = countryList;
}
if ((c.countryName ?? "").isNotEmpty) {
_countryByNameMap[c.countryName!] = c;
}
}
_countryMap.forEach((k, v) {
countryModeList.add(CountryMode(k, v));
});
///
countryModeList.sort((a, b) => a.prefix.compareTo(b.prefix));
notifyListeners();
} catch (_) {
notifyListeners();
} finally {
_isFetchingCountryList = false;
}
}
///
Country? findCountryByName(String countryName) {
return _countryByNameMap[countryName];
}
///
Country? chooseCountry(int subIndex, int cIndex) {
var selectCm = countryModeList[subIndex].prefixCountrys[cIndex];
if (selectCountryInfo != null) {
selectCountryInfo = null;
} else {
selectCountryInfo = selectCm;
}
return selectCountryInfo;
}
void updateCurrentCountry(Country? countryInfo) {
selectCountryInfo = countryInfo;
notifyListeners();
}
void clearCountrySelection() {
selectCountryInfo = null;
}
void openDrawer(BuildContext context) {
Scaffold.of(context).openDrawer();
}
void closeDrawer(BuildContext context) {
Scaffold.of(context).closeDrawer();
}
void giftList({bool includeCustomized = true}) async {
try {
giftResList = await SCChatRoomRepository().giftList();
giftByTab.clear();
for (var gift in giftResList) {
giftByTab[gift.giftTab];
var gmap = giftByTab[gift.giftTab];
gmap ??= [];
gmap.add(gift);
giftByTab[gift.giftTab!] = gmap;
var gAllMap = giftByTab["ALL"];
gAllMap ??= [];
if (gift.giftTab != "NSCIONAL_FLAG" &&
gift.giftTab != "ACTIVITY" &&
gift.giftTab != "LUCKY_GIFT" &&
gift.giftTab != "CP" &&
gift.giftTab != "CUSTOMIZED" &&
gift.giftTab != "MAGIC") {
gAllMap.add(gift);
}
giftByTab["ALL"] = gAllMap;
_giftByIdMap[gift.id!] = gift;
}
if (includeCustomized) {
giftByTab["CUSTOMIZED"] ??= [];
///
giftByTab["CUSTOMIZED"]?.insert(0, SocialChatGiftRes(id: "-1000"));
} else {
giftByTab.remove("CUSTOMIZED");
}
notifyListeners();
///
downLoad(giftResList);
} catch (e) {}
}
void giftActivityList() async {
try {
activityList.clear();
activityList = await SCChatRoomRepository().giftActivityList();
activityGiftByTab.clear();
for (var gift in activityList) {
var gmap = activityGiftByTab["${gift.activityId}"];
gmap ??= [];
gmap.add(gift);
activityGiftByTab["${gift.activityId}"] = gmap;
}
notifyListeners();
} catch (e) {}
}
///
void giftBackpack() async {
try {
var result = await SCChatRoomRepository().giftBackpack();
} catch (e) {}
}
///id获取礼物
SocialChatGiftRes? getGiftById(String id) {
return _giftByIdMap[id];
}
void downLoad(List<SocialChatGiftRes> giftResList) {
for (var gift in giftResList) {
final giftSourceUrl = gift.giftSourceUrl ?? "";
if (giftSourceUrl.isEmpty) {
continue;
}
if (!scGiftHasFullScreenEffect(gift.special)) {
continue;
}
SCGiftVapSvgaManager().preload(giftSourceUrl);
}
}
List<SCIndexBannerRes> homeBanners = [];
List<SCIndexBannerRes> exploreBanners = [];
List<SCIndexBannerRes> roomBanners = [];
List<SCIndexBannerRes> gameBanners = [];
///banner
void loadMainBanner() async {
try {
var banners = await SCConfigRepositoryImp().getBanner();
homeBanners.clear();
roomBanners.clear();
gameBanners.clear();
exploreBanners.clear();
for (var v in banners) {
if ((v.displayPosition ?? "").contains(
SCBannerType.EXPLORE_PAGE.name,
)) {
exploreBanners.add(v);
}
if ((v.displayPosition ?? "").contains(SCBannerType.ROOM.name)) {
roomBanners.add(v);
}
if ((v.displayPosition ?? "").contains(SCBannerType.HOME_ALERT.name)) {
homeBanners.add(v);
}
if ((v.displayPosition ?? "").contains(SCBannerType.GAME.name)) {
gameBanners.add(v);
}
}
notifyListeners();
} catch (e) {}
}
Map<String, List<Emojis>> emojiByTab = {};
///emoji表情
void emojiAll() async {
var roomEmojis = await SCChatRoomRepository().emojiAll();
for (var value in roomEmojis) {
emojiByTab[value.groupName!] = value.emojis ?? [];
}
notifyListeners();
}
///
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};
bool get _controllersReady =>
animationControllerList.length >= giftMap.length;
void enqueueGiftAnimation(LGiftModel giftModel) {
pendingAnimationsQueue.add(giftModel);
proceedToNextAnimation();
@ -18,7 +21,7 @@ class GiftAnimationManager extends ChangeNotifier {
///
proceedToNextAnimation() {
if (pendingAnimationsQueue.isEmpty) {
if (pendingAnimationsQueue.isEmpty || !_controllersReady) {
return;
}
var playGift = pendingAnimationsQueue.first;
@ -45,6 +48,7 @@ class GiftAnimationManager extends ChangeNotifier {
void attachAnimationControllers(List<LGiftScrollingScreenAnimsBean> anins) {
animationControllerList = anins;
proceedToNextAnimation();
}
void cleanupAnimationResources() {

View File

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

View File

@ -1,6 +1,7 @@
import 'package:yumi/shared/data_sources/sources/local/user_manager.dart';
import '../../../data_sources/models/enum/sc_props_type.dart';
/// token : ""
/// 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}]}
@ -214,7 +215,7 @@ class SocialChatUserProfile {
_useProps?.add(UseProps.fromJson(v));
});
}
_userAvatar = json['userAvatar'];
_userAvatar = json['userAvatar'] ?? json['avatar'];
_roles = json['roles'];
_userNickname = json['userNickname'];
_hobby = json['hobby'];
@ -462,6 +463,7 @@ class SocialChatUserProfile {
void cleanWearBadge() {
_wearBadge?.clear();
}
void cleanUseProps() {
_useProps?.clear();
}

View File

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

View File

@ -1,8 +1,15 @@
enum SCGiftType{
ANIMSCION,
MUSIC,
GLOBAL_GIFT,
LUCKY_GIFT,
MAGIC,
CP,
}
enum SCGiftType { ANIMSCION, MUSIC, GLOBAL_GIFT, LUCKY_GIFT, MAGIC, CP }
const String kSCGiftAnimationSpecialAlias = 'ANIMATION';
bool scGiftHasAnimationSpecial(String? special) {
final value = special ?? '';
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 'package:yumi/shared/business_logic/repositories/general_repository.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;
SCGeneralRepositoryImp._internal();
factory SCGeneralRepositoryImp() {
return _instance ??= SCGeneralRepositoryImp._internal();
}
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:yumi/shared/business_logic/repositories/general_repository.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;
SCGeneralRepositoryImp._internal();
factory SCGeneralRepositoryImp() {
return _instance ??= SCGeneralRepositoryImp._internal();
}
///external/oss/upload
@override
Future<String> upload(File image) async {
String filePath = image.path;
var name =
filePath.substring(filePath.lastIndexOf("/") + 1, filePath.length);
FormData formData = FormData.fromMap(
{"file": await MultipartFile.fromFile(filePath, filename: name)},
var name = filePath.substring(
filePath.lastIndexOf("/") + 1,
filePath.length,
);
FormData formData = FormData.fromMap({
"file": await MultipartFile.fromFile(filePath, filename: name),
});
final response = await http.dio.post(
"91458261d1ab337ad7b1da3610dd496cf7f58dbb0fecacfbdfe516c4892b1694",
data: formData,
@ -28,15 +31,15 @@ class SCGeneralRepositoryImp implements SocialChatGeneralRepository {
contentType: 'multipart/form-data',
sendTimeout: const Duration(seconds: 60),
receiveTimeout: const Duration(seconds: 60),
extra: {
TimeOutInterceptor.timeoutSecondsKey: 60,
},
extra: {TimeOutInterceptor.timeoutSecondsKey: 60},
),
);
final baseResponse = ResponseData<String>.fromJson(
response.data as Map<String, dynamic>,
fromJsonT: (json) => json as String,
);
debugPrint("[OSS Upload] response.data: ${response.data}");
debugPrint("[OSS Upload] parsed file url: ${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/services/audio/rtm_manager.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 {
static SCChatRoomRepository? _instance;
@ -55,9 +56,10 @@ class SCChatRoomRepository implements SocialChatRoomRepository {
"363603111e51beac2d183014dd29b81ced652239ec13178acd7001096cab7429",
queryParams: queryParams,
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
@ -84,7 +86,7 @@ class SCChatRoomRepository implements SocialChatRoomRepository {
queryParams: queryParams,
fromJson: (json) => MyRoomRes.fromJson(json),
);
return result;
return SCRoomProfileCache.applyToMyRoomRes(result);
}
///room/relation/status
@ -115,7 +117,9 @@ class SCChatRoomRepository implements SocialChatRoomRepository {
///activity/room-contribution-activity
@override
Future<SCRoomContributeLevelRes> roomContributionActivity(String roomId) async {
Future<SCRoomContributeLevelRes> roomContributionActivity(
String roomId,
) async {
Map<String, dynamic> queryParams = {};
queryParams["roomId"] = roomId;
final result = await http.get(
@ -191,7 +195,10 @@ class SCChatRoomRepository implements SocialChatRoomRepository {
"a01ade8fbb604c0904557a9483fcc4d9",
queryParams: {"roomId": roomId},
fromJson:
(json) => (json as List).map((e) => SocialChatUserProfile.fromJson(e)).toList(),
(json) =>
(json as List)
.map((e) => SocialChatUserProfile.fromJson(e))
.toList(),
);
return result;
}
@ -213,7 +220,8 @@ class SCChatRoomRepository implements SocialChatRoomRepository {
final result = await http.get<List<SocialChatGiftRes>>(
"3a613b7450f8fd9082b988bd21df454d",
fromJson:
(json) => (json as List).map((e) => SocialChatGiftRes.fromJson(e)).toList(),
(json) =>
(json as List).map((e) => SocialChatGiftRes.fromJson(e)).toList(),
);
return result;
}
@ -317,9 +325,10 @@ class SCChatRoomRepository implements SocialChatRoomRepository {
"363603111e51beac2d183014dd29b81cce9dda0f9cd8c267f6e4fd3f05bda090",
queryParams: {"roomAccount": roomAccount},
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
@ -500,7 +509,9 @@ class SCChatRoomRepository implements SocialChatRoomRepository {
queryParams: parm,
fromJson:
(json) =>
(json as List).map((e) => SocialChatRoomMemberRes.fromJson(e)).toList(),
(json as List)
.map((e) => SocialChatRoomMemberRes.fromJson(e))
.toList(),
);
return result;
}
@ -584,7 +595,9 @@ class SCChatRoomRepository implements SocialChatRoomRepository {
"48ab0afa0e4305070eaa82edc3fc3996",
fromJson:
(json) =>
(json as List).map((e) => SocialChatGiftBackpackRes.fromJson(e)).toList(),
(json as List)
.map((e) => SocialChatGiftBackpackRes.fromJson(e))
.toList(),
);
return result;
}
@ -775,7 +788,8 @@ class SCChatRoomRepository implements SocialChatRoomRepository {
final result = await http.get<List<SocialChatGiftRes>>(
"60296415428765e04263c733bfc8428c397bb180e31856d94bf25d85eb342787",
fromJson:
(json) => (json as List).map((e) => SocialChatGiftRes.fromJson(e)).toList(),
(json) =>
(json as List).map((e) => SocialChatGiftRes.fromJson(e)).toList(),
);
return result;
}
@ -815,11 +829,4 @@ class SCChatRoomRepository implements SocialChatRoomRepository {
);
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_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/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_task_list_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/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/follow_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/follow_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/message_friend_user_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/repositories/user_repository.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 {
static SCAccountRepository? _instance;
@ -83,7 +88,10 @@ class SCAccountRepository implements SocialChatUserRepository {
///auth/account/login/channel
@override
Future<SocialChatLoginRes> loginForChannel(String authType, String openId) async {
Future<SocialChatLoginRes> loginForChannel(
String authType,
String openId,
) async {
final result = await http.post<SocialChatLoginRes>(
"e64f27b9ba6b37881120f4584a5444a5531a33eb137749a5decad584f58aaa44",
data: {"authType": authType, "openId": openId},
@ -106,7 +114,7 @@ class SCAccountRepository implements SocialChatUserRepository {
(json) =>
(json as List).map((e) => FollowRoomRes.fromJson(e)).toList(),
);
return result;
return result.map(SCRoomProfileCache.applyToFollowRoomRes).toList();
}
///room/relation/joined
@ -118,7 +126,7 @@ class SCAccountRepository implements SocialChatUserRepository {
(json) =>
(json as List).map((e) => FollowRoomRes.fromJson(e)).toList(),
);
return result;
return result.map(SCRoomProfileCache.applyToFollowRoomRes).toList();
}
///room/relation/trace
@ -135,7 +143,7 @@ class SCAccountRepository implements SocialChatUserRepository {
(json) =>
(json as List).map((e) => FollowRoomRes.fromJson(e)).toList(),
);
return result;
return result.map(SCRoomProfileCache.applyToFollowRoomRes).toList();
}
///room/profile
@ -145,7 +153,7 @@ class SCAccountRepository implements SocialChatUserRepository {
"1e41384e55cbd6c2374608b129e2ed27",
fromJson: (json) => MyRoomRes.fromJson(json),
);
return result;
return SCRoomProfileCache.applyToMyRoomRes(result);
}
///room/live-voice/create
@ -179,7 +187,7 @@ class SCAccountRepository implements SocialChatUserRepository {
data: param,
fromJson: (json) => JoinRoomRes.fromJson(json),
);
return result;
return SCRoomProfileCache.applyToJoinRoomRes(result);
}
///live/user/heartbeat
@ -235,13 +243,21 @@ class SCAccountRepository implements SocialChatUserRepository {
"1e41384e55cbd6c2374608b129e2ed27",
data: {
"id": roomId,
"roomId": roomId,
"roomCover": roomCover,
"cover": roomCover,
"roomName": roomName,
"roomDesc": roomDesc,
"event": event,
},
fromJson: (json) => SCEditRoomInfoRes.fromJson(json),
);
await SCRoomProfileCache.saveRoomProfile(
roomId: roomId,
roomCover: roomCover,
roomName: roomName,
roomDesc: roomDesc,
);
return result;
}
@ -431,10 +447,10 @@ class SCAccountRepository implements SocialChatUserRepository {
parm["personalPhotos"] = personalPhotos;
}
final result = await http.put<SocialChatUserProfile>(
"2fa1d4f56bb726558904ce2a50b83f96365e62b6aa808acde7b56d2d5945fbe4",
data: parm,
fromJson: (json) => SocialChatUserProfile.fromJson(json),
final result = await http.put<SocialChatUserProfile>(
"2fa1d4f56bb726558904ce2a50b83f96365e62b6aa808acde7b56d2d5945fbe4",
data: parm,
fromJson: (json) => SocialChatUserProfile.fromJson(json),
);
return result;
}
@ -535,7 +551,6 @@ class SCAccountRepository implements SocialChatUserRepository {
return result;
}
///user/user-level/consumption/exp
@override
Future<SCUserLevelExpRes> userLevelConsumptionExp(
@ -757,7 +772,8 @@ class SCAccountRepository implements SocialChatUserRepository {
final result = await http.get<List<SCTaskListRes>>(
"6e87048da643aa0388a9e46ea391daff574aff257ce7e668d08f4caccd1c6232",
fromJson:
(json) => (json as List).map((e) => SCTaskListRes.fromJson(e)).toList(),
(json) =>
(json as List).map((e) => SCTaskListRes.fromJson(e)).toList(),
);
return result;
}
@ -915,7 +931,6 @@ class SCAccountRepository implements SocialChatUserRepository {
return result;
}
///user/cp-relationship/create-apply
@override
Future<bool> cpRelationshipSendApply(String acceptApplyUserId) async {
@ -1086,7 +1101,6 @@ class SCAccountRepository implements SocialChatUserRepository {
return result;
}
///user/vip/ability/update
@override
Future<bool> userVipAbilityUpdate({

View File

@ -1,305 +1,567 @@
import 'package:flutter/animation.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_svga/flutter_svga.dart';
import 'package:yumi/app/constants/sc_global_config.dart';
import 'package:yumi/shared/tools/sc_path_utils.dart';
import 'package:yumi/shared/data_sources/sources/local/file_cache_manager.dart';
import 'package:tancent_vap/utils/constant.dart';
import 'package:tancent_vap/widgets/vap_view.dart';
import 'package:yumi/shared/data_sources/sources/local/data_persistence.dart';
class SCGiftVapSvgaManager {
Map<String, MovieEntity> videoItemCache = {};
static SCGiftVapSvgaManager? _inst;
SCGiftVapSvgaManager._internal();
factory SCGiftVapSvgaManager() => _inst ??= SCGiftVapSvgaManager._internal();
final SCPriorityQueue<SCVapTask> _tq = SCPriorityQueue<SCVapTask>(
(a, b) => a.compareTo(b), // SCVapTask compareTo
);
VapController? _rgc;
SVGAAnimationController? _rsc;
bool _play = false;
bool _dis = false;
bool _pause = false;
//
bool _mute = false;
void setMute(bool muteMusic) {
_mute = muteMusic;
DataPersistence.setPlayGiftMusic(_mute);
}
bool getMute() {
return _mute;
}
//
void bindVapCtrl(VapController vapController) {
_mute = DataPersistence.getPlayGiftMusic();
_dis = false;
_rgc = vapController;
_rgc?.setAnimListener(
onVideoStart: () {
},
onVideoComplete: () {
_hcs();
},
onFailed: (code, type, msg) {
_hcs();
},
);
}
void bindSvgaCtrl(SVGAAnimationController svgaController) {
_dis = false;
_rsc = svgaController;
_rsc?.addStatusListener((AnimationStatus status) {
if (status.isCompleted) {
_rsc?.reset();
_play = false;
_pn();
}
});
}
//
void play(
String path, {
int priority = 0,
Map<String, VAPContent>? customResources,
int type = 0,
}) {
if (path.isEmpty) {
return;
}
if (SCGlobalConfig.sdkInt > SCGlobalConfig.maxSdkNoAnim) {
if (_dis) return;
final task = SCVapTask(
path: path,
priority: priority,
customResources: customResources,
);
_tq.add(task);
if (!_play) {
_pn();
}
}
}
//
Future<void> _pn() async {
if (_pause) {
return;
}
if (_dis || _tq.isEmpty || _play) return;
final task = _tq.removeFirst();
_play = true;
try {
final pathType = SCPathUtils.getPathType(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);
}
} catch (e, s) {
print('VAP_SVGA播放失败: $e\n$s');
} finally {
//
// if (!_dis) {
// _pn();
// }
}
}
//
Future<void> _pa(SCVapTask task) async {
if (_dis) return;
if (SCPathUtils.getFileExtension(task.path).toLowerCase() == ".svga") {
MovieEntity entity;
if (videoItemCache.containsKey(task.path)) {
entity = videoItemCache[task.path]!;
} else {
entity = await SVGAParser.shared.decodeFromAssets(task.path);
videoItemCache[task.path] = entity;
}
_rsc?.videoItem = entity;
_rsc?.reset();
_rsc?.forward();
} else {
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 || _rgc == null) return;
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") {
MovieEntity? entity;
if (videoItemCache.containsKey(task.path)) {
entity = videoItemCache[task.path]!;
entity.autorelease = false;
} else {
try {
entity = await SVGAParser.shared.decodeFromURL(task.path);
entity.autorelease = false;
videoItemCache[task.path] = entity;
} catch (e) {
_play = false;
print('svga解析出错$e');
}
}
if (entity != null) {
_rsc?.videoItem = entity;
_rsc?.reset();
_rsc?.forward();
}
} else {
final file = await FileCacheManager.getInstance().getFile(url: task.path);
if (file != null && !_dis) {
await _pf(
SCVapTask(
path: file.path,
priority: task.priority,
customResources: task.customResources,
),
);
}
}
}
//
void _hcs() {
if (_rgc != null && !_dis) {
_play = false;
//
Future.delayed(const Duration(milliseconds: 50), _pn);
}
}
//
void pauseAnim() {
_pause = true;
}
//
void resumeAnim() {
_pause = false;
_pn();
}
//
void dispose() {
_dis = true;
_play = false;
_tq.clear();
_rgc?.stop();
_rgc?.dispose();
_rgc = null;
_rsc?.stop();
_rsc?.dispose();
_rsc = null;
}
//
void clearTasks() {
_play = false;
_tq.clear();
_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;
}
import 'dart:async';
import 'dart:collection';
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter_svga/flutter_svga.dart';
import 'package:yumi/app/constants/sc_global_config.dart';
import 'package:yumi/shared/tools/sc_path_utils.dart';
import 'package:yumi/shared/data_sources/sources/local/file_cache_manager.dart';
import 'package:tancent_vap/utils/constant.dart';
import 'package:tancent_vap/widgets/vap_view.dart';
import 'package:yumi/shared/data_sources/sources/local/data_persistence.dart';
class SCGiftVapSvgaManager {
Map<String, MovieEntity> videoItemCache = {};
static SCGiftVapSvgaManager? _inst;
static const int _maxPreloadConcurrency = 1;
SCGiftVapSvgaManager._internal();
factory SCGiftVapSvgaManager() => _inst ??= SCGiftVapSvgaManager._internal();
final SCPriorityQueue<SCVapTask> _tq = SCPriorityQueue<SCVapTask>(
(a, b) => a.compareTo(b), // SCVapTask compareTo
);
VapController? _rgc;
SVGAAnimationController? _rsc;
bool _play = false;
bool _dis = false;
SCVapTask? _currentTask;
final Queue<String> _preloadQueue = Queue<String>();
final Set<String> _queuedPreloadPaths = <String>{};
final Map<String, Future<MovieEntity>> _svgaLoadTasks = {};
final Map<String, Future<String>> _playablePathTasks = {};
final Map<String, String> _playablePathCache = {};
int _activePreloadCount = 0;
bool _pause = false;
//
bool _mute = false;
void setMute(bool muteMusic) {
_mute = muteMusic;
DataPersistence.setPlayGiftMusic(_mute);
}
bool getMute() {
return _mute;
}
void _log(String message) {
debugPrint('[GiftFX][Player] $message');
}
bool _needsSvgaController(String path) {
return SCPathUtils.getFileExtension(path).toLowerCase() == ".svga";
}
bool _isControllerReady(SCVapTask task) {
if (_needsSvgaController(task.path)) {
return _rsc != null;
}
return _rgc != null;
}
bool _isPlayableFileReady(String path) {
final cachedPath = _playablePathCache[path];
if (cachedPath == null || cachedPath.isEmpty) {
return false;
}
return File(cachedPath).existsSync();
}
bool _isPreloadedOrLoading(String path) {
if (_needsSvgaController(path)) {
return videoItemCache.containsKey(path) ||
_svgaLoadTasks.containsKey(path);
}
final pathType = SCPathUtils.getPathType(path);
if (pathType == PathType.asset || pathType == PathType.file) {
return true;
}
return _isPlayableFileReady(path) || _playablePathTasks.containsKey(path);
}
Future<void> preload(String path, {bool highPriority = false}) async {
if (path.isEmpty || _dis || _isPreloadedOrLoading(path)) {
return;
}
if (highPriority) {
_log('high priority preload path=$path');
await _warmupPath(path);
return;
}
if (_queuedPreloadPaths.contains(path)) {
return;
}
_preloadQueue.add(path);
_queuedPreloadPaths.add(path);
_log('enqueue preload path=$path queue=${_preloadQueue.length}');
_drainPreloadQueue();
}
void _drainPreloadQueue() {
if (_dis ||
_pause ||
_play ||
_activePreloadCount >= _maxPreloadConcurrency ||
_preloadQueue.isEmpty) {
return;
}
final path = _preloadQueue.removeFirst();
_queuedPreloadPaths.remove(path);
_activePreloadCount++;
_log(
'start preload path=$path active=$_activePreloadCount '
'queueRemaining=${_preloadQueue.length}',
);
_warmupPath(path).whenComplete(() {
_activePreloadCount--;
_log(
'finish preload path=$path active=$_activePreloadCount '
'queueRemaining=${_preloadQueue.length}',
);
_drainPreloadQueue();
});
}
Future<void> _warmupPath(String path) async {
if (_needsSvgaController(path)) {
await _loadSvgaEntity(path);
return;
}
await _ensurePlayableFilePath(path);
}
Future<MovieEntity> _loadSvgaEntity(String path) async {
final cached = videoItemCache[path];
if (cached != null) {
return cached;
}
final loadingTask = _svgaLoadTasks[path];
if (loadingTask != null) {
return loadingTask;
}
final future = () async {
final pathType = SCPathUtils.getPathType(path);
late final MovieEntity entity;
if (pathType == PathType.asset) {
entity = await SVGAParser.shared.decodeFromAssets(path);
} else if (pathType == PathType.network) {
entity = await SVGAParser.shared.decodeFromURL(path);
} else if (pathType == PathType.file) {
final bytes = await File(path).readAsBytes();
entity = await SVGAParser.shared.decodeFromBuffer(bytes);
} else {
throw Exception('Unsupported SVGA path: $path');
}
entity.autorelease = false;
videoItemCache[path] = entity;
return entity;
}();
_svgaLoadTasks[path] = future;
try {
return await future;
} finally {
_svgaLoadTasks.remove(path);
}
}
Future<String> _ensurePlayableFilePath(String path) async {
final pathType = SCPathUtils.getPathType(path);
if (pathType == PathType.asset || pathType == PathType.file) {
return path;
}
final cachedPath = _playablePathCache[path];
if (cachedPath != null &&
cachedPath.isNotEmpty &&
File(cachedPath).existsSync()) {
return cachedPath;
}
final loadingTask = _playablePathTasks[path];
if (loadingTask != null) {
return loadingTask;
}
final future = () async {
final file = await FileCacheManager.getInstance().getFile(url: path);
_playablePathCache[path] = file.path;
return file.path;
}();
_playablePathTasks[path] = future;
try {
return await future;
} finally {
_playablePathTasks.remove(path);
}
}
void _scheduleNextTask({Duration delay = Duration.zero}) {
if (_dis) {
return;
}
if (delay == Duration.zero) {
Future.microtask(_pn);
return;
}
Future.delayed(delay, _pn);
}
void _finishCurrentTask({Duration delay = Duration.zero}) {
if (_dis) {
return;
}
_play = false;
_currentTask = null;
_scheduleNextTask(delay: delay);
if (delay == Duration.zero) {
Future.microtask(_drainPreloadQueue);
} else {
Future.delayed(delay, _drainPreloadQueue);
}
}
//
void bindVapCtrl(VapController vapController) {
_mute = DataPersistence.getPlayGiftMusic();
_dis = false;
_rgc = vapController;
_log(
'bindVapCtrl hasVapCtrl=${_rgc != null} '
'hasSvgaCtrl=${_rsc != null} queue=${_tq.length} mute=$_mute',
);
_rgc?.setAnimListener(
onVideoStart: () {
_log('vap onVideoStart path=${_currentTask?.path}');
},
onVideoComplete: () {
_log('vap onVideoComplete path=${_currentTask?.path}');
_hcs();
},
onFailed: (code, type, msg) {
_log(
'vap onFailed path=${_currentTask?.path} code=$code type=$type msg=$msg',
);
_hcs();
},
);
_scheduleNextTask();
_drainPreloadQueue();
}
void bindSvgaCtrl(SVGAAnimationController svgaController) {
_dis = false;
_rsc = svgaController;
_log(
'bindSvgaCtrl hasSvgaCtrl=${_rsc != null} '
'hasVapCtrl=${_rgc != null} queue=${_tq.length}',
);
_rsc?.addStatusListener((AnimationStatus status) {
if (status.isCompleted) {
_log('svga completed path=${_currentTask?.path}');
_rsc?.reset();
_play = false;
_currentTask = null;
_pn();
}
});
_scheduleNextTask();
_drainPreloadQueue();
}
//
void play(
String path, {
int priority = 0,
Map<String, VAPContent>? customResources,
int type = 0,
}) {
if (path.isEmpty) {
_log('play ignored because path is empty');
return;
}
_log(
'play request path=$path ext=${SCPathUtils.getFileExtension(path)} '
'priority=$priority type=$type '
'sdkInt=${SCGlobalConfig.sdkInt} maxSdkNoAnim=${SCGlobalConfig.maxSdkNoAnim} '
'disposed=$_dis playing=$_play queueBefore=${_tq.length} '
'hasSvgaCtrl=${_rsc != null} hasVapCtrl=${_rgc != null}',
);
if (SCGlobalConfig.sdkInt > SCGlobalConfig.maxSdkNoAnim) {
if (_dis) {
_log('play ignored because manager is disposed path=$path');
return;
}
final task = SCVapTask(
path: path,
priority: priority,
customResources: customResources,
);
_tq.add(task);
_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',
'.mp4',
'.vap',
'.svga',
].contains(ext);
return isDirectAsset && isSupportedAsset;
@ -74,6 +75,7 @@ class SCPathUtils {
'.mp4',
'.mov',
'.avi',
'.svga',
'.png',
'.jpg',
'.txt',
@ -132,7 +134,6 @@ class SCPathUtils {
return false;
}
static bool fileTypeIsPic(String filePath) {
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_screen.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';
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({
required String url,
required double width,
@ -26,52 +35,99 @@ Widget head({
bool showDefault = true,
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(
child: Stack(
alignment: Alignment.center,
children: [
Container(
padding: EdgeInsets.all(width * 0.12),
width: width,
height: height ?? width,
child: ExtendedImage.network(
url,
width: width,
height: height ?? width,
fit: fit,
cache: true,
shape: shape,
border: border,
clearMemoryCacheWhenDispose: false,
clearMemoryCacheIfFailed: true,
borderRadius: borderRadius,
loadStateChanged: (ExtendedImageState state) {
if (state.extendedImageLoadState == LoadState.completed) {
return ExtendedRawImage(
image: state.extendedImageInfo?.image,
fit: fit,
);
} else if (state.extendedImageLoadState == LoadState.loading) {
if (showDefault) {
return ExtendedImage.asset(
shape: shape,
"sc_images/general/sc_icon_loading.png",
fit: BoxFit.cover,
);
}
return Container();
} else {
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)
avatar,
hasPictureHeaddress
? ColorFiltered(
colorFilter: const ColorFilter.matrix([
1,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
0,
1,
0,
0,
-1,
-1,
-1,
3,
0,
]),
child: netImage(
url: headdress,
width: width,
height: height ?? width,
fit: BoxFit.contain,
noDefaultImg: true,
),
)
: Container(),
headdress != null &&
SCPathUtils.getFileExtension(headdress).toLowerCase() ==
@ -86,7 +142,8 @@ Widget head({
)
: Container(),
headdress != null &&
SCPathUtils.getFileExtension(headdress).toLowerCase() == ".mp4" &&
SCPathUtils.getFileExtension(headdress).toLowerCase() ==
".mp4" &&
SCGlobalConfig.sdkInt > SCGlobalConfig.maxSdkNoAnim
? SizedBox(
width: width,
@ -301,7 +358,7 @@ searchWidget({
"sc_images/index/sc_icon_serach2.png",
width: 20.w,
color: serachIconColor ?? Color(0xff18F2B1),
height: 20.w
height: 20.w,
),
SizedBox(width: width(6)),
_buildPhoneInput(
@ -343,7 +400,10 @@ _buildPhoneInput(
cursorColor: SocialChatTheme.primaryColor,
decoration: InputDecoration(
hintText: tint,
hintStyle: TextStyle(color: SocialChatTheme.textSecondary, fontSize: hintSize ?? sp(14)),
hintStyle: TextStyle(
color: SocialChatTheme.textSecondary,
fontSize: hintSize ?? sp(14),
),
counterText: '',
isDense: true,
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/app/constants/sc_screen.dart';
import 'package:yumi/ui_kit/theme/socialchat_theme.dart';
//
Offset kDefaultFloatOffset = Offset(
ScreenUtil().screenWidth - width(90),
ScreenUtil().screenHeight - height(120),
);
class SCFloatIchart {
///
static final SCFloatIchart _SCFloatIchart = SCFloatIchart._internal(); //1
factory SCFloatIchart() {
return _SCFloatIchart;
}
SCFloatIchart._internal();
bool _inserted = false;
OverlayEntry? overlayEntry;
BuildContext? context;
Offset offset = kDefaultFloatOffset;
show() {
if (overlayEntry != null) {
remove(); //
}
var overlayState = Overlay.of(context!);
overlayEntry = OverlayEntry(
//
Offset kDefaultFloatOffset = Offset(
ScreenUtil().screenWidth - width(90),
ScreenUtil().screenHeight - height(120),
);
class SCFloatIchart {
///
static final SCFloatIchart _SCFloatIchart = SCFloatIchart._internal(); //1
factory SCFloatIchart() {
return _SCFloatIchart;
}
SCFloatIchart._internal();
bool _inserted = false;
OverlayEntry? overlayEntry;
BuildContext? context;
Offset offset = kDefaultFloatOffset;
show() {
if (overlayEntry != null) {
remove(); //
}
var overlayState = Overlay.of(context!);
overlayEntry = OverlayEntry(
builder: (context) {
if (SCNavigatorUtils.inLoginPage) {
remove();
return Container();
} else {
return buildToastLayout();
}
},
);
overlayState.insert(overlayEntry!);
_inserted = true; //
}
bool isShow() {
return _inserted;
}
remove() {
if (overlayEntry != null && overlayEntry!.mounted) {
if (_inserted) {
overlayEntry?.remove();
_inserted = false;
}
}
}
LayoutBuilder buildToastLayout() {
Timer? timer;
return LayoutBuilder(
builder: (context, constraints) {
return Stack(
children: <Widget>[
Positioned(
left: offset.dx,
top: offset.dy,
child: GestureDetector(
//child的位置
onPanUpdate: (details) {
var localPosition = details.delta;
var dx = (offset.dx + localPosition.dx).clamp(
0.0,
ScreenUtil().screenWidth - width(90),
);
var dy = (offset.dy + localPosition.dy).clamp(
0.0,
ScreenUtil().screenHeight - height(55) - kToolbarHeight,
);
offset = Offset(dx, dy);
overlayEntry!.markNeedsBuild();
},
//child贴边悬浮
onPanEnd: (details) {
var oldPosition = offset.dx;
var targets =
offset.dx + width(80) > (ScreenUtil().screenWidth / 2)
? ScreenUtil().screenWidth - width(90)
: 0;
timer = Timer.periodic(Duration(milliseconds: 1), (t) {
if (targets > 0) {
oldPosition++;
} else {
oldPosition--;
}
offset = Offset(oldPosition.toDouble(), offset.dy);
overlayEntry!.markNeedsBuild();
if (oldPosition < 0 ||
oldPosition > ScreenUtil().screenWidth - width(90)) {
timer?.cancel();
}
});
},
child: Container(
child: float(),
margin: EdgeInsets.only(bottom: height(kToolbarHeight)),
),
),
),
],
);
},
);
}
Widget float() {
JoinRoomRes? room =
Provider.of<RealTimeCommunicationManager>(
context!,
listen: false,
).currenRoom;
// User user = Provider.of<RealTimeCommunicationManager>(context, listen: false).maiMap[0];
SocialChatLoginRes? user = AccountStorage().getCurrentUser();
return FloatRoomWindow(room: room, user: user, remove: remove);
}
void init(BuildContext context) {
this.context = context;
}
}
class FloatRoomWindow extends StatefulWidget {
final JoinRoomRes? room;
final SocialChatLoginRes? user;
final Function remove;
const FloatRoomWindow({Key? key, this.room, this.user, required this.remove})
: super(key: key);
@override
_FloatRoomWindowState createState() => _FloatRoomWindowState();
}
class _FloatRoomWindowState extends State<FloatRoomWindow> {
@override
void initState() {
super.initState();
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
return SCDebounceWidget(
onTap: () {
SCRoomUtils.openCurrentRoom(context);
},
child: Container(
height: 55.w,
width: 90.w,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8.w),
color: SocialChatTheme.primaryLight,
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
SizedBox(width: 8.w),
Stack(
alignment: Alignment.center,
children: [
netImage(
url: widget.room?.roomProfile?.roomProfile?.roomCover ?? "",
width: 40.w,
height: 40.w,
borderRadius: BorderRadius.circular(8.w),
),
Image.asset(
"sc_images/general/sc_icon_online_user.png",
width: 15.w,
height: 15.w,
),
],
),
SizedBox(width: width(5)),
SCDebounceWidget(
onTap: () {
Provider.of<RealTimeCommunicationManager>(
context,
listen: false,
).exitCurrentVoiceRoomSession(false);
Timer(Duration(milliseconds: 550), () {
widget.remove.call();
});
},
child: Padding(
padding: EdgeInsets.all(5.w),
child: Image.asset(
"sc_images/index/sc_icon_room_flot_close.png",
width: 20.w,
height: 20.w,
),
),
),
],
),
),
);
}
}
return buildToastLayout();
}
},
);
overlayState.insert(overlayEntry!);
_inserted = true; //
}
bool isShow() {
return _inserted;
}
remove() {
if (overlayEntry != null && overlayEntry!.mounted) {
if (_inserted) {
overlayEntry?.remove();
_inserted = false;
}
}
}
LayoutBuilder buildToastLayout() {
Timer? timer;
return LayoutBuilder(
builder: (context, constraints) {
return Stack(
children: <Widget>[
Positioned(
left: offset.dx,
top: offset.dy,
child: GestureDetector(
//child的位置
onPanUpdate: (details) {
var localPosition = details.delta;
var dx = (offset.dx + localPosition.dx).clamp(
0.0,
ScreenUtil().screenWidth - width(90),
);
var dy = (offset.dy + localPosition.dy).clamp(
0.0,
ScreenUtil().screenHeight - height(55) - kToolbarHeight,
);
offset = Offset(dx, dy);
overlayEntry!.markNeedsBuild();
},
//child贴边悬浮
onPanEnd: (details) {
var oldPosition = offset.dx;
var targets =
offset.dx + width(80) > (ScreenUtil().screenWidth / 2)
? ScreenUtil().screenWidth - width(90)
: 0;
timer = Timer.periodic(Duration(milliseconds: 1), (t) {
if (targets > 0) {
oldPosition++;
} else {
oldPosition--;
}
offset = Offset(oldPosition.toDouble(), offset.dy);
overlayEntry!.markNeedsBuild();
if (oldPosition < 0 ||
oldPosition > ScreenUtil().screenWidth - width(90)) {
timer?.cancel();
}
});
},
child: Container(
child: float(),
margin: EdgeInsets.only(bottom: height(kToolbarHeight)),
),
),
),
],
);
},
);
}
Widget float() {
JoinRoomRes? room =
Provider.of<RealTimeCommunicationManager>(
context!,
listen: false,
).currenRoom;
// User user = Provider.of<RealTimeCommunicationManager>(context, listen: false).maiMap[0];
SocialChatLoginRes? user = AccountStorage().getCurrentUser();
return FloatRoomWindow(room: room, user: user, remove: remove);
}
void init(BuildContext context) {
this.context = context;
}
}
class FloatRoomWindow extends StatefulWidget {
final JoinRoomRes? room;
final SocialChatLoginRes? user;
final Function remove;
const FloatRoomWindow({Key? key, this.room, this.user, required this.remove})
: super(key: key);
@override
_FloatRoomWindowState createState() => _FloatRoomWindowState();
}
class _FloatRoomWindowState extends State<FloatRoomWindow> {
@override
void initState() {
super.initState();
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
return SCDebounceWidget(
onTap: () {
SCRoomUtils.openCurrentRoom(context);
},
child: Container(
height: 55.w,
width: 90.w,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8.w),
color: SocialChatTheme.primaryLight,
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
SizedBox(width: 8.w),
Stack(
alignment: Alignment.center,
children: [
netImage(
url: resolveRoomCoverUrl(
widget.room?.roomProfile?.roomProfile?.id,
widget.room?.roomProfile?.roomProfile?.roomCover,
),
defaultImg: kRoomCoverDefaultImg,
width: 40.w,
height: 40.w,
borderRadius: BorderRadius.circular(8.w),
),
Image.asset(
"sc_images/general/sc_icon_online_user.png",
width: 15.w,
height: 15.w,
),
],
),
SizedBox(width: width(5)),
SCDebounceWidget(
onTap: () {
Provider.of<RealTimeCommunicationManager>(
context,
listen: false,
).exitCurrentVoiceRoomSession(false);
Timer(Duration(milliseconds: 550), () {
widget.remove.call();
});
},
child: Padding(
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 {
late SVGAAnimationController _svgaController;
void _giftFxLog(String message) {
debugPrint('[GiftFX][Widget] $message');
}
@override
void initState() {
super.initState();
_svgaController = SVGAAnimationController(vsync: this);
_giftFxLog('initState tag=${widget.tag}');
if (widget.tag == "room_gift") {
SCGiftVapSvgaManager().bindSvgaCtrl(_svgaController);
}
@ -28,31 +33,43 @@ class _VapPlusSvgaPlayerState extends State<VapPlusSvgaPlayer>
@override
void dispose() {
_giftFxLog('dispose tag=${widget.tag}');
SCGiftVapSvgaManager().dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
Positioned.fill(
child: IgnorePointer(
// VapView可以通过外层包Container(),
// VapView can set the width and height through the outer package Container() to limit the width and height of the pop-up video
child: VapView(
scaleType: ScaleType.centerCrop,
onViewCreated: (controller) {
if (widget.tag == "room_gift") {
SCGiftVapSvgaManager().bindVapCtrl(controller);
// VapManager().play("https://res.cloudinary.com/dkmchpua1/video/upload/v1737624783/vcg9co6yyfqsadgety1n.mp4");
}
},
return IgnorePointer(
child: RepaintBoundary(
child: Stack(
children: [
Positioned.fill(
child: RepaintBoundary(
child: VapView(
scaleType: ScaleType.fitCenter,
onViewCreated: (controller) {
_giftFxLog('onViewCreated tag=${widget.tag}');
if (widget.tag == "room_gift") {
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(
widget.message.roomId!,
navigatorKey.currentState!.context,
fromFloting: true
fromFloting: true,
);
}
},
@ -157,7 +157,7 @@ class _FloatingGiftScreenWidgetState extends State<FloatingGiftScreenWidget>
padding: EdgeInsets.symmetric(horizontal: 10.w),
decoration: BoxDecoration(
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,
),
),
@ -278,18 +278,18 @@ class _FloatingGiftScreenWidgetState extends State<FloatingGiftScreenWidget>
String _getLuckGiftFloatBg() {
if ((widget.message.coins ?? 0) > 4999 &&
(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 &&
(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 &&
(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 &&
(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) {
return "sc_images/room/sc_icon_luck_gift_flosc_bg5.png";
return "sc_images/room/sc_icon_luck_gift_float_bg5.png";
}
return "";

View File

@ -127,7 +127,7 @@ class _FloatingLuckGiftScreenWidgetState
SCRoomUtils.goRoom(
widget.message.roomId!,
navigatorKey.currentState!.context,
fromFloting: true
fromFloting: true,
);
}
},
@ -161,7 +161,7 @@ class _FloatingLuckGiftScreenWidgetState
flipX: SCGlobalConfig.lang == "ar" ? true : false, //
flipY: false, // false
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,
),
),
@ -322,7 +322,7 @@ class _FloatingLuckGiftScreenWidgetState
SCRoomUtils.goRoom(
widget.message.roomId!,
navigatorKey.currentState!.context,
fromFloting: true
fromFloting: true,
);
}
},
@ -354,7 +354,7 @@ class _FloatingLuckGiftScreenWidgetState
padding: EdgeInsets.symmetric(horizontal: 15.w),
decoration: BoxDecoration(
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,
),
),

View File

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

View File

@ -1,173 +1,176 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.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/shared/tools/sc_lk_dialog_util.dart';
import 'package:yumi/services/room/rc_room_manager.dart';
import 'package:yumi/services/audio/rtc_manager.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 {
@override
_RoomOnlineUserWidgetState createState() => _RoomOnlineUserWidgetState();
}
class _RoomOnlineUserWidgetState extends State<RoomOnlineUserWidget> {
@override
Widget build(BuildContext context) {
return Consumer<RtcProvider>(
builder: (context, ref, child) {
return Row(
children: [
_buildExperience(),
SizedBox(width: 15.w),
ref.onlineUsers.isNotEmpty
? Expanded(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Spacer(),
SizedBox(
height: 25.w,
child: Stack(
clipBehavior: Clip.none,
children: [
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
mainAxisSize: MainAxisSize.min,
children: List.generate(
ref.onlineUsers.length,
(index) {
return Transform.translate(
offset: Offset(-3.w * index, 0),
child: Padding(
padding: EdgeInsets.only(right: 0.w),
child: netImage(
url:
ref
.onlineUsers[index]
.userAvatar ??
"",
width: 23.w,
height: 23.w,
shape: BoxShape.circle,
border: Border.all(
color: Colors.white,
width: 1.w,
),
),
),
);
},
),
),
),
],
),
),
GestureDetector(
child: Container(
padding: EdgeInsets.symmetric(
horizontal: 3.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,
),
],
),
),
onTap: () {
showBottomInBottomDialog(
context,
RoomOnlinePage(
roomId:
ref
.currenRoom
?.roomProfile
?.roomProfile
?.id,
),
);
},
),
SizedBox(width: 5.w,)
],
),
)
: Container(),
],
);
},
);
}
///
_buildExperience() {
return Consumer<SocialChatRoomManager>(
builder: (context, ref, child) {
return GestureDetector(
child: Container(
width: 90.w,
height: 27.w,
decoration: BoxDecoration(
color: Colors.white10,
borderRadius: BorderRadiusDirectional.only(
topEnd: Radius.circular(20.w),
bottomEnd: Radius.circular(20.w),
),
),
alignment: AlignmentDirectional.center,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
"sc_images/room/sc_icon_room_contribute.png",
width: 18.w,
height: 18.w,
),
SizedBox(width: 5.w),
text(
"${ref.roomContributeLevelRes?.thisWeekIntegral ?? 0}",
fontSize: 13.sp,
textColor: Colors.orangeAccent,
fontWeight: FontWeight.w600,
),
Icon(
Icons.chevron_right,
size: 13.w,
color: Colors.orangeAccent,
),
],
),
),
onTap: () {
SmartDialog.show(
tag: "showRoomGiftRankPage",
alignment: Alignment.bottomCenter,
animationType: SmartAnimationType.fade,
builder: (_) {
return RoomGiftRankPage();
},
);
},
);
},
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.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/shared/tools/sc_lk_dialog_util.dart';
import 'package:yumi/services/room/rc_room_manager.dart';
import 'package:yumi/services/audio/rtc_manager.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});
@override
State<RoomOnlineUserWidget> createState() => _RoomOnlineUserWidgetState();
}
class _RoomOnlineUserWidgetState extends State<RoomOnlineUserWidget> {
void _openRoomOnlinePage(RtcProvider ref) {
showBottomInBottomDialog(
context,
RoomOnlinePage(roomId: ref.currenRoom?.roomProfile?.roomProfile?.id),
);
}
@override
Widget build(BuildContext context) {
return Consumer<RtcProvider>(
builder: (context, ref, child) {
return Row(
children: [
_buildExperience(),
SizedBox(width: 15.w),
ref.onlineUsers.isNotEmpty
? Expanded(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Spacer(),
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => _openRoomOnlinePage(ref),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
height: 25.w,
child: Stack(
clipBehavior: Clip.none,
children: [
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
mainAxisSize: MainAxisSize.min,
children: List.generate(
ref.onlineUsers.length,
(index) {
return Transform.translate(
offset: Offset(-3.w * index, 0),
child: Padding(
padding: EdgeInsets.only(
right: 0.w,
),
child: netImage(
url:
ref
.onlineUsers[index]
.userAvatar ??
"",
width: 23.w,
height: 23.w,
shape: BoxShape.circle,
border: Border.all(
color: Colors.white,
width: 1.w,
),
),
),
);
},
),
),
),
],
),
),
Container(
padding: EdgeInsets.symmetric(
horizontal: 3.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,
),
],
),
),
],
),
),
SizedBox(width: 5.w),
],
),
)
: Container(),
],
);
},
);
}
///
_buildExperience() {
return Consumer<SocialChatRoomManager>(
builder: (context, ref, child) {
return GestureDetector(
child: Container(
width: 90.w,
height: 27.w,
decoration: BoxDecoration(
color: Colors.white10,
borderRadius: BorderRadiusDirectional.only(
topEnd: Radius.circular(20.w),
bottomEnd: Radius.circular(20.w),
),
),
alignment: AlignmentDirectional.center,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
"sc_images/room/sc_icon_room_contribute.png",
width: 18.w,
height: 18.w,
),
SizedBox(width: 5.w),
text(
"${ref.roomContributeLevelRes?.thisWeekIntegral ?? 0}",
fontSize: 13.sp,
textColor: Colors.orangeAccent,
fontWeight: FontWeight.w600,
),
Icon(
Icons.chevron_right,
size: 13.w,
color: Colors.orangeAccent,
),
],
),
),
onTap: () {
SmartDialog.show(
tag: "showRoomGiftRankPage",
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` 双字段,并补齐编辑房间成功后的封面内存回写。
- 已继续修复房间设置保存封面后丢失的问题:房间保存接口改为兼容提交 `id/roomId``roomCover/cover`,并在保存成功后优先保留刚提交的封面、名称、公告,避免被空响应覆盖。
- 已新增按房间 ID 的本地房间资料缓存兜底:首页、我的房间、历史/关注、搜索和再次进入房间都会优先回填最近一次本地保存的封面;同时已将房间封面空态图替换为新的 `sc_no_data.png`
- 已确认房间封面上传接口与 `PUT /room/profile` 保存接口都返回了正确图片地址,并修正首页/重进房间的取值优先级,避免被列表接口短时间返回的旧封面或无效封面重新覆盖。
- 已继续完善个人主页头像与滚动表现:顶部小头像保持纯头像,主头像恢复头像框叠加;同时将个人主页背景并入可滚动 header修复滑动时内容与背景不同步的问题。
- 已进一步统一头像框方案:共享头像组件改为“头像居中 + 头像框包边”结构,并对这类静态头像框资源增加白底去除处理,不再简单把头像框整张盖在头像上;个人主页主头像与房间麦位头像现已同步采用这一展示方式。
- 已修复静态头像框透明区域误变黑的问题:调整头像框白底透明化公式后,进入房间时不再出现只有头像附近有颜色、其余房间背景被黑块覆盖的现象。
- 已修复个人主页首屏黑屏与内容区多余圆角问题:为个人页补充稳定的底图/底色占位,避免进入页面时先出现黑屏;同时移除资料内容区顶部多余圆角。
- 已将个人主页首屏占位升级为骨架屏进入个人页时会先按真实版式展示头像、昵称、计数区、Tab 和资料内容的骨架块,不再先露默认背景等待约 1 秒。
- 已优化语言房顶部成员入口的点击范围:右上角在线成员区域现已将左侧头像堆叠区与右侧成员图标统一为同一点击热区,用户点击头像区也可直接打开成员列表页。
- 完成仓库结构、依赖引用、资源体积和 APK 组成的第一轮排查。
- 移除 4 个当前未在业务层直接使用的插件依赖:`loading_indicator_view_plus``social_sharing_plus``flutter_foreground_task``on_audio_query`,并清理相关平台声明。
- 修补 `image_cropper 5.0.1` Android 兼容问题,切换到本地 path 依赖以恢复构建。
@ -50,12 +63,32 @@
## 已改动文件
- `需求进度.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/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/follow_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/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`
- `android/key.properties`
- `pubspec.yaml`
@ -81,8 +114,21 @@
- `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` 两种返回字段,上传封面后不会再因为字段名不一致掉回空占位。
- 房间设置保存封面时,前端现在会同时兼容提交和读取两套字段,并在 `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`
- 当前包体过大的主要原因已经确认:
- universal APK 带入了 `arm64-v8a``armeabi-v7a``x86_64` 三套 ABI。