动画 以及头像框等已知问题
This commit is contained in:
parent
905e48592e
commit
41671f9e43
@ -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}',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
),
|
||||
),
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
@ -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: [
|
||||
|
||||
@ -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
@ -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;
|
||||
}
|
||||
|
||||
///播放音乐
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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) {}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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'];
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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 ?? "";
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -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({
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
146
lib/shared/tools/sc_room_profile_cache.dart
Normal file
146
lib/shared/tools/sc_room_profile_cache.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 "";
|
||||
|
||||
@ -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,
|
||||
),
|
||||
),
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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();
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
BIN
sc_images/general/sc_no_data.png
Normal file
BIN
sc_images/general/sc_no_data.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 41 KiB |
46
需求进度.md
46
需求进度.md
@ -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。
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user