1.3.1
This commit is contained in:
parent
f2e9ac3c60
commit
3103d51d4c
@ -288,6 +288,7 @@
|
|||||||
"rechargeAgency": "وكالة الشحن",
|
"rechargeAgency": "وكالة الشحن",
|
||||||
"logout": "تسجيل الخروج",
|
"logout": "تسجيل الخروج",
|
||||||
"adminCenter": "مركز الإدارة",
|
"adminCenter": "مركز الإدارة",
|
||||||
|
"managerCenter": "مركز المدير",
|
||||||
"explore": "استكشاف",
|
"explore": "استكشاف",
|
||||||
"micManagement": "إدارة الميكروفون",
|
"micManagement": "إدارة الميكروفون",
|
||||||
"followList": "قائمة المتابعة",
|
"followList": "قائمة المتابعة",
|
||||||
|
|||||||
@ -442,6 +442,7 @@
|
|||||||
"bdCenter": "BD সেন্টার",
|
"bdCenter": "BD সেন্টার",
|
||||||
"rechargeAgency": "রিচার্জ এজেন্সি",
|
"rechargeAgency": "রিচার্জ এজেন্সি",
|
||||||
"adminCenter": "অ্যাডমিন সেন্টার",
|
"adminCenter": "অ্যাডমিন সেন্টার",
|
||||||
|
"managerCenter": "ম্যানেজার সেন্টার",
|
||||||
"goldList": "সোনা তালিকা",
|
"goldList": "সোনা তালিকা",
|
||||||
"followList": "ফলো তালিকা",
|
"followList": "ফলো তালিকা",
|
||||||
"fansList": "ফ্যান তালিকা",
|
"fansList": "ফ্যান তালিকা",
|
||||||
|
|||||||
@ -412,6 +412,7 @@
|
|||||||
"bdCenter": "BD Center",
|
"bdCenter": "BD Center",
|
||||||
"rechargeAgency": "Recharge Agency",
|
"rechargeAgency": "Recharge Agency",
|
||||||
"adminCenter": "Admin Center",
|
"adminCenter": "Admin Center",
|
||||||
|
"managerCenter": "Manager Center",
|
||||||
"goldList": "Gold List",
|
"goldList": "Gold List",
|
||||||
"followList": "Follow List",
|
"followList": "Follow List",
|
||||||
"fansList": "Fans List",
|
"fansList": "Fans List",
|
||||||
|
|||||||
@ -411,6 +411,7 @@
|
|||||||
"bdCenter": "BD Merkezi",
|
"bdCenter": "BD Merkezi",
|
||||||
"rechargeAgency": "Yükleme Temsilciliği",
|
"rechargeAgency": "Yükleme Temsilciliği",
|
||||||
"adminCenter": "Yönetici Merkezi",
|
"adminCenter": "Yönetici Merkezi",
|
||||||
|
"managerCenter": "Manager Center",
|
||||||
"goldList": "Altın Listesi",
|
"goldList": "Altın Listesi",
|
||||||
"followList": "Takip Listesi",
|
"followList": "Takip Listesi",
|
||||||
"fansList": "Hayran Listesi",
|
"fansList": "Hayran Listesi",
|
||||||
|
|||||||
@ -502,7 +502,7 @@
|
|||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 12;
|
CURRENT_PROJECT_VERSION = 131;
|
||||||
DEVELOPMENT_TEAM = S9X2AJ2US9;
|
DEVELOPMENT_TEAM = S9X2AJ2US9;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
@ -693,7 +693,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = Runner/RunnerDebug.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/RunnerDebug.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 12;
|
CURRENT_PROJECT_VERSION = 131;
|
||||||
DEVELOPMENT_TEAM = F33K8VUZ62;
|
DEVELOPMENT_TEAM = F33K8VUZ62;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
@ -722,7 +722,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = Runner/RunnerRelease.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/RunnerRelease.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 12;
|
CURRENT_PROJECT_VERSION = 131;
|
||||||
DEVELOPMENT_TEAM = F33K8VUZ62;
|
DEVELOPMENT_TEAM = F33K8VUZ62;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
|||||||
@ -46,6 +46,9 @@ abstract class AppConfig {
|
|||||||
/// 管理员URL
|
/// 管理员URL
|
||||||
String get adminUrl;
|
String get adminUrl;
|
||||||
|
|
||||||
|
/// Yumi管理中心URL
|
||||||
|
String get managerCenterUrl;
|
||||||
|
|
||||||
/// 代理中心URL
|
/// 代理中心URL
|
||||||
String get agencyCenterUrl;
|
String get agencyCenterUrl;
|
||||||
|
|
||||||
|
|||||||
@ -16,7 +16,7 @@ class SCVariant1Config implements AppConfig {
|
|||||||
@override
|
@override
|
||||||
String get apiHost => const String.fromEnvironment(
|
String get apiHost => const String.fromEnvironment(
|
||||||
'API_HOST',
|
'API_HOST',
|
||||||
defaultValue: 'https://jvapi.haiyihy.com/',
|
defaultValue: 'http://192.168.110.64:1100/',
|
||||||
); // 默认连线上环境,本地调试可通过 --dart-define=API_HOST 覆盖 本地“http://192.168.110.64:1100/”,线上“https://jvapi.haiyihy.com/”
|
); // 默认连线上环境,本地调试可通过 --dart-define=API_HOST 覆盖 本地“http://192.168.110.64:1100/”,线上“https://jvapi.haiyihy.com/”
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -55,6 +55,10 @@ class SCVariant1Config implements AppConfig {
|
|||||||
@override
|
@override
|
||||||
String get adminUrl => 'https://h5.haiyihy.com/admin-center/index.html'; // 正式 H5 页面
|
String get adminUrl => 'https://h5.haiyihy.com/admin-center/index.html'; // 正式 H5 页面
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get managerCenterUrl =>
|
||||||
|
'https://h5.haiyihy.com/hyapp/manager-center/index.html'; // 正式 H5 页面
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get agencyCenterUrl =>
|
String get agencyCenterUrl =>
|
||||||
'https://h5.haiyihy.com/agency-center/index.html'; // 正式 H5 页面
|
'https://h5.haiyihy.com/agency-center/index.html'; // 正式 H5 页面
|
||||||
@ -136,6 +140,7 @@ class SCVariant1Config implements AppConfig {
|
|||||||
'imgHost': imgHost,
|
'imgHost': imgHost,
|
||||||
'privacyAgreementUrl': privacyAgreementUrl,
|
'privacyAgreementUrl': privacyAgreementUrl,
|
||||||
'userAgreementUrl': userAgreementUrl,
|
'userAgreementUrl': userAgreementUrl,
|
||||||
|
'managerCenterUrl': managerCenterUrl,
|
||||||
'primaryColor': primaryColor,
|
'primaryColor': primaryColor,
|
||||||
'tencentImAppid': tencentImAppid,
|
'tencentImAppid': tencentImAppid,
|
||||||
'agoraRtcAppid': agoraRtcAppid,
|
'agoraRtcAppid': agoraRtcAppid,
|
||||||
|
|||||||
@ -20,6 +20,7 @@ class SCGlobalConfig {
|
|||||||
static String get bdLeaderUrl => AppConfig.current.bdLeaderUrl;
|
static String get bdLeaderUrl => AppConfig.current.bdLeaderUrl;
|
||||||
static String get coinSellerUrl => AppConfig.current.coinSellerUrl;
|
static String get coinSellerUrl => AppConfig.current.coinSellerUrl;
|
||||||
static String get adminUrl => AppConfig.current.adminUrl;
|
static String get adminUrl => AppConfig.current.adminUrl;
|
||||||
|
static String get managerCenterUrl => AppConfig.current.managerCenterUrl;
|
||||||
static String get agencyCenterUrl => AppConfig.current.agencyCenterUrl;
|
static String get agencyCenterUrl => AppConfig.current.agencyCenterUrl;
|
||||||
static String get gamesKingUrl => AppConfig.current.gamesKingUrl;
|
static String get gamesKingUrl => AppConfig.current.gamesKingUrl;
|
||||||
|
|
||||||
|
|||||||
@ -1025,6 +1025,8 @@ class SCAppLocalizations {
|
|||||||
|
|
||||||
String get adminCenter => translate('adminCenter');
|
String get adminCenter => translate('adminCenter');
|
||||||
|
|
||||||
|
String get managerCenter => translate('managerCenter');
|
||||||
|
|
||||||
String get confirm => translate('confirm');
|
String get confirm => translate('confirm');
|
||||||
|
|
||||||
String get cancel => translate('cancel');
|
String get cancel => translate('cancel');
|
||||||
|
|||||||
@ -46,7 +46,6 @@ import 'services/auth/user_profile_manager.dart';
|
|||||||
import 'ui_kit/theme/socialchat_theme.dart';
|
import 'ui_kit/theme/socialchat_theme.dart';
|
||||||
import 'ui_kit/widgets/room/anim/room_gift_seat_flight_overlay.dart';
|
import 'ui_kit/widgets/room/anim/room_gift_seat_flight_overlay.dart';
|
||||||
import 'ui_kit/widgets/room/effect/vapp_svga_layer_widget.dart';
|
import 'ui_kit/widgets/room/effect/vapp_svga_layer_widget.dart';
|
||||||
import 'ui_kit/widgets/svga/sc_svga_asset_widget.dart';
|
|
||||||
|
|
||||||
bool _isCrashlyticsReady = false;
|
bool _isCrashlyticsReady = false;
|
||||||
|
|
||||||
@ -109,7 +108,6 @@ void _configureImageCache() {
|
|||||||
|
|
||||||
void _releaseVisualMemoryCaches({bool clearLiveImages = false}) {
|
void _releaseVisualMemoryCaches({bool clearLiveImages = false}) {
|
||||||
SCGiftVapSvgaManager().clearMemoryCache();
|
SCGiftVapSvgaManager().clearMemoryCache();
|
||||||
SCSvgaAssetWidget.clearMemoryCache();
|
|
||||||
final imageCache = PaintingBinding.instance.imageCache;
|
final imageCache = PaintingBinding.instance.imageCache;
|
||||||
imageCache.clear();
|
imageCache.clear();
|
||||||
if (clearLiveImages) {
|
if (clearLiveImages) {
|
||||||
|
|||||||
@ -16,7 +16,6 @@ import 'package:yumi/app/routes/sc_fluro_navigator.dart';
|
|||||||
import 'package:yumi/shared/tools/sc_loading_manager.dart';
|
import 'package:yumi/shared/tools/sc_loading_manager.dart';
|
||||||
import 'package:yumi/shared/tools/sc_version_utils.dart';
|
import 'package:yumi/shared/tools/sc_version_utils.dart';
|
||||||
import 'package:yumi/shared/data_sources/sources/repositories/sc_user_repository_impl.dart';
|
import 'package:yumi/shared/data_sources/sources/repositories/sc_user_repository_impl.dart';
|
||||||
import 'package:yumi/shared/business_logic/models/res/login_res.dart';
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:yumi/services/audio/rtc_manager.dart';
|
import 'package:yumi/services/audio/rtc_manager.dart';
|
||||||
import 'package:yumi/services/audio/rtm_manager.dart';
|
import 'package:yumi/services/audio/rtm_manager.dart';
|
||||||
@ -376,14 +375,16 @@ class SCLoginWithAccountPageState extends State<SCLoginWithAccountPage>
|
|||||||
}
|
}
|
||||||
SCLoadingManager.show(context: context);
|
SCLoadingManager.show(context: context);
|
||||||
try {
|
try {
|
||||||
final results = await Future.wait([
|
var user = await SCAccountRepository().loginForAccount(account, pass);
|
||||||
SCAccountRepository().loginForAccount(account, pass),
|
if (!mounted) {
|
||||||
SCVersionUtils.checkReview(),
|
return;
|
||||||
]);
|
}
|
||||||
var user = (results[0] as SocialChatLoginRes);
|
AccountStorage().setCurrentUser(user);
|
||||||
if (!mounted) {
|
final canContinue = await SCVersionUtils.checkReview(context: context);
|
||||||
return;
|
if (!mounted || !canContinue) {
|
||||||
}
|
SCLoadingManager.hide();
|
||||||
|
return;
|
||||||
|
}
|
||||||
await Provider.of<RtcProvider>(
|
await Provider.of<RtcProvider>(
|
||||||
context,
|
context,
|
||||||
listen: false,
|
listen: false,
|
||||||
@ -393,7 +394,6 @@ class SCLoginWithAccountPageState extends State<SCLoginWithAccountPage>
|
|||||||
if (!mounted) {
|
if (!mounted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
AccountStorage().setCurrentUser(user);
|
|
||||||
SCLoadingManager.hide();
|
SCLoadingManager.hide();
|
||||||
try {
|
try {
|
||||||
await Future.wait([
|
await Future.wait([
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
|
|||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:yumi/app_localizations.dart';
|
import 'package:yumi/app_localizations.dart';
|
||||||
import 'package:yumi/app/constants/sc_global_config.dart';
|
import 'package:yumi/app/constants/sc_global_config.dart';
|
||||||
|
import 'package:yumi/main.dart' show routeObserver;
|
||||||
import 'package:yumi/ui_kit/components/sc_compontent.dart';
|
import 'package:yumi/ui_kit/components/sc_compontent.dart';
|
||||||
import 'package:yumi/services/audio/rtc_manager.dart';
|
import 'package:yumi/services/audio/rtc_manager.dart';
|
||||||
import 'package:yumi/ui_kit/widgets/room/room_bottom_widget.dart';
|
import 'package:yumi/ui_kit/widgets/room/room_bottom_widget.dart';
|
||||||
@ -51,7 +52,7 @@ class VoiceRoomPage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _VoiceRoomPageState extends State<VoiceRoomPage>
|
class _VoiceRoomPageState extends State<VoiceRoomPage>
|
||||||
with SingleTickerProviderStateMixin {
|
with SingleTickerProviderStateMixin, RouteAware {
|
||||||
static const int _chatTabCount = 3;
|
static const int _chatTabCount = 3;
|
||||||
static const Duration _luckyGiftComboWindow = Duration(seconds: 3);
|
static const Duration _luckyGiftComboWindow = Duration(seconds: 3);
|
||||||
static const Duration _luckyGiftQueueDrainWindow = Duration(seconds: 3);
|
static const Duration _luckyGiftQueueDrainWindow = Duration(seconds: 3);
|
||||||
@ -76,11 +77,17 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
|
|||||||
int _shownRoomStartupFailureToken = 0;
|
int _shownRoomStartupFailureToken = 0;
|
||||||
RtcProvider? _rtcProvider;
|
RtcProvider? _rtcProvider;
|
||||||
bool _roomProviderRebuildScheduled = false;
|
bool _roomProviderRebuildScheduled = false;
|
||||||
|
PageRoute<dynamic>? _routeObserverRoute;
|
||||||
|
bool _roomRouteVisible = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_tabController = TabController(length: _chatTabCount, vsync: this);
|
_tabController = TabController(length: _chatTabCount, vsync: this);
|
||||||
|
Provider.of<RtcProvider>(
|
||||||
|
context,
|
||||||
|
listen: false,
|
||||||
|
).setVoiceRoomRouteVisible(true);
|
||||||
_enableRoomVisualEffects();
|
_enableRoomVisualEffects();
|
||||||
_tabController.addListener(_handleTabChange);
|
_tabController.addListener(_handleTabChange);
|
||||||
_subscription = eventBus.on<SCGiveRoomLuckPageDisposeEvent>().listen((
|
_subscription = eventBus.on<SCGiveRoomLuckPageDisposeEvent>().listen((
|
||||||
@ -110,11 +117,23 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
|
|||||||
rtcProvider.addListener(_handleRoomProviderChanged);
|
rtcProvider.addListener(_handleRoomProviderChanged);
|
||||||
context.read<RoomMusicManager>().attachRtcProvider(rtcProvider);
|
context.read<RoomMusicManager>().attachRtcProvider(rtcProvider);
|
||||||
}
|
}
|
||||||
|
final route = ModalRoute.of(context);
|
||||||
|
if (route is PageRoute<dynamic> && _routeObserverRoute != route) {
|
||||||
|
if (_routeObserverRoute != null) {
|
||||||
|
routeObserver.unsubscribe(this);
|
||||||
|
}
|
||||||
|
_routeObserverRoute = route;
|
||||||
|
routeObserver.subscribe(this, route);
|
||||||
|
_setRoomRouteVisible(route.isCurrent);
|
||||||
|
}
|
||||||
_ensureRoomVisualEffectsEnabled();
|
_ensureRoomVisualEffectsEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
routeObserver.unsubscribe(this);
|
||||||
|
_routeObserverRoute = null;
|
||||||
|
_setRoomRouteVisible(false);
|
||||||
_rtcProvider?.removeListener(_handleRoomProviderChanged);
|
_rtcProvider?.removeListener(_handleRoomProviderChanged);
|
||||||
_rtcProvider = null;
|
_rtcProvider = null;
|
||||||
_roomProviderRebuildScheduled = false;
|
_roomProviderRebuildScheduled = false;
|
||||||
@ -125,6 +144,36 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didPush() {
|
||||||
|
_setRoomRouteVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didPopNext() {
|
||||||
|
_setRoomRouteVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didPushNext() {
|
||||||
|
_setRoomRouteVisible(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didPop() {
|
||||||
|
_setRoomRouteVisible(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _setRoomRouteVisible(bool visible) {
|
||||||
|
if (_roomRouteVisible == visible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_roomRouteVisible = visible;
|
||||||
|
final rtcProvider =
|
||||||
|
_rtcProvider ?? Provider.of<RtcProvider>(context, listen: false);
|
||||||
|
rtcProvider.setVoiceRoomRouteVisible(visible);
|
||||||
|
}
|
||||||
|
|
||||||
void _handleTabChange() {
|
void _handleTabChange() {
|
||||||
if (!mounted) {
|
if (!mounted) {
|
||||||
return;
|
return;
|
||||||
@ -168,6 +217,9 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
|
|||||||
|
|
||||||
void _suspendRoomVisualEffects() {
|
void _suspendRoomVisualEffects() {
|
||||||
final rtcProvider = Provider.of<RtcProvider>(context, listen: false);
|
final rtcProvider = Provider.of<RtcProvider>(context, listen: false);
|
||||||
|
final currentRoomId =
|
||||||
|
rtcProvider.currenRoom?.roomProfile?.roomProfile?.id?.trim() ?? '';
|
||||||
|
rtcProvider.setVoiceRoomRouteVisible(false);
|
||||||
rtcProvider.setRoomVisualEffectsEnabled(false);
|
rtcProvider.setRoomVisualEffectsEnabled(false);
|
||||||
|
|
||||||
final rtmProvider = Provider.of<RtmProvider>(context, listen: false);
|
final rtmProvider = Provider.of<RtmProvider>(context, listen: false);
|
||||||
@ -184,7 +236,7 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
|
|||||||
_clearGiftFlightBatchDedupeTimers();
|
_clearGiftFlightBatchDedupeTimers();
|
||||||
_clearGiftVisualBatchDedupeTimers();
|
_clearGiftVisualBatchDedupeTimers();
|
||||||
_giftSeatFlightController.clear();
|
_giftSeatFlightController.clear();
|
||||||
OverlayManager().removeRoom();
|
OverlayManager().removeRoom(roomId: currentRoomId);
|
||||||
SCRoomEffectScheduler().clearDeferredTasks(reason: 'voice_room_suspend');
|
SCRoomEffectScheduler().clearDeferredTasks(reason: 'voice_room_suspend');
|
||||||
SCGiftVapSvgaManager().stopPlayback();
|
SCGiftVapSvgaManager().stopPlayback();
|
||||||
unawaited(
|
unawaited(
|
||||||
|
|||||||
@ -79,13 +79,15 @@ class VoiceRoomRoute implements SCIRouterProvider {
|
|||||||
final floatingGiftListener = rtmProvider.msgFloatingGiftListener;
|
final floatingGiftListener = rtmProvider.msgFloatingGiftListener;
|
||||||
final luckyGiftRewardTickerListener =
|
final luckyGiftRewardTickerListener =
|
||||||
rtmProvider.msgLuckyGiftRewardTickerListener;
|
rtmProvider.msgLuckyGiftRewardTickerListener;
|
||||||
|
final currentRoomId =
|
||||||
|
rtcProvider.currenRoom?.roomProfile?.roomProfile?.id?.trim() ?? '';
|
||||||
|
|
||||||
rtcProvider.setRoomVisualEffectsEnabled(false);
|
rtcProvider.setRoomVisualEffectsEnabled(false);
|
||||||
rtmProvider.msgFloatingGiftListener = null;
|
rtmProvider.msgFloatingGiftListener = null;
|
||||||
rtmProvider.msgLuckyGiftRewardTickerListener = null;
|
rtmProvider.msgLuckyGiftRewardTickerListener = null;
|
||||||
RoomEntranceHelper.clearQueue();
|
RoomEntranceHelper.clearQueue();
|
||||||
giftAnimationManager.clearActiveAnimations();
|
giftAnimationManager.clearActiveAnimations();
|
||||||
OverlayManager().removeRoom();
|
OverlayManager().removeRoom(roomId: currentRoomId);
|
||||||
SCRoomEffectScheduler().clearDeferredTasks(reason: 'room_background_route');
|
SCRoomEffectScheduler().clearDeferredTasks(reason: 'room_background_route');
|
||||||
SCGiftVapSvgaManager().stopPlayback();
|
SCGiftVapSvgaManager().stopPlayback();
|
||||||
|
|
||||||
|
|||||||
@ -448,13 +448,13 @@ class _SplashPageState extends State<SplashPage> {
|
|||||||
|
|
||||||
Future<void> _goMainPage() async {
|
Future<void> _goMainPage() async {
|
||||||
try {
|
try {
|
||||||
await SCVersionUtils.checkReview();
|
|
||||||
if (!mounted) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var user = AccountStorage().getCurrentUser();
|
var user = AccountStorage().getCurrentUser();
|
||||||
var token = AccountStorage().getToken();
|
var token = AccountStorage().getToken();
|
||||||
if (user != null && token.isNotEmpty) {
|
if (user != null && token.isNotEmpty) {
|
||||||
|
final canContinue = await SCVersionUtils.checkReview(context: context);
|
||||||
|
if (!mounted || !canContinue) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
SCNavigatorUtils.push(context, SCRoutes.home, replace: true);
|
SCNavigatorUtils.push(context, SCRoutes.home, replace: true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -464,6 +464,10 @@ class _SplashPageState extends State<SplashPage> {
|
|||||||
}
|
}
|
||||||
if (restoredUser != null &&
|
if (restoredUser != null &&
|
||||||
(restoredUser.token ?? "").trim().isNotEmpty) {
|
(restoredUser.token ?? "").trim().isNotEmpty) {
|
||||||
|
final canContinue = await SCVersionUtils.checkReview(context: context);
|
||||||
|
if (!mounted || !canContinue) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
SCNavigatorUtils.push(context, SCRoutes.home, replace: true);
|
SCNavigatorUtils.push(context, SCRoutes.home, replace: true);
|
||||||
} else {
|
} else {
|
||||||
SCNavigatorUtils.pushLoginIfNeeded(context, replace: true);
|
SCNavigatorUtils.pushLoginIfNeeded(context, replace: true);
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:yumi/app/constants/sc_global_config.dart';
|
import 'package:yumi/app/constants/sc_global_config.dart';
|
||||||
@ -50,6 +51,7 @@ class _MePage2State extends State<MePage2> {
|
|||||||
listen: false,
|
listen: false,
|
||||||
);
|
);
|
||||||
profileManager.fetchUserProfileData();
|
profileManager.fetchUserProfileData();
|
||||||
|
profileManager.getUserIdentity();
|
||||||
profileManager.balance();
|
profileManager.balance();
|
||||||
_loadCounter();
|
_loadCounter();
|
||||||
_loadVipStatus();
|
_loadVipStatus();
|
||||||
@ -660,6 +662,16 @@ class _MePage2State extends State<MePage2> {
|
|||||||
final items = <_MenuRowData>[];
|
final items = <_MenuRowData>[];
|
||||||
final hasCurrentUser = userProfile.currentUserProfile != null;
|
final hasCurrentUser = userProfile.currentUserProfile != null;
|
||||||
final isAgent = userProfile.userIdentity?.agent ?? false;
|
final isAgent = userProfile.userIdentity?.agent ?? false;
|
||||||
|
final isYumiManager = userProfile.userIdentity?.yumiManager ?? false;
|
||||||
|
if (kDebugMode) {
|
||||||
|
final currentProfile = userProfile.currentUserProfile;
|
||||||
|
debugPrint(
|
||||||
|
'[MePage2] menu identity userId=${currentProfile?.id}, '
|
||||||
|
'account=${currentProfile?.account}, hasCurrentUser=$hasCurrentUser, '
|
||||||
|
'agent=${userProfile.userIdentity?.agent}, admin=${userProfile.userIdentity?.admin}, '
|
||||||
|
'manager=${userProfile.userIdentity?.manager}, yumiManager=${userProfile.userIdentity?.yumiManager}',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// 1. 主播/代理入口
|
// 1. 主播/代理入口
|
||||||
if (hasCurrentUser) {
|
if (hasCurrentUser) {
|
||||||
@ -754,6 +766,25 @@ class _MePage2State extends State<MePage2> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (isYumiManager) {
|
||||||
|
if (kDebugMode) {
|
||||||
|
debugPrint('[MePage2] show Manager Center');
|
||||||
|
}
|
||||||
|
items.add(
|
||||||
|
_MenuRowData(
|
||||||
|
title: SCAppLocalizations.of(context)!.managerCenter,
|
||||||
|
assetIcon: 'sc_images/index/sc_icon_admin_center.png',
|
||||||
|
onTap: () {
|
||||||
|
SCNavigatorUtils.push(
|
||||||
|
context,
|
||||||
|
"${SCMainRoute.webViewPage}?url=${Uri.encodeComponent(SCGlobalConfig.managerCenterUrl)}&showTitle=false",
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else if (kDebugMode) {
|
||||||
|
debugPrint('[MePage2] hide Manager Center because yumiManager=$isYumiManager');
|
||||||
|
}
|
||||||
return _buildMenuCard(items);
|
return _buildMenuCard(items);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:flutter_svga/flutter_svga.dart';
|
import 'package:flutter_svga/flutter_svga.dart';
|
||||||
@ -564,7 +566,6 @@ class _GiftWallSvgaEffectOverlay extends StatefulWidget {
|
|||||||
class _GiftWallSvgaEffectOverlayState extends State<_GiftWallSvgaEffectOverlay>
|
class _GiftWallSvgaEffectOverlayState extends State<_GiftWallSvgaEffectOverlay>
|
||||||
with SingleTickerProviderStateMixin {
|
with SingleTickerProviderStateMixin {
|
||||||
late final SVGAAnimationController _controller;
|
late final SVGAAnimationController _controller;
|
||||||
MovieEntity? _retainedMovieEntity;
|
|
||||||
bool _loaded = false;
|
bool _loaded = false;
|
||||||
bool _finished = false;
|
bool _finished = false;
|
||||||
|
|
||||||
@ -584,15 +585,9 @@ class _GiftWallSvgaEffectOverlayState extends State<_GiftWallSvgaEffectOverlay>
|
|||||||
|
|
||||||
Future<void> _loadAndPlay() async {
|
Future<void> _loadAndPlay() async {
|
||||||
try {
|
try {
|
||||||
final movieEntity = await SCGiftVapSvgaManager().acquireSvgaEntity(
|
final movieEntity = await _loadMovieEntity(widget.sourceUrl);
|
||||||
widget.sourceUrl,
|
if (!mounted || _finished) return;
|
||||||
);
|
|
||||||
if (!mounted || _finished) {
|
|
||||||
SCGiftVapSvgaManager().releaseSvgaEntity(movieEntity);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_retainedMovieEntity = movieEntity;
|
|
||||||
_loaded = true;
|
_loaded = true;
|
||||||
_controller.videoItem = movieEntity;
|
_controller.videoItem = movieEntity;
|
||||||
});
|
});
|
||||||
@ -604,6 +599,28 @@ class _GiftWallSvgaEffectOverlayState extends State<_GiftWallSvgaEffectOverlay>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<MovieEntity> _loadMovieEntity(String sourceUrl) async {
|
||||||
|
final cache = SCGiftVapSvgaManager().videoItemCache;
|
||||||
|
final cached = cache[sourceUrl];
|
||||||
|
if (cached != null) return cached;
|
||||||
|
|
||||||
|
final pathType = SCPathUtils.getPathType(sourceUrl);
|
||||||
|
late final MovieEntity movieEntity;
|
||||||
|
if (pathType == PathType.network) {
|
||||||
|
movieEntity = await SVGAParser.shared.decodeFromURL(sourceUrl);
|
||||||
|
} else if (pathType == PathType.asset) {
|
||||||
|
movieEntity = await SVGAParser.shared.decodeFromAssets(sourceUrl);
|
||||||
|
} else if (pathType == PathType.file) {
|
||||||
|
final bytes = await File(sourceUrl).readAsBytes();
|
||||||
|
movieEntity = await SVGAParser.shared.decodeFromBuffer(bytes);
|
||||||
|
} else {
|
||||||
|
throw Exception("Unsupported SVGA source: $sourceUrl");
|
||||||
|
}
|
||||||
|
movieEntity.autorelease = false;
|
||||||
|
cache[sourceUrl] = movieEntity;
|
||||||
|
return movieEntity;
|
||||||
|
}
|
||||||
|
|
||||||
void _finish() {
|
void _finish() {
|
||||||
if (_finished) return;
|
if (_finished) return;
|
||||||
_finished = true;
|
_finished = true;
|
||||||
@ -614,8 +631,6 @@ class _GiftWallSvgaEffectOverlayState extends State<_GiftWallSvgaEffectOverlay>
|
|||||||
void dispose() {
|
void dispose() {
|
||||||
_controller.removeStatusListener(_handleStatusChanged);
|
_controller.removeStatusListener(_handleStatusChanged);
|
||||||
_controller.dispose();
|
_controller.dispose();
|
||||||
SCGiftVapSvgaManager().releaseSvgaEntity(_retainedMovieEntity);
|
|
||||||
_retainedMovieEntity = null;
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
@ -48,6 +49,9 @@ class _WebViewPageState extends State<WebViewPage> {
|
|||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
urlLink = SCH5UrlUtils.appendAppParams(widget.url);
|
urlLink = SCH5UrlUtils.appendAppParams(widget.url);
|
||||||
|
if (kDebugMode) {
|
||||||
|
debugPrint('[WebViewPage] load url=${_maskSensitiveUrl(urlLink)}');
|
||||||
|
}
|
||||||
// 初始化 WebViewController
|
// 初始化 WebViewController
|
||||||
_controller =
|
_controller =
|
||||||
WebViewController()
|
WebViewController()
|
||||||
@ -138,11 +142,21 @@ class _WebViewPageState extends State<WebViewPage> {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
onPageStarted: (String url) {
|
onPageStarted: (String url) {
|
||||||
|
if (kDebugMode) {
|
||||||
|
debugPrint(
|
||||||
|
'[WebViewPage] page started url=${_maskSensitiveUrl(url)}',
|
||||||
|
);
|
||||||
|
}
|
||||||
setState(() {
|
setState(() {
|
||||||
_isLoading = true;
|
_isLoading = true;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onPageFinished: (String url) {
|
onPageFinished: (String url) {
|
||||||
|
if (kDebugMode) {
|
||||||
|
debugPrint(
|
||||||
|
'[WebViewPage] page finished url=${_maskSensitiveUrl(url)}',
|
||||||
|
);
|
||||||
|
}
|
||||||
// 延迟500毫秒注入,等待Vue3初始化
|
// 延迟500毫秒注入,等待Vue3初始化
|
||||||
Future.delayed(Duration(milliseconds: 550), () {
|
Future.delayed(Duration(milliseconds: 550), () {
|
||||||
_injectJavaScriptInterface();
|
_injectJavaScriptInterface();
|
||||||
@ -310,6 +324,23 @@ class _WebViewPageState extends State<WebViewPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _onWebResourceError(WebResourceError error) {
|
void _onWebResourceError(WebResourceError error) {
|
||||||
|
if (kDebugMode) {
|
||||||
|
debugPrint(
|
||||||
|
'[WebViewPage] resource error code=${error.errorCode}, '
|
||||||
|
'type=${error.errorType}, url=${_maskSensitiveUrl(error.url ?? '')}, '
|
||||||
|
'description=${error.description}',
|
||||||
|
);
|
||||||
|
}
|
||||||
// 可以在这里添加错误处理逻辑,比如显示错误页面
|
// 可以在这里添加错误处理逻辑,比如显示错误页面
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _maskSensitiveUrl(String url) {
|
||||||
|
final uri = Uri.tryParse(url);
|
||||||
|
if (uri == null) return url;
|
||||||
|
final params = Map<String, String>.from(uri.queryParameters);
|
||||||
|
if (params.containsKey('token')) {
|
||||||
|
params['token'] = '***';
|
||||||
|
}
|
||||||
|
return uri.replace(queryParameters: params).toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -311,6 +311,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
|
|
||||||
bool needUpDataUserInfo = false;
|
bool needUpDataUserInfo = false;
|
||||||
bool _roomVisualEffectsEnabled = false;
|
bool _roomVisualEffectsEnabled = false;
|
||||||
|
bool _voiceRoomRouteVisible = false;
|
||||||
bool _isExitingCurrentVoiceRoomSession = false;
|
bool _isExitingCurrentVoiceRoomSession = false;
|
||||||
bool _isHandlingAgoraRoomFailure = false;
|
bool _isHandlingAgoraRoomFailure = false;
|
||||||
bool _agoraJoined = false;
|
bool _agoraJoined = false;
|
||||||
@ -435,6 +436,8 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
|
|
||||||
bool get roomVisualEffectsEnabled => _roomVisualEffectsEnabled;
|
bool get roomVisualEffectsEnabled => _roomVisualEffectsEnabled;
|
||||||
|
|
||||||
|
bool get isVoiceRoomRouteVisible => _voiceRoomRouteVisible;
|
||||||
|
|
||||||
bool get isExitingCurrentVoiceRoomSession =>
|
bool get isExitingCurrentVoiceRoomSession =>
|
||||||
_isExitingCurrentVoiceRoomSession;
|
_isExitingCurrentVoiceRoomSession;
|
||||||
|
|
||||||
@ -479,6 +482,10 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setVoiceRoomRouteVisible(bool visible) {
|
||||||
|
_voiceRoomRouteVisible = visible;
|
||||||
|
}
|
||||||
|
|
||||||
void _setRoomStartupLoading() {
|
void _setRoomStartupLoading() {
|
||||||
_isHandlingRoomStartupFailure = false;
|
_isHandlingRoomStartupFailure = false;
|
||||||
_roomStartupFailureType = RoomStartupFailureType.none;
|
_roomStartupFailureType = RoomStartupFailureType.none;
|
||||||
@ -3383,6 +3390,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
redPacketList.clear();
|
redPacketList.clear();
|
||||||
currenRoom = null;
|
currenRoom = null;
|
||||||
_roomVisualEffectsEnabled = false;
|
_roomVisualEffectsEnabled = false;
|
||||||
|
_voiceRoomRouteVisible = false;
|
||||||
_isExitingCurrentVoiceRoomSession = false;
|
_isExitingCurrentVoiceRoomSession = false;
|
||||||
isMic = true;
|
isMic = true;
|
||||||
isMusicPlaying = false;
|
isMusicPlaying = false;
|
||||||
|
|||||||
@ -1697,6 +1697,7 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
|||||||
final currentRoomId =
|
final currentRoomId =
|
||||||
rtcProvider.currenRoom?.roomProfile?.roomProfile?.id?.trim() ?? '';
|
rtcProvider.currenRoom?.roomProfile?.roomProfile?.id?.trim() ?? '';
|
||||||
return rtcProvider.shouldShowRoomVisualEffects &&
|
return rtcProvider.shouldShowRoomVisualEffects &&
|
||||||
|
rtcProvider.isVoiceRoomRouteVisible &&
|
||||||
currentRoomId.isNotEmpty &&
|
currentRoomId.isNotEmpty &&
|
||||||
currentRoomId == normalizedRoomId;
|
currentRoomId == normalizedRoomId;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,7 +10,6 @@ import 'package:yumi/shared/tools/sc_version_utils.dart';
|
|||||||
import 'package:yumi/shared/data_sources/sources/local/data_persistence.dart';
|
import 'package:yumi/shared/data_sources/sources/local/data_persistence.dart';
|
||||||
import 'package:yumi/shared/data_sources/sources/local/user_manager.dart';
|
import 'package:yumi/shared/data_sources/sources/local/user_manager.dart';
|
||||||
import 'package:yumi/shared/data_sources/sources/repositories/sc_user_repository_impl.dart';
|
import 'package:yumi/shared/data_sources/sources/repositories/sc_user_repository_impl.dart';
|
||||||
import 'package:yumi/shared/business_logic/models/res/login_res.dart';
|
|
||||||
import 'package:yumi/ui_kit/components/sc_tts.dart';
|
import 'package:yumi/ui_kit/components/sc_tts.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:yumi/services/audio/rtc_manager.dart';
|
import 'package:yumi/services/audio/rtc_manager.dart';
|
||||||
@ -64,12 +63,22 @@ class SocialChatAuthenticationManager extends ChangeNotifier {
|
|||||||
SCLoadingManager.show();
|
SCLoadingManager.show();
|
||||||
if (uid.isNotEmpty) {
|
if (uid.isNotEmpty) {
|
||||||
await DataPersistence.setPendingChannelAuth(authType, uid);
|
await DataPersistence.setPendingChannelAuth(authType, uid);
|
||||||
final res = await Future.wait([
|
var user = await SCAccountRepository().loginForChannel(authType, uid);
|
||||||
SCAccountRepository().loginForChannel(authType, uid),
|
|
||||||
SCVersionUtils.checkReview(),
|
|
||||||
]);
|
|
||||||
var user = (res[0] as SocialChatLoginRes);
|
|
||||||
if (!context.mounted) {
|
if (!context.mounted) {
|
||||||
|
SCLoadingManager.hide();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
AccountStorage().setCurrentUser(user);
|
||||||
|
await DataPersistence.clearPendingChannelAuth();
|
||||||
|
if (!context.mounted) {
|
||||||
|
SCLoadingManager.hide();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final canContinue = await SCVersionUtils.checkReview(
|
||||||
|
context: context,
|
||||||
|
);
|
||||||
|
if (!context.mounted || !canContinue) {
|
||||||
|
SCLoadingManager.hide();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await Provider.of<RtcProvider>(
|
await Provider.of<RtcProvider>(
|
||||||
@ -84,11 +93,6 @@ class SocialChatAuthenticationManager extends ChangeNotifier {
|
|||||||
if (!context.mounted) {
|
if (!context.mounted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
AccountStorage().setCurrentUser(user);
|
|
||||||
await DataPersistence.clearPendingChannelAuth();
|
|
||||||
if (!context.mounted) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
SCLoadingManager.hide();
|
SCLoadingManager.hide();
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
SCNavigatorUtils.push(context, SCRoutes.home, replace: true);
|
SCNavigatorUtils.push(context, SCRoutes.home, replace: true);
|
||||||
@ -109,12 +113,22 @@ class SocialChatAuthenticationManager extends ChangeNotifier {
|
|||||||
SCLoadingManager.show();
|
SCLoadingManager.show();
|
||||||
if (uid.isNotEmpty) {
|
if (uid.isNotEmpty) {
|
||||||
await DataPersistence.setPendingChannelAuth(authType, uid);
|
await DataPersistence.setPendingChannelAuth(authType, uid);
|
||||||
final res = await Future.wait([
|
var user = await SCAccountRepository().loginForChannel(authType, uid);
|
||||||
SCAccountRepository().loginForChannel(authType, uid),
|
|
||||||
SCVersionUtils.checkReview(),
|
|
||||||
]);
|
|
||||||
var user = (res[0] as SocialChatLoginRes);
|
|
||||||
if (!context.mounted) {
|
if (!context.mounted) {
|
||||||
|
SCLoadingManager.hide();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
AccountStorage().setCurrentUser(user);
|
||||||
|
await DataPersistence.clearPendingChannelAuth();
|
||||||
|
if (!context.mounted) {
|
||||||
|
SCLoadingManager.hide();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final canContinue = await SCVersionUtils.checkReview(
|
||||||
|
context: context,
|
||||||
|
);
|
||||||
|
if (!context.mounted || !canContinue) {
|
||||||
|
SCLoadingManager.hide();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await Provider.of<RtcProvider>(
|
await Provider.of<RtcProvider>(
|
||||||
@ -129,11 +143,6 @@ class SocialChatAuthenticationManager extends ChangeNotifier {
|
|||||||
if (!context.mounted) {
|
if (!context.mounted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
AccountStorage().setCurrentUser(user);
|
|
||||||
await DataPersistence.clearPendingChannelAuth();
|
|
||||||
if (!context.mounted) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
SCLoadingManager.hide();
|
SCLoadingManager.hide();
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
SCNavigatorUtils.push(context, SCRoutes.home, replace: true);
|
SCNavigatorUtils.push(context, SCRoutes.home, replace: true);
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:yumi/app_localizations.dart';
|
import 'package:yumi/app_localizations.dart';
|
||||||
import 'package:yumi/ui_kit/components/sc_tts.dart';
|
import 'package:yumi/ui_kit/components/sc_tts.dart';
|
||||||
import 'package:yumi/shared/data_sources/sources/local/user_manager.dart';
|
import 'package:yumi/shared/data_sources/sources/local/user_manager.dart';
|
||||||
@ -95,8 +96,26 @@ class SocialChatUserProfileManager extends ChangeNotifier {
|
|||||||
SCUserIdentityRes? userIdentity;
|
SCUserIdentityRes? userIdentity;
|
||||||
|
|
||||||
void getUserIdentity() async {
|
void getUserIdentity() async {
|
||||||
userIdentity = await SCAccountRepository().userIdentity();
|
if (kDebugMode) {
|
||||||
notifyListeners();
|
final profile = AccountStorage().getCurrentUser()?.userProfile;
|
||||||
|
debugPrint(
|
||||||
|
'[SCUserIdentity] request start userId=${profile?.id}, account=${profile?.account}',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
userIdentity = await SCAccountRepository().userIdentity();
|
||||||
|
if (kDebugMode) {
|
||||||
|
debugPrint(
|
||||||
|
'[SCUserIdentity] manager stored admin=${userIdentity?.admin}, '
|
||||||
|
'manager=${userIdentity?.manager}, yumiManager=${userIdentity?.yumiManager}',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
notifyListeners();
|
||||||
|
} catch (error) {
|
||||||
|
if (kDebugMode) {
|
||||||
|
debugPrint('[SCUserIdentity] request failed error=$error');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 当前正在查看的个人详情资料,不用于承载登录用户自己的 Me 页数据。
|
/// 当前正在查看的个人详情资料,不用于承载登录用户自己的 Me 页数据。
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
/// agent : true
|
// agent : true
|
||||||
/// anchor : true
|
// anchor : true
|
||||||
/// bd : false
|
// bd : false
|
||||||
/// freightAgent : false
|
// freightAgent : false
|
||||||
|
|
||||||
class SCUserIdentityRes {
|
class SCUserIdentityRes {
|
||||||
SCUserIdentityRes({
|
SCUserIdentityRes({
|
||||||
@ -13,12 +13,14 @@ class SCUserIdentityRes {
|
|||||||
bool? bdLeader,
|
bool? bdLeader,
|
||||||
bool? superFreightAgent,
|
bool? superFreightAgent,
|
||||||
bool? manager,
|
bool? manager,
|
||||||
|
bool? yumiManager,
|
||||||
bool? freightAgent,}){
|
bool? freightAgent,}){
|
||||||
_agent = agent;
|
_agent = agent;
|
||||||
_superFreightAgent = superFreightAgent;
|
_superFreightAgent = superFreightAgent;
|
||||||
_anchor = anchor;
|
_anchor = anchor;
|
||||||
_bd = bd;
|
_bd = bd;
|
||||||
_manager = manager;
|
_manager = manager;
|
||||||
|
_yumiManager = yumiManager;
|
||||||
_admin = admin;
|
_admin = admin;
|
||||||
_superAdmin = superAdmin;
|
_superAdmin = superAdmin;
|
||||||
_bdLeader = bdLeader;
|
_bdLeader = bdLeader;
|
||||||
@ -35,6 +37,9 @@ class SCUserIdentityRes {
|
|||||||
_admin = json['admin'];
|
_admin = json['admin'];
|
||||||
_freightAgent = json['freightAgent'];
|
_freightAgent = json['freightAgent'];
|
||||||
_manager = json['manager'];
|
_manager = json['manager'];
|
||||||
|
_yumiManager = _boolValue(
|
||||||
|
json['yumi_manager'] ?? json['yumiManager'] ?? json['manager'],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
bool? _agent;
|
bool? _agent;
|
||||||
bool? _anchor;
|
bool? _anchor;
|
||||||
@ -44,13 +49,16 @@ class SCUserIdentityRes {
|
|||||||
bool? _bdLeader;
|
bool? _bdLeader;
|
||||||
bool? _freightAgent;
|
bool? _freightAgent;
|
||||||
bool? _manager;
|
bool? _manager;
|
||||||
|
bool? _yumiManager;
|
||||||
bool? _superFreightAgent;
|
bool? _superFreightAgent;
|
||||||
SCUserIdentityRes copyWith({ bool? agent,
|
SCUserIdentityRes copyWith({ bool? agent,
|
||||||
bool? anchor,
|
bool? anchor,
|
||||||
bool? bd,
|
bool? bd,
|
||||||
bool? admin,
|
bool? admin,
|
||||||
bool? superAdmin,
|
bool? superAdmin,
|
||||||
|
bool? bdLeader,
|
||||||
bool? manager,
|
bool? manager,
|
||||||
|
bool? yumiManager,
|
||||||
bool? superFreightAgent,
|
bool? superFreightAgent,
|
||||||
bool? freightAgent,
|
bool? freightAgent,
|
||||||
}) => SCUserIdentityRes( agent: agent ?? _agent,
|
}) => SCUserIdentityRes( agent: agent ?? _agent,
|
||||||
@ -60,6 +68,7 @@ SCUserIdentityRes copyWith({ bool? agent,
|
|||||||
bdLeader: bdLeader ?? _bdLeader,
|
bdLeader: bdLeader ?? _bdLeader,
|
||||||
admin: admin ?? _admin,
|
admin: admin ?? _admin,
|
||||||
manager: manager ?? _manager,
|
manager: manager ?? _manager,
|
||||||
|
yumiManager: yumiManager ?? _yumiManager,
|
||||||
superFreightAgent: superFreightAgent ?? _superFreightAgent,
|
superFreightAgent: superFreightAgent ?? _superFreightAgent,
|
||||||
freightAgent: freightAgent ?? _freightAgent,
|
freightAgent: freightAgent ?? _freightAgent,
|
||||||
);
|
);
|
||||||
@ -68,6 +77,7 @@ SCUserIdentityRes copyWith({ bool? agent,
|
|||||||
bool? get bd => _bd;
|
bool? get bd => _bd;
|
||||||
bool? get admin => _admin;
|
bool? get admin => _admin;
|
||||||
bool? get manager => _manager;
|
bool? get manager => _manager;
|
||||||
|
bool? get yumiManager => _yumiManager;
|
||||||
bool? get superAdmin => _superAdmin;
|
bool? get superAdmin => _superAdmin;
|
||||||
bool? get bdLeader => _bdLeader;
|
bool? get bdLeader => _bdLeader;
|
||||||
bool? get superFreightAgent => _superFreightAgent;
|
bool? get superFreightAgent => _superFreightAgent;
|
||||||
@ -79,6 +89,7 @@ SCUserIdentityRes copyWith({ bool? agent,
|
|||||||
map['anchor'] = _anchor;
|
map['anchor'] = _anchor;
|
||||||
map['bd'] = _bd;
|
map['bd'] = _bd;
|
||||||
map['manager'] = _manager;
|
map['manager'] = _manager;
|
||||||
|
map['yumi_manager'] = _yumiManager;
|
||||||
map['admin'] = _admin;
|
map['admin'] = _admin;
|
||||||
map['superAdmin'] = _superAdmin;
|
map['superAdmin'] = _superAdmin;
|
||||||
map['bdLeader'] = _bdLeader;
|
map['bdLeader'] = _bdLeader;
|
||||||
@ -87,4 +98,25 @@ SCUserIdentityRes copyWith({ bool? agent,
|
|||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
static bool? _boolValue(dynamic value) {
|
||||||
|
if (value == null || value is bool) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
if (value is num) {
|
||||||
|
return value != 0;
|
||||||
|
}
|
||||||
|
if (value is String) {
|
||||||
|
final normalized = value.trim().toLowerCase();
|
||||||
|
if (normalized.isEmpty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (normalized == 'true' || normalized == '1' || normalized == 'yes') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (normalized == 'false' || normalized == '0' || normalized == 'no') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -470,15 +470,23 @@ class OverlayManager {
|
|||||||
_isProcessing = false;
|
_isProcessing = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void removeRoom() {
|
void removeRoom({String? roomId}) {
|
||||||
_removeActiveRoomMessage();
|
final normalizedRoomId = roomId?.trim() ?? '';
|
||||||
|
_removeActiveRoomMessage(
|
||||||
|
roomId: normalizedRoomId,
|
||||||
|
includeRedPacket: normalizedRoomId.isNotEmpty,
|
||||||
|
);
|
||||||
_removeMessagesByType(1);
|
_removeMessagesByType(1);
|
||||||
_removeMessagesByType(0);
|
_removeMessagesByType(0);
|
||||||
_removeMessagesByType(5);
|
_removeMessagesByType(5);
|
||||||
|
if (normalizedRoomId.isNotEmpty) {
|
||||||
|
_removeMessagesByType(4, roomId: normalizedRoomId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 辅助方法:移除特定类型的消息
|
// 辅助方法:移除特定类型的消息
|
||||||
void _removeMessagesByType(int type) {
|
void _removeMessagesByType(int type, {String? roomId}) {
|
||||||
|
final normalizedRoomId = roomId?.trim() ?? '';
|
||||||
// 由于 PriorityQueue 没有直接的方法来移除特定元素,
|
// 由于 PriorityQueue 没有直接的方法来移除特定元素,
|
||||||
// 我们需要重建队列,排除指定类型的消息
|
// 我们需要重建队列,排除指定类型的消息
|
||||||
final newQueue = SCPriorityQueue<SCFloatingMessage>(
|
final newQueue = SCPriorityQueue<SCFloatingMessage>(
|
||||||
@ -488,7 +496,11 @@ class OverlayManager {
|
|||||||
// 遍历原始队列,只添加非指定类型的消息
|
// 遍历原始队列,只添加非指定类型的消息
|
||||||
while (_messageQueue.isNotEmpty) {
|
while (_messageQueue.isNotEmpty) {
|
||||||
final message = _messageQueue.removeFirst();
|
final message = _messageQueue.removeFirst();
|
||||||
if (message.type != type) {
|
final matchesType = message.type == type;
|
||||||
|
final matchesRoom =
|
||||||
|
normalizedRoomId.isEmpty ||
|
||||||
|
(message.roomId ?? "").trim() == normalizedRoomId;
|
||||||
|
if (!matchesType || !matchesRoom) {
|
||||||
newQueue.add(message);
|
newQueue.add(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -605,7 +617,8 @@ class OverlayManager {
|
|||||||
context,
|
context,
|
||||||
listen: false,
|
listen: false,
|
||||||
);
|
);
|
||||||
if (!rtcProvider.shouldShowRoomVisualEffects) {
|
if (!rtcProvider.shouldShowRoomVisualEffects ||
|
||||||
|
!rtcProvider.isVoiceRoomRouteVisible) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
final currentRoomId =
|
final currentRoomId =
|
||||||
@ -633,6 +646,7 @@ class OverlayManager {
|
|||||||
final messageRoomId = (message.roomId ?? "").trim();
|
final messageRoomId = (message.roomId ?? "").trim();
|
||||||
final isCurrentVisibleRoom =
|
final isCurrentVisibleRoom =
|
||||||
rtcProvider.shouldShowRoomVisualEffects &&
|
rtcProvider.shouldShowRoomVisualEffects &&
|
||||||
|
rtcProvider.isVoiceRoomRouteVisible &&
|
||||||
currentRoomId.isNotEmpty &&
|
currentRoomId.isNotEmpty &&
|
||||||
messageRoomId.isNotEmpty &&
|
messageRoomId.isNotEmpty &&
|
||||||
currentRoomId == messageRoomId;
|
currentRoomId == messageRoomId;
|
||||||
@ -640,6 +654,13 @@ class OverlayManager {
|
|||||||
// 区域红包在当前房间仍然需要展示;区域礼物当前房间由 RTM 层跳过,避免和房间 IM 礼物飘屏重复。
|
// 区域红包在当前房间仍然需要展示;区域礼物当前房间由 RTM 层跳过,避免和房间 IM 礼物飘屏重复。
|
||||||
return message.type == 4;
|
return message.type == 4;
|
||||||
}
|
}
|
||||||
|
final isCurrentRoomMessage =
|
||||||
|
currentRoomId.isNotEmpty &&
|
||||||
|
messageRoomId.isNotEmpty &&
|
||||||
|
currentRoomId == messageRoomId;
|
||||||
|
if (message.type == 4 && isCurrentRoomMessage) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (message.type == 4 && rtcProvider.shouldShowRoomVisualEffects) {
|
if (message.type == 4 && rtcProvider.shouldShowRoomVisualEffects) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -684,12 +705,21 @@ class OverlayManager {
|
|||||||
_safeScheduleNext();
|
_safeScheduleNext();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _removeActiveRoomMessage() {
|
void _removeActiveRoomMessage({
|
||||||
|
String? roomId,
|
||||||
|
bool includeRedPacket = false,
|
||||||
|
}) {
|
||||||
final activeMessage = _currentMessage;
|
final activeMessage = _currentMessage;
|
||||||
|
final normalizedRoomId = roomId?.trim() ?? '';
|
||||||
if (activeMessage == null ||
|
if (activeMessage == null ||
|
||||||
(activeMessage.type != 0 &&
|
(activeMessage.type != 0 &&
|
||||||
activeMessage.type != 1 &&
|
activeMessage.type != 1 &&
|
||||||
activeMessage.type != 5)) {
|
activeMessage.type != 5 &&
|
||||||
|
!(includeRedPacket && activeMessage.type == 4))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (normalizedRoomId.isNotEmpty &&
|
||||||
|
(activeMessage.roomId ?? "").trim() != normalizedRoomId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_removeActiveMessage();
|
_removeActiveMessage();
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:yumi/app/constants/sc_global_config.dart';
|
import 'package:yumi/app/constants/sc_global_config.dart';
|
||||||
import 'package:yumi/shared/business_logic/models/res/sc_entry_popup_res.dart';
|
import 'package:yumi/shared/business_logic/models/res/sc_entry_popup_res.dart';
|
||||||
import 'package:yumi/shared/business_logic/models/res/sc_google_pay_res.dart';
|
import 'package:yumi/shared/business_logic/models/res/sc_google_pay_res.dart';
|
||||||
@ -296,7 +298,10 @@ class SCConfigRepositoryImp implements SocialChatConfigRepository {
|
|||||||
Future<SCVersionManageLatestRes> versionManageLatest() async {
|
Future<SCVersionManageLatestRes> versionManageLatest() async {
|
||||||
final result = await http.get<SCVersionManageLatestRes>(
|
final result = await http.get<SCVersionManageLatestRes>(
|
||||||
"ee9584f714ded864780e47dab2cf4a2e84ac21c90fcd0966a13d2ce9e8845eb8e580afbe66f9f0fef79429cd5c1e0687",
|
"ee9584f714ded864780e47dab2cf4a2e84ac21c90fcd0966a13d2ce9e8845eb8e580afbe66f9f0fef79429cd5c1e0687",
|
||||||
fromJson: (json) => SCVersionManageLatestRes.fromJson(json),
|
fromJson: (json) {
|
||||||
|
_debugVersionResponse('releaseLatest', json);
|
||||||
|
return SCVersionManageLatestRes.fromJson(json);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -306,11 +311,22 @@ class SCConfigRepositoryImp implements SocialChatConfigRepository {
|
|||||||
Future<VersionManageLatesReviewRes> versionManageLatestReview() async {
|
Future<VersionManageLatesReviewRes> versionManageLatestReview() async {
|
||||||
final result = await http.get<VersionManageLatesReviewRes>(
|
final result = await http.get<VersionManageLatesReviewRes>(
|
||||||
"ee9584f714ded864780e47dab2cf4a2e11ce42bdd061186d4efe3305b73f10fe574aff257ce7e668d08f4caccd1c6232",
|
"ee9584f714ded864780e47dab2cf4a2e11ce42bdd061186d4efe3305b73f10fe574aff257ce7e668d08f4caccd1c6232",
|
||||||
fromJson: (json) => VersionManageLatesReviewRes.fromJson(json),
|
fromJson: (json) {
|
||||||
|
_debugVersionResponse('latestReview', json);
|
||||||
|
return VersionManageLatesReviewRes.fromJson(json);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _debugVersionResponse(String name, dynamic json) {
|
||||||
|
try {
|
||||||
|
debugPrint('[SCVersionUtils] $name rawBody=${jsonEncode(json)}');
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('[SCVersionUtils] $name rawBody=$json encodeError=$e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
///sys/config/customer-service
|
///sys/config/customer-service
|
||||||
@override
|
@override
|
||||||
Future<SocialChatUserProfile> customerService() async {
|
Future<SocialChatUserProfile> customerService() async {
|
||||||
|
|||||||
@ -607,7 +607,20 @@ class SCAccountRepository implements SocialChatUserRepository {
|
|||||||
final result = await http.get<SCUserIdentityRes>(
|
final result = await http.get<SCUserIdentityRes>(
|
||||||
"d29121f09fa26c45b5322df6de0fc25b",
|
"d29121f09fa26c45b5322df6de0fc25b",
|
||||||
queryParams: parm,
|
queryParams: parm,
|
||||||
fromJson: (json) => SCUserIdentityRes.fromJson(json),
|
fromJson: (json) {
|
||||||
|
final identity = SCUserIdentityRes.fromJson(json);
|
||||||
|
if (kDebugMode) {
|
||||||
|
debugPrint('[SCUserIdentity] raw=${jsonEncode(json)}');
|
||||||
|
debugPrint(
|
||||||
|
'[SCUserIdentity] parsed admin=${identity.admin}, '
|
||||||
|
'manager=${identity.manager}, yumiManager=${identity.yumiManager}, '
|
||||||
|
'raw_manager=${json['manager']}(${json['manager']?.runtimeType}), '
|
||||||
|
'raw_yumi_manager=${json['yumi_manager']}(${json['yumi_manager']?.runtimeType}), '
|
||||||
|
'raw_yumiManager=${json['yumiManager']}(${json['yumiManager']?.runtimeType})',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return identity;
|
||||||
|
},
|
||||||
);
|
);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,8 +23,7 @@ class SCGiftVapSvgaManager {
|
|||||||
static const int _maxConsecutiveEntryTasksBeforeGift = 2;
|
static const int _maxConsecutiveEntryTasksBeforeGift = 2;
|
||||||
static const Duration _taskWatchdogTimeout = Duration(seconds: 20);
|
static const Duration _taskWatchdogTimeout = Duration(seconds: 20);
|
||||||
static const Duration _entryTaskTtl = Duration(seconds: 6);
|
static const Duration _entryTaskTtl = Duration(seconds: 6);
|
||||||
final Map<String, MovieEntity> _videoItemCache = <String, MovieEntity>{};
|
Map<String, MovieEntity> videoItemCache = {};
|
||||||
final Map<MovieEntity, int> _svgaEntityUseCounts = <MovieEntity, int>{};
|
|
||||||
static SCGiftVapSvgaManager? _inst;
|
static SCGiftVapSvgaManager? _inst;
|
||||||
static const int _maxPreloadConcurrency = 1;
|
static const int _maxPreloadConcurrency = 1;
|
||||||
static const int _maxPreloadQueueLength = 12;
|
static const int _maxPreloadQueueLength = 12;
|
||||||
@ -80,44 +79,6 @@ class SCGiftVapSvgaManager {
|
|||||||
return SCPathUtils.getFileExtension(path).toLowerCase() == ".svga";
|
return SCPathUtils.getFileExtension(path).toLowerCase() == ".svga";
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<MovieEntity> acquireSvgaEntity(String path) async {
|
|
||||||
final entity = await _loadSvgaEntity(path);
|
|
||||||
_retainSvgaEntity(entity);
|
|
||||||
return entity;
|
|
||||||
}
|
|
||||||
|
|
||||||
void releaseSvgaEntity(MovieEntity? entity) {
|
|
||||||
if (entity == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final count = _svgaEntityUseCounts[entity] ?? 0;
|
|
||||||
if (count <= 1) {
|
|
||||||
_svgaEntityUseCounts.remove(entity);
|
|
||||||
if (!_videoItemCache.containsValue(entity) &&
|
|
||||||
!_isSvgaEntityInUse(entity)) {
|
|
||||||
_disposeSvgaEntity(entity);
|
|
||||||
} else {
|
|
||||||
_trimSvgaCache();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_svgaEntityUseCounts[entity] = count - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void evictSvgaEntity(String path, {bool includeCurrent = false}) {
|
|
||||||
final entity = _videoItemCache.remove(path);
|
|
||||||
if (entity == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (includeCurrent || !_isSvgaEntityInUse(entity)) {
|
|
||||||
_disposeSvgaEntity(entity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _retainSvgaEntity(MovieEntity entity) {
|
|
||||||
_svgaEntityUseCounts[entity] = (_svgaEntityUseCounts[entity] ?? 0) + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int resolveEffectPriority({
|
static int resolveEffectPriority({
|
||||||
int priority = 0,
|
int priority = 0,
|
||||||
int type = giftEffectType,
|
int type = giftEffectType,
|
||||||
@ -155,7 +116,7 @@ class SCGiftVapSvgaManager {
|
|||||||
|
|
||||||
bool _isPreloadedOrLoading(String path) {
|
bool _isPreloadedOrLoading(String path) {
|
||||||
if (_needsSvgaController(path)) {
|
if (_needsSvgaController(path)) {
|
||||||
return _videoItemCache.containsKey(path) ||
|
return videoItemCache.containsKey(path) ||
|
||||||
_svgaLoadTasks.containsKey(path);
|
_svgaLoadTasks.containsKey(path);
|
||||||
}
|
}
|
||||||
final pathType = SCPathUtils.getPathType(path);
|
final pathType = SCPathUtils.getPathType(path);
|
||||||
@ -295,19 +256,19 @@ class SCGiftVapSvgaManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
MovieEntity? _touchCachedSvgaEntity(String path) {
|
MovieEntity? _touchCachedSvgaEntity(String path) {
|
||||||
final cached = _videoItemCache.remove(path);
|
final cached = videoItemCache.remove(path);
|
||||||
if (cached != null) {
|
if (cached != null) {
|
||||||
_videoItemCache[path] = cached;
|
videoItemCache[path] = cached;
|
||||||
}
|
}
|
||||||
return cached;
|
return cached;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _cacheSvgaEntity(String path, MovieEntity entity) {
|
void _cacheSvgaEntity(String path, MovieEntity entity) {
|
||||||
final previous = _videoItemCache.remove(path);
|
final previous = videoItemCache.remove(path);
|
||||||
if (previous != null && !identical(previous, entity)) {
|
if (previous != null && !identical(previous, entity)) {
|
||||||
_disposeSvgaEntityIfUnused(previous);
|
_disposeSvgaEntityIfUnused(previous);
|
||||||
}
|
}
|
||||||
_videoItemCache[path] = entity;
|
videoItemCache[path] = entity;
|
||||||
_trimSvgaCache();
|
_trimSvgaCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -326,17 +287,17 @@ class SCGiftVapSvgaManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _trimSvgaCache() {
|
void _trimSvgaCache() {
|
||||||
while (_videoItemCache.length > _maxSvgaCacheEntries) {
|
while (videoItemCache.length > _maxSvgaCacheEntries) {
|
||||||
final removableEntry = _videoItemCache.entries.firstWhere(
|
final removableEntry = videoItemCache.entries.firstWhere(
|
||||||
(entry) => !_isSvgaEntityInUse(entry.value),
|
(entry) => !_isSvgaEntityInUse(entry.value),
|
||||||
orElse: () => _videoItemCache.entries.first,
|
orElse: () => videoItemCache.entries.first,
|
||||||
);
|
);
|
||||||
if (_isSvgaEntityInUse(removableEntry.value)) {
|
if (_isSvgaEntityInUse(removableEntry.value)) {
|
||||||
_videoItemCache.remove(removableEntry.key);
|
videoItemCache.remove(removableEntry.key);
|
||||||
_videoItemCache[removableEntry.key] = removableEntry.value;
|
videoItemCache[removableEntry.key] = removableEntry.value;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
_videoItemCache.remove(removableEntry.key);
|
videoItemCache.remove(removableEntry.key);
|
||||||
_disposeSvgaEntity(removableEntry.value);
|
_disposeSvgaEntity(removableEntry.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -791,21 +752,20 @@ class SCGiftVapSvgaManager {
|
|||||||
_preloadQueue.clear();
|
_preloadQueue.clear();
|
||||||
_queuedPreloadPaths.clear();
|
_queuedPreloadPaths.clear();
|
||||||
_activePreloadCount = 0;
|
_activePreloadCount = 0;
|
||||||
final entries = _videoItemCache.entries.toList(growable: false);
|
final entries = videoItemCache.entries.toList(growable: false);
|
||||||
for (final entry in entries) {
|
for (final entry in entries) {
|
||||||
final entity = entry.value;
|
final entity = entry.value;
|
||||||
if (!includeCurrent && _isSvgaEntityInUse(entity)) {
|
if (!includeCurrent && _isSvgaEntityInUse(entity)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
_videoItemCache.remove(entry.key);
|
videoItemCache.remove(entry.key);
|
||||||
_disposeSvgaEntity(entity);
|
_disposeSvgaEntity(entity);
|
||||||
}
|
}
|
||||||
_playablePathCache.clear();
|
_playablePathCache.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _isSvgaEntityInUse(MovieEntity entity) {
|
bool _isSvgaEntityInUse(MovieEntity entity) {
|
||||||
return identical(_rsc?.videoItem, entity) ||
|
return identical(_rsc?.videoItem, entity);
|
||||||
(_svgaEntityUseCounts[entity] ?? 0) > 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _disposeSvgaEntityIfUnused(MovieEntity entity) {
|
void _disposeSvgaEntityIfUnused(MovieEntity entity) {
|
||||||
@ -817,7 +777,6 @@ class SCGiftVapSvgaManager {
|
|||||||
|
|
||||||
void _disposeSvgaEntity(MovieEntity entity) {
|
void _disposeSvgaEntity(MovieEntity entity) {
|
||||||
try {
|
try {
|
||||||
_svgaEntityUseCounts.remove(entity);
|
|
||||||
entity.dispose();
|
entity.dispose();
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,14 +1,599 @@
|
|||||||
import 'package:yumi/shared/data_sources/sources/repositories/sc_config_repository_imp.dart';
|
import 'dart:async';
|
||||||
import 'package:yumi/app/constants/sc_global_config.dart';
|
import 'dart:io';
|
||||||
|
|
||||||
class SCVersionUtils {
|
import 'package:flutter/material.dart';
|
||||||
static Future<void> checkReview() async {
|
import 'package:yumi/app/constants/sc_global_config.dart';
|
||||||
try {
|
import 'package:yumi/shared/business_logic/models/res/sc_version_manage_latest_res.dart';
|
||||||
var versionInfo = await SCConfigRepositoryImp().versionManageLatestReview();
|
import 'package:yumi/shared/data_sources/sources/repositories/sc_config_repository_imp.dart';
|
||||||
if (versionInfo.review != null) {
|
import 'package:yumi/shared/tools/sc_loading_manager.dart';
|
||||||
SCGlobalConfig.isReview =
|
import 'package:yumi/shared/tools/sc_url_launcher_utils.dart';
|
||||||
"${versionInfo.review?.version}" == SCGlobalConfig.version;
|
|
||||||
}
|
class SCVersionUtils {
|
||||||
} catch (e) {}
|
static bool _forceUpdateDialogShowing = false;
|
||||||
}
|
|
||||||
}
|
static Future<bool> checkReview({BuildContext? context}) async {
|
||||||
|
_debugLog(
|
||||||
|
'checkReview start '
|
||||||
|
'currentVersion=${SCGlobalConfig.version} '
|
||||||
|
'currentBuild=${SCGlobalConfig.build} '
|
||||||
|
'platform=${Platform.isIOS ? "ios" : "android"} '
|
||||||
|
'apiHost=${SCGlobalConfig.apiHost} '
|
||||||
|
'hasContext=${context != null} '
|
||||||
|
'contextMounted=${context?.mounted}',
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
var versionInfo =
|
||||||
|
await SCConfigRepositoryImp().versionManageLatestReview();
|
||||||
|
_debugLog(
|
||||||
|
'checkReview response '
|
||||||
|
'review=${_versionInfoDebug(versionInfo.review)} '
|
||||||
|
'latest=${_versionInfoDebug(versionInfo.latest)}',
|
||||||
|
);
|
||||||
|
if (versionInfo.review != null) {
|
||||||
|
SCGlobalConfig.isReview =
|
||||||
|
"${versionInfo.review?.version}" == SCGlobalConfig.version;
|
||||||
|
_debugLog('checkReview isReview=${SCGlobalConfig.isReview}');
|
||||||
|
}
|
||||||
|
final latest = await _resolveLatestVersion(versionInfo.latest);
|
||||||
|
final shouldForceUpdate = latest != null && _shouldForceUpdate(latest);
|
||||||
|
_debugLog(
|
||||||
|
'checkReview forceDecision '
|
||||||
|
'shouldForceUpdate=$shouldForceUpdate '
|
||||||
|
'reason=${_forceUpdateDecisionDebug(latest)}',
|
||||||
|
);
|
||||||
|
if (context != null &&
|
||||||
|
context.mounted &&
|
||||||
|
latest != null &&
|
||||||
|
shouldForceUpdate) {
|
||||||
|
SCLoadingManager.hide();
|
||||||
|
_debugLog('checkReview showForceUpdateDialog');
|
||||||
|
_showForceUpdateDialog(context, latest);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (shouldForceUpdate && context == null) {
|
||||||
|
_debugLog('checkReview skip dialog reason=no_context');
|
||||||
|
} else if (shouldForceUpdate &&
|
||||||
|
context != null &&
|
||||||
|
context.mounted == false) {
|
||||||
|
_debugLog('checkReview skip dialog reason=context_not_mounted');
|
||||||
|
} else {
|
||||||
|
_debugLog('checkReview no force dialog');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
_debugLog('checkReview request failed error=$e');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool _shouldForceUpdate(SCVersionManageLatestRes latest) {
|
||||||
|
if (latest.forceUpdate != true) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final latestBuild = latest.buildVersion?.toInt() ?? 0;
|
||||||
|
final currentBuild = _currentComparableBuild();
|
||||||
|
final hasLatestVersion = (latest.version ?? '').trim().isNotEmpty;
|
||||||
|
final versionCompare = _compareVersion(
|
||||||
|
latest.version,
|
||||||
|
SCGlobalConfig.version,
|
||||||
|
);
|
||||||
|
if (hasLatestVersion) {
|
||||||
|
if (versionCompare > 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (versionCompare < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (latestBuild > 0 && currentBuild > 0) {
|
||||||
|
return latestBuild > currentBuild;
|
||||||
|
}
|
||||||
|
|
||||||
|
return hasLatestVersion && versionCompare > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<SCVersionManageLatestRes?> _resolveLatestVersion(
|
||||||
|
SCVersionManageLatestRes? latest,
|
||||||
|
) async {
|
||||||
|
if (!_isVersionInfoEmpty(latest)) {
|
||||||
|
return latest;
|
||||||
|
}
|
||||||
|
_debugLog('checkReview latestReview latest empty, fallback releaseLatest');
|
||||||
|
try {
|
||||||
|
final fallback = await SCConfigRepositoryImp().versionManageLatest();
|
||||||
|
_debugLog('checkReview fallbackLatest=${_versionInfoDebug(fallback)}');
|
||||||
|
return _isVersionInfoEmpty(fallback) ? latest : fallback;
|
||||||
|
} catch (e) {
|
||||||
|
_debugLog('checkReview fallback releaseLatest failed error=$e');
|
||||||
|
return latest;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool _isVersionInfoEmpty(SCVersionManageLatestRes? version) {
|
||||||
|
if (version == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return (version.id ?? '').trim().isEmpty &&
|
||||||
|
(version.platform ?? '').trim().isEmpty &&
|
||||||
|
(version.version ?? '').trim().isEmpty &&
|
||||||
|
version.forceUpdate == null &&
|
||||||
|
(version.appType ?? '').trim().isEmpty &&
|
||||||
|
version.review == null &&
|
||||||
|
version.patch == null &&
|
||||||
|
(version.updateDescribe ?? '').trim().isEmpty &&
|
||||||
|
(version.updateWorshipDescribe ?? '').trim().isEmpty &&
|
||||||
|
(version.downloadUrl ?? '').trim().isEmpty &&
|
||||||
|
version.buildVersion == null &&
|
||||||
|
version.createTime == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int _compareVersion(String? latestVersion, String currentVersion) {
|
||||||
|
final latestParts = _versionParts(latestVersion);
|
||||||
|
final currentParts = _versionParts(currentVersion);
|
||||||
|
final maxLength =
|
||||||
|
latestParts.length > currentParts.length
|
||||||
|
? latestParts.length
|
||||||
|
: currentParts.length;
|
||||||
|
for (var i = 0; i < maxLength; i++) {
|
||||||
|
final latestPart = i < latestParts.length ? latestParts[i] : 0;
|
||||||
|
final currentPart = i < currentParts.length ? currentParts[i] : 0;
|
||||||
|
if (latestPart != currentPart) {
|
||||||
|
return latestPart.compareTo(currentPart);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<int> _versionParts(String? version) {
|
||||||
|
final normalized = version?.trim() ?? "";
|
||||||
|
if (normalized.isEmpty) {
|
||||||
|
return const <int>[];
|
||||||
|
}
|
||||||
|
return normalized
|
||||||
|
.split(RegExp(r'[.+\-_]'))
|
||||||
|
.map(
|
||||||
|
(part) => int.tryParse(RegExp(r'^\d+').stringMatch(part) ?? '0') ?? 0,
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
static int _currentRawBuild() {
|
||||||
|
return int.tryParse(SCGlobalConfig.build.trim()) ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int _currentComparableBuild() {
|
||||||
|
final rawBuild = _currentRawBuild();
|
||||||
|
if (!Platform.isAndroid || rawBuild < 1000) {
|
||||||
|
return rawBuild;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flutter split-per-abi APKs offset Android versionCode by ABI, e.g.
|
||||||
|
// build 132 becomes 2132 for arm64-v8a. The backend stores the base build.
|
||||||
|
final baseBuild = rawBuild % 1000;
|
||||||
|
return baseBuild > 0 ? baseBuild : rawBuild;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String _currentBuildDebug() {
|
||||||
|
final rawBuild = _currentRawBuild();
|
||||||
|
final comparableBuild = _currentComparableBuild();
|
||||||
|
if (rawBuild == comparableBuild) {
|
||||||
|
return '$rawBuild';
|
||||||
|
}
|
||||||
|
return '$comparableBuild(raw=$rawBuild)';
|
||||||
|
}
|
||||||
|
|
||||||
|
static String _forceUpdateDecisionDebug(SCVersionManageLatestRes? latest) {
|
||||||
|
if (latest == null) {
|
||||||
|
return 'latest_null';
|
||||||
|
}
|
||||||
|
final latestBuild = latest.buildVersion?.toInt() ?? 0;
|
||||||
|
final currentBuild = _currentComparableBuild();
|
||||||
|
final currentBuildDebug = _currentBuildDebug();
|
||||||
|
final versionCompare = _compareVersion(
|
||||||
|
latest.version,
|
||||||
|
SCGlobalConfig.version,
|
||||||
|
);
|
||||||
|
if (latest.forceUpdate != true) {
|
||||||
|
return 'forceUpdate_not_true '
|
||||||
|
'forceUpdate=${latest.forceUpdate} '
|
||||||
|
'latestBuild=$latestBuild currentBuild=$currentBuildDebug '
|
||||||
|
'versionCompare=$versionCompare';
|
||||||
|
}
|
||||||
|
if (latestBuild > 0 && currentBuild > 0) {
|
||||||
|
return 'compare_build '
|
||||||
|
'latestBuild=$latestBuild currentBuild=$currentBuildDebug '
|
||||||
|
'versionCompare=$versionCompare';
|
||||||
|
}
|
||||||
|
return 'compare_version '
|
||||||
|
'latestVersion=${latest.version} '
|
||||||
|
'currentVersion=${SCGlobalConfig.version} '
|
||||||
|
'versionCompare=$versionCompare '
|
||||||
|
'latestBuild=$latestBuild currentBuild=$currentBuildDebug';
|
||||||
|
}
|
||||||
|
|
||||||
|
static String _versionInfoDebug(SCVersionManageLatestRes? version) {
|
||||||
|
if (version == null) {
|
||||||
|
return 'null';
|
||||||
|
}
|
||||||
|
return '{'
|
||||||
|
'id=${version.id}, '
|
||||||
|
'platform=${version.platform}, '
|
||||||
|
'version=${version.version}, '
|
||||||
|
'buildVersion=${version.buildVersion}, '
|
||||||
|
'forceUpdate=${version.forceUpdate}, '
|
||||||
|
'review=${version.review}, '
|
||||||
|
'patch=${version.patch}, '
|
||||||
|
'downloadUrl=${version.downloadUrl}'
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _debugLog(String message) {
|
||||||
|
debugPrint('[SCVersionUtils] $message');
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _showForceUpdateDialog(
|
||||||
|
BuildContext context,
|
||||||
|
SCVersionManageLatestRes latest,
|
||||||
|
) {
|
||||||
|
if (_forceUpdateDialogShowing || !context.mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_forceUpdateDialogShowing = true;
|
||||||
|
unawaited(
|
||||||
|
showDialog<void>(
|
||||||
|
context: context,
|
||||||
|
barrierDismissible: false,
|
||||||
|
barrierColor: Colors.black.withValues(alpha: 0.62),
|
||||||
|
builder: (dialogContext) {
|
||||||
|
final version = (latest.version ?? "").trim();
|
||||||
|
final updateDescribe = (latest.updateDescribe ?? "").trim();
|
||||||
|
final message =
|
||||||
|
updateDescribe.isNotEmpty
|
||||||
|
? updateDescribe
|
||||||
|
: "A newer version is required to keep voice rooms stable "
|
||||||
|
"and secure.";
|
||||||
|
return PopScope(
|
||||||
|
canPop: false,
|
||||||
|
child: Dialog(
|
||||||
|
elevation: 0,
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
insetPadding: const EdgeInsets.symmetric(horizontal: 28),
|
||||||
|
child: SCForceUpdateDialog(
|
||||||
|
version: version,
|
||||||
|
message: message,
|
||||||
|
onUpdate: () {
|
||||||
|
unawaited(_openUpdateUrl(latest));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
).whenComplete(() {
|
||||||
|
_forceUpdateDialogShowing = false;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> _openUpdateUrl(SCVersionManageLatestRes latest) async {
|
||||||
|
final url = _resolveDownloadUrl(latest);
|
||||||
|
if (url.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await SCUrlLauncherUtils.launchExternal(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
static String _resolveDownloadUrl(SCVersionManageLatestRes latest) {
|
||||||
|
final downloadUrl = (latest.downloadUrl ?? "").trim();
|
||||||
|
if (downloadUrl.isNotEmpty) {
|
||||||
|
return downloadUrl;
|
||||||
|
}
|
||||||
|
return Platform.isIOS
|
||||||
|
? SCGlobalConfig.appDownloadUrlApple
|
||||||
|
: SCGlobalConfig.appDownloadUrlGoogle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SCForceUpdateDialog extends StatelessWidget {
|
||||||
|
const SCForceUpdateDialog({
|
||||||
|
super.key,
|
||||||
|
required this.version,
|
||||||
|
required this.message,
|
||||||
|
required this.onUpdate,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String version;
|
||||||
|
final String message;
|
||||||
|
final VoidCallback onUpdate;
|
||||||
|
|
||||||
|
static const _asset = 'sc_images/general/sc_icon_app_update_bg.png';
|
||||||
|
static const _primary = Color(0xff18F2B1);
|
||||||
|
static const _primaryDark = Color(0xff10C894);
|
||||||
|
static const _titleColor = Color(0xff151823);
|
||||||
|
static const _bodyColor = Color(0xff626775);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final constrainedTextScaler = MediaQuery.textScalerOf(
|
||||||
|
context,
|
||||||
|
).clamp(maxScaleFactor: 1.15);
|
||||||
|
|
||||||
|
return MediaQuery(
|
||||||
|
data: MediaQuery.of(context).copyWith(textScaler: constrainedTextScaler),
|
||||||
|
child: LayoutBuilder(
|
||||||
|
builder: (context, constraints) {
|
||||||
|
final width =
|
||||||
|
constraints.maxWidth.isFinite
|
||||||
|
? constraints.maxWidth.clamp(280.0, 340.0)
|
||||||
|
: 340.0;
|
||||||
|
|
||||||
|
return Center(
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: BoxConstraints.tightFor(width: width),
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(28),
|
||||||
|
child: DecoratedBox(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withValues(alpha: 0.18),
|
||||||
|
blurRadius: 34,
|
||||||
|
offset: const Offset(0, 18),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
height: 164,
|
||||||
|
width: double.infinity,
|
||||||
|
child: Image.asset(
|
||||||
|
_asset,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
alignment: Alignment.topCenter,
|
||||||
|
frameBuilder: (
|
||||||
|
context,
|
||||||
|
child,
|
||||||
|
frame,
|
||||||
|
wasSynchronouslyLoaded,
|
||||||
|
) {
|
||||||
|
if (wasSynchronouslyLoaded || frame != null) {
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
return const _UpdateHeroFallback();
|
||||||
|
},
|
||||||
|
errorBuilder: (context, error, stackTrace) {
|
||||||
|
return const _UpdateHeroFallback();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(24, 0, 24, 24),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
_VersionPill(version: version),
|
||||||
|
const SizedBox(height: 14),
|
||||||
|
const Text(
|
||||||
|
'Update Required',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
color: _titleColor,
|
||||||
|
fontSize: 22,
|
||||||
|
fontWeight: FontWeight.w800,
|
||||||
|
height: 1.16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Text(
|
||||||
|
message,
|
||||||
|
maxLines: 3,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: _bodyColor,
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
height: 1.45,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 18),
|
||||||
|
const _UpdateNotice(),
|
||||||
|
const SizedBox(height: 22),
|
||||||
|
_UpdateButton(onPressed: onUpdate),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _UpdateHeroFallback extends StatelessWidget {
|
||||||
|
const _UpdateHeroFallback();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return DecoratedBox(
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: Alignment.bottomRight,
|
||||||
|
colors: [Color(0xff14D6A1), Color(0xff65F3CD)],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
Positioned(
|
||||||
|
left: 34,
|
||||||
|
top: 26,
|
||||||
|
child: Icon(
|
||||||
|
Icons.auto_awesome_rounded,
|
||||||
|
color: Colors.white.withValues(alpha: 0.72),
|
||||||
|
size: 18,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
right: 44,
|
||||||
|
top: 36,
|
||||||
|
child: Icon(
|
||||||
|
Icons.auto_awesome_rounded,
|
||||||
|
color: Colors.white.withValues(alpha: 0.58),
|
||||||
|
size: 14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Center(
|
||||||
|
child: Container(
|
||||||
|
width: 86,
|
||||||
|
height: 86,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white.withValues(alpha: 0.92),
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withValues(alpha: 0.12),
|
||||||
|
blurRadius: 18,
|
||||||
|
offset: const Offset(0, 10),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: const Icon(
|
||||||
|
Icons.rocket_launch_rounded,
|
||||||
|
color: SCForceUpdateDialog._primaryDark,
|
||||||
|
size: 46,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _VersionPill extends StatelessWidget {
|
||||||
|
const _VersionPill({required this.version});
|
||||||
|
|
||||||
|
final String version;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final label =
|
||||||
|
version.isEmpty ? 'New version available' : 'Version $version';
|
||||||
|
|
||||||
|
return DecoratedBox(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: SCForceUpdateDialog._primary.withValues(alpha: 0.12),
|
||||||
|
borderRadius: BorderRadius.circular(999),
|
||||||
|
border: Border.all(
|
||||||
|
color: SCForceUpdateDialog._primary.withValues(alpha: 0.28),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||||
|
child: Text(
|
||||||
|
label,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: SCForceUpdateDialog._primaryDark,
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
height: 1.1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _UpdateNotice extends StatelessWidget {
|
||||||
|
const _UpdateNotice();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return DecoratedBox(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xffF6F8FA),
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
||||||
|
child: const Text(
|
||||||
|
'This Agora-based version has expired. Please update to the latest '
|
||||||
|
'version to continue using voice rooms normally.',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
color: SCForceUpdateDialog._titleColor,
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
height: 1.36,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _UpdateButton extends StatelessWidget {
|
||||||
|
const _UpdateButton({required this.onPressed});
|
||||||
|
|
||||||
|
final VoidCallback onPressed;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: Ink(
|
||||||
|
height: 50,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: const LinearGradient(
|
||||||
|
colors: [SCForceUpdateDialog._primary, Color(0xff76FFD8)],
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: SCForceUpdateDialog._primary.withValues(alpha: 0.34),
|
||||||
|
blurRadius: 16,
|
||||||
|
offset: const Offset(0, 8),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: InkWell(
|
||||||
|
onTap: onPressed,
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
child: const Center(
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Update Now',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Color(0xff10131A),
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w800,
|
||||||
|
height: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 8),
|
||||||
|
Icon(
|
||||||
|
Icons.arrow_forward_rounded,
|
||||||
|
color: Color(0xff10131A),
|
||||||
|
size: 20,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -48,8 +48,6 @@ class SVGAHeadwearWidget extends StatefulWidget {
|
|||||||
class _SVGAHeadwearWidgetState extends State<SVGAHeadwearWidget>
|
class _SVGAHeadwearWidgetState extends State<SVGAHeadwearWidget>
|
||||||
with SingleTickerProviderStateMixin {
|
with SingleTickerProviderStateMixin {
|
||||||
SVGAAnimationController? _animationController;
|
SVGAAnimationController? _animationController;
|
||||||
MovieEntity? _retainedMovieEntity;
|
|
||||||
String? _retainedResource;
|
|
||||||
bool _isLoading = true;
|
bool _isLoading = true;
|
||||||
bool _isNetworkResource = false;
|
bool _isNetworkResource = false;
|
||||||
bool _hasError = false;
|
bool _hasError = false;
|
||||||
@ -84,53 +82,41 @@ class _SVGAHeadwearWidgetState extends State<SVGAHeadwearWidget>
|
|||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// 检查缓存
|
||||||
|
MovieEntity? videoItem;
|
||||||
if (widget.useCache) {
|
if (widget.useCache) {
|
||||||
final videoItem =
|
videoItem = SCGiftVapSvgaManager().videoItemCache[widget.resource];
|
||||||
_retainedResource == widget.resource && _retainedMovieEntity != null
|
}
|
||||||
? _retainedMovieEntity!
|
|
||||||
: await SCGiftVapSvgaManager().acquireSvgaEntity(
|
|
||||||
widget.resource,
|
|
||||||
);
|
|
||||||
final acquiredNewEntity = !identical(videoItem, _retainedMovieEntity);
|
|
||||||
if (!mounted) {
|
|
||||||
if (acquiredNewEntity) {
|
|
||||||
SCGiftVapSvgaManager().releaseSvgaEntity(videoItem);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setState(() {
|
// 如果没有缓存,则进行解析
|
||||||
_releaseRetainedMovieEntity(except: videoItem);
|
if (videoItem == null) {
|
||||||
_retainedMovieEntity = videoItem;
|
videoItem =
|
||||||
_retainedResource = widget.resource;
|
|
||||||
_animationController?.videoItem = videoItem;
|
|
||||||
_isLoading = false;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
final videoItem =
|
|
||||||
_isNetworkResource
|
_isNetworkResource
|
||||||
? await SVGAParser.shared.decodeFromURL(widget.resource)
|
? await SVGAParser.shared.decodeFromURL(widget.resource)
|
||||||
: await SVGAParser.shared.decodeFromAssets(widget.resource);
|
: await SVGAParser.shared.decodeFromAssets(widget.resource);
|
||||||
if (!mounted) {
|
videoItem.autorelease = false;
|
||||||
videoItem.dispose();
|
// 存入缓存
|
||||||
return;
|
if (widget.useCache) {
|
||||||
|
SCGiftVapSvgaManager().videoItemCache[widget.resource] = videoItem;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_animationController?.videoItem = videoItem;
|
_animationController?.videoItem = videoItem;
|
||||||
_isLoading = false;
|
_isLoading = false;
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
// 根据循环次数设置播放方式
|
// 根据循环次数设置播放方式
|
||||||
if (widget.loops == 0) {
|
if (widget.loops == 0) {
|
||||||
_animationController?.repeat();
|
_animationController?.repeat();
|
||||||
} else {
|
} else {
|
||||||
_animationController?.repeat(count: widget.loops);
|
_animationController?.repeat(count: widget.loops);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (widget.onFinishLoading != null) {
|
if (widget.onFinishLoading != null) {
|
||||||
widget.onFinishLoading!();
|
widget.onFinishLoading!();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
@ -171,16 +157,14 @@ class _SVGAHeadwearWidgetState extends State<SVGAHeadwearWidget>
|
|||||||
_animationController?.stop();
|
_animationController?.stop();
|
||||||
if (widget.clearsAfterStop) {
|
if (widget.clearsAfterStop) {
|
||||||
_animationController?.videoItem = null;
|
_animationController?.videoItem = null;
|
||||||
_releaseRetainedMovieEntity();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重新加载动画
|
// 重新加载动画
|
||||||
void reload() {
|
void reload() {
|
||||||
if (widget.useCache) {
|
if (widget.useCache) {
|
||||||
_animationController?.videoItem = null;
|
// 清除缓存项
|
||||||
_releaseRetainedMovieEntity();
|
SCGiftVapSvgaManager().videoItemCache.remove(widget.resource);
|
||||||
SCGiftVapSvgaManager().evictSvgaEntity(widget.resource);
|
|
||||||
}
|
}
|
||||||
_loadAnimation();
|
_loadAnimation();
|
||||||
}
|
}
|
||||||
@ -189,20 +173,9 @@ class _SVGAHeadwearWidgetState extends State<SVGAHeadwearWidget>
|
|||||||
void dispose() {
|
void dispose() {
|
||||||
stop();
|
stop();
|
||||||
_animationController?.dispose();
|
_animationController?.dispose();
|
||||||
_releaseRetainedMovieEntity();
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _releaseRetainedMovieEntity({MovieEntity? except}) {
|
|
||||||
final entity = _retainedMovieEntity;
|
|
||||||
if (entity == null || identical(entity, except)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_retainedMovieEntity = null;
|
|
||||||
_retainedResource = null;
|
|
||||||
SCGiftVapSvgaManager().releaseSvgaEntity(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (_isLoading && !widget.autoPlay) {
|
if (_isLoading && !widget.autoPlay) {
|
||||||
|
|||||||
@ -270,8 +270,6 @@ class _RoomEntranceWidgetState extends State<RoomEntranceWidget>
|
|||||||
bool _isLoading = false;
|
bool _isLoading = false;
|
||||||
bool _hasError = false;
|
bool _hasError = false;
|
||||||
String? _currentResource;
|
String? _currentResource;
|
||||||
MovieEntity? _retainedMovieEntity;
|
|
||||||
String? _retainedResource;
|
|
||||||
|
|
||||||
// 用于存储SVGAImage组件的key,以便刷新占位符
|
// 用于存储SVGAImage组件的key,以便刷新占位符
|
||||||
GlobalKey _svgaKey = GlobalKey();
|
GlobalKey _svgaKey = GlobalKey();
|
||||||
@ -353,79 +351,45 @@ class _RoomEntranceWidgetState extends State<RoomEntranceWidget>
|
|||||||
_hasError = false;
|
_hasError = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
MovieEntity? acquiredMovieEntity;
|
|
||||||
try {
|
try {
|
||||||
|
final isNetworkResource = resource.startsWith('http');
|
||||||
|
MovieEntity? videoItem;
|
||||||
|
|
||||||
if (widget.useCache) {
|
if (widget.useCache) {
|
||||||
final videoItem =
|
videoItem = SCGiftVapSvgaManager().videoItemCache[resource];
|
||||||
_retainedResource == resource && _retainedMovieEntity != null
|
}
|
||||||
? _retainedMovieEntity!
|
|
||||||
: await SCGiftVapSvgaManager().acquireSvgaEntity(resource);
|
|
||||||
final acquiredNewEntity = !identical(videoItem, _retainedMovieEntity);
|
|
||||||
if (acquiredNewEntity) {
|
|
||||||
acquiredMovieEntity = videoItem;
|
|
||||||
}
|
|
||||||
if (!mounted) {
|
|
||||||
if (acquiredNewEntity) {
|
|
||||||
SCGiftVapSvgaManager().releaseSvgaEntity(videoItem);
|
|
||||||
acquiredMovieEntity = null;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 应用动态数据(文本、图片替换)
|
if (videoItem == null) {
|
||||||
await _applyDynamicData(videoItem, dynamicData);
|
videoItem =
|
||||||
if (!mounted) {
|
|
||||||
if (acquiredNewEntity) {
|
|
||||||
SCGiftVapSvgaManager().releaseSvgaEntity(videoItem);
|
|
||||||
acquiredMovieEntity = null;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
_releaseRetainedMovieEntity(except: videoItem);
|
|
||||||
_retainedMovieEntity = videoItem;
|
|
||||||
_retainedResource = resource;
|
|
||||||
_animationController?.videoItem = videoItem;
|
|
||||||
_isLoading = false;
|
|
||||||
});
|
|
||||||
acquiredMovieEntity = null;
|
|
||||||
} else {
|
|
||||||
final isNetworkResource = resource.startsWith('http');
|
|
||||||
final videoItem =
|
|
||||||
isNetworkResource
|
isNetworkResource
|
||||||
? await SVGAParser.shared.decodeFromURL(resource)
|
? await SVGAParser.shared.decodeFromURL(resource)
|
||||||
: await SVGAParser.shared.decodeFromAssets(resource);
|
: await SVGAParser.shared.decodeFromAssets(resource);
|
||||||
if (!mounted) {
|
videoItem.autorelease = false;
|
||||||
videoItem.dispose();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (widget.useCache) {
|
||||||
|
SCGiftVapSvgaManager().videoItemCache[resource] = videoItem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mounted) {
|
||||||
// 应用动态数据(文本、图片替换)
|
// 应用动态数据(文本、图片替换)
|
||||||
await _applyDynamicData(videoItem, dynamicData);
|
await _applyDynamicData(videoItem, dynamicData);
|
||||||
if (!mounted) {
|
|
||||||
videoItem.dispose();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_animationController?.videoItem = videoItem;
|
_animationController?.videoItem = videoItem;
|
||||||
_isLoading = false;
|
_isLoading = false;
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
// 开始播放动画
|
// 开始播放动画
|
||||||
_startAnimation();
|
_startAnimation();
|
||||||
|
|
||||||
if (widget.onFinishLoading != null) {
|
if (widget.onFinishLoading != null) {
|
||||||
widget.onFinishLoading!();
|
widget.onFinishLoading!();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
SCGiftVapSvgaManager().releaseSvgaEntity(acquiredMovieEntity);
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_animationController?.videoItem = null;
|
|
||||||
_releaseRetainedMovieEntity();
|
|
||||||
_isLoading = false;
|
_isLoading = false;
|
||||||
_hasError = true;
|
_hasError = true;
|
||||||
});
|
});
|
||||||
@ -579,7 +543,6 @@ class _RoomEntranceWidgetState extends State<RoomEntranceWidget>
|
|||||||
_animationController?.stop();
|
_animationController?.stop();
|
||||||
if (widget.clearsAfterStop) {
|
if (widget.clearsAfterStop) {
|
||||||
_animationController?.videoItem = null;
|
_animationController?.videoItem = null;
|
||||||
_releaseRetainedMovieEntity();
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_currentResource = null;
|
_currentResource = null;
|
||||||
});
|
});
|
||||||
@ -590,9 +553,7 @@ class _RoomEntranceWidgetState extends State<RoomEntranceWidget>
|
|||||||
void reload() {
|
void reload() {
|
||||||
if (_currentResource != null) {
|
if (_currentResource != null) {
|
||||||
if (widget.useCache) {
|
if (widget.useCache) {
|
||||||
_animationController?.videoItem = null;
|
SCGiftVapSvgaManager().videoItemCache.remove(_currentResource);
|
||||||
_releaseRetainedMovieEntity();
|
|
||||||
SCGiftVapSvgaManager().evictSvgaEntity(_currentResource!);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_loadAnimation(
|
_loadAnimation(
|
||||||
@ -607,22 +568,11 @@ class _RoomEntranceWidgetState extends State<RoomEntranceWidget>
|
|||||||
void dispose() {
|
void dispose() {
|
||||||
stop();
|
stop();
|
||||||
_animationController?.dispose();
|
_animationController?.dispose();
|
||||||
_releaseRetainedMovieEntity();
|
|
||||||
// 从队列管理器中移除当前播放器
|
// 从队列管理器中移除当前播放器
|
||||||
_queueManager.clearCurrentPlayer();
|
_queueManager.clearCurrentPlayer();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _releaseRetainedMovieEntity({MovieEntity? except}) {
|
|
||||||
final entity = _retainedMovieEntity;
|
|
||||||
if (entity == null || identical(entity, except)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_retainedMovieEntity = null;
|
|
||||||
_retainedResource = null;
|
|
||||||
SCGiftVapSvgaManager().releaseSvgaEntity(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
// 显示加载状态
|
// 显示加载状态
|
||||||
|
|||||||
@ -74,11 +74,16 @@ class _ExitMinRoomPageState extends State<ExitMinRoomPage> {
|
|||||||
final giftAnimationManager =
|
final giftAnimationManager =
|
||||||
context.read<GiftAnimationManager>();
|
context.read<GiftAnimationManager>();
|
||||||
final rtmProvider = context.read<RtmProvider>();
|
final rtmProvider = context.read<RtmProvider>();
|
||||||
|
final currentRoomId =
|
||||||
|
rtcProvider.currenRoom?.roomProfile?.roomProfile?.id
|
||||||
|
?.trim() ??
|
||||||
|
'';
|
||||||
|
rtcProvider.setVoiceRoomRouteVisible(false);
|
||||||
rtcProvider.setRoomVisualEffectsEnabled(false);
|
rtcProvider.setRoomVisualEffectsEnabled(false);
|
||||||
rtmProvider.msgFloatingGiftListener = null;
|
rtmProvider.msgFloatingGiftListener = null;
|
||||||
rtmProvider.msgLuckyGiftRewardTickerListener = null;
|
rtmProvider.msgLuckyGiftRewardTickerListener = null;
|
||||||
RoomEntranceHelper.clearQueue();
|
RoomEntranceHelper.clearQueue();
|
||||||
OverlayManager().removeRoom();
|
OverlayManager().removeRoom(roomId: currentRoomId);
|
||||||
SCRoomEffectScheduler().clearDeferredTasks(
|
SCRoomEffectScheduler().clearDeferredTasks(
|
||||||
reason: 'room_minimize',
|
reason: 'room_minimize',
|
||||||
);
|
);
|
||||||
|
|||||||
@ -22,6 +22,8 @@ class RoomPlayWidget extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _RoomPlayWidgetState extends State<RoomPlayWidget> {
|
class _RoomPlayWidgetState extends State<RoomPlayWidget> {
|
||||||
|
static const double _rightActionDownShift = 24;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SizedBox.expand(
|
return SizedBox.expand(
|
||||||
@ -29,17 +31,17 @@ class _RoomPlayWidgetState extends State<RoomPlayWidget> {
|
|||||||
children: [
|
children: [
|
||||||
PositionedDirectional(
|
PositionedDirectional(
|
||||||
end: 15.w,
|
end: 15.w,
|
||||||
bottom: 150.w,
|
bottom: (150 - _rightActionDownShift).w,
|
||||||
child: const RoomMusicFloatingEntry(),
|
child: const RoomMusicFloatingEntry(),
|
||||||
),
|
),
|
||||||
PositionedDirectional(
|
PositionedDirectional(
|
||||||
end: 15.w,
|
end: 15.w,
|
||||||
bottom: 200.w,
|
bottom: (200 - _rightActionDownShift).w,
|
||||||
child: const _RoomRedPacketFloatingEntry(),
|
child: const _RoomRedPacketFloatingEntry(),
|
||||||
),
|
),
|
||||||
PositionedDirectional(
|
PositionedDirectional(
|
||||||
end: 15.w,
|
end: 15.w,
|
||||||
bottom: 100.w,
|
bottom: (100 - _rightActionDownShift).w,
|
||||||
child: const RoomGameEntryButton(),
|
child: const RoomGameEntryButton(),
|
||||||
),
|
),
|
||||||
PositionedDirectional(
|
PositionedDirectional(
|
||||||
|
|||||||
@ -52,8 +52,6 @@ class _SCNetworkSvgaWidgetState extends State<SCNetworkSvgaWidget>
|
|||||||
with SingleTickerProviderStateMixin {
|
with SingleTickerProviderStateMixin {
|
||||||
late final SVGAAnimationController _controller;
|
late final SVGAAnimationController _controller;
|
||||||
String? _loadedResource;
|
String? _loadedResource;
|
||||||
MovieEntity? _retainedMovieEntity;
|
|
||||||
String? _retainedResource;
|
|
||||||
bool _hasError = false;
|
bool _hasError = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -80,10 +78,9 @@ class _SCNetworkSvgaWidgetState extends State<SCNetworkSvgaWidget>
|
|||||||
final resource = widget.resource.trim();
|
final resource = widget.resource.trim();
|
||||||
if (resource.isEmpty || !SCNetworkSvgaWidget.isSvga(resource)) {
|
if (resource.isEmpty || !SCNetworkSvgaWidget.isSvga(resource)) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_controller.videoItem = null;
|
|
||||||
_releaseRetainedMovieEntity();
|
|
||||||
_hasError = true;
|
_hasError = true;
|
||||||
_loadedResource = null;
|
_loadedResource = null;
|
||||||
|
_controller.videoItem = null;
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -92,53 +89,37 @@ class _SCNetworkSvgaWidgetState extends State<SCNetworkSvgaWidget>
|
|||||||
_hasError = false;
|
_hasError = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
MovieEntity? acquiredMovieEntity;
|
|
||||||
try {
|
try {
|
||||||
final movieEntity =
|
final cache = SCGiftVapSvgaManager().videoItemCache;
|
||||||
_retainedResource == resource && _retainedMovieEntity != null
|
var movieEntity = cache[resource];
|
||||||
? _retainedMovieEntity!
|
if (movieEntity == null) {
|
||||||
: await SCGiftVapSvgaManager().acquireSvgaEntity(resource);
|
movieEntity =
|
||||||
final acquiredNewEntity = !identical(movieEntity, _retainedMovieEntity);
|
resource.startsWith("http")
|
||||||
if (acquiredNewEntity) {
|
? await SVGAParser.shared.decodeFromURL(resource)
|
||||||
acquiredMovieEntity = movieEntity;
|
: await SVGAParser.shared.decodeFromAssets(resource);
|
||||||
}
|
movieEntity.autorelease = false;
|
||||||
if (!mounted || widget.resource.trim() != resource) {
|
cache[resource] = movieEntity;
|
||||||
if (acquiredNewEntity) {
|
|
||||||
SCGiftVapSvgaManager().releaseSvgaEntity(movieEntity);
|
|
||||||
acquiredMovieEntity = null;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
if (widget.movieConfigurer != null) {
|
if (widget.movieConfigurer != null) {
|
||||||
movieEntity.dynamicItem.reset();
|
movieEntity.dynamicItem.reset();
|
||||||
await widget.movieConfigurer!(movieEntity);
|
await widget.movieConfigurer!(movieEntity);
|
||||||
}
|
}
|
||||||
if (!mounted || widget.resource.trim() != resource) {
|
if (!mounted || widget.resource.trim() != resource) {
|
||||||
if (acquiredNewEntity) {
|
|
||||||
SCGiftVapSvgaManager().releaseSvgaEntity(movieEntity);
|
|
||||||
acquiredMovieEntity = null;
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setState(() {
|
setState(() {
|
||||||
_releaseRetainedMovieEntity(except: movieEntity);
|
|
||||||
_retainedMovieEntity = movieEntity;
|
|
||||||
_retainedResource = resource;
|
|
||||||
_loadedResource = resource;
|
_loadedResource = resource;
|
||||||
_controller.videoItem = movieEntity;
|
_controller.videoItem = movieEntity;
|
||||||
});
|
});
|
||||||
acquiredMovieEntity = null;
|
|
||||||
_syncPlayback(restartIfActive: true);
|
_syncPlayback(restartIfActive: true);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
SCGiftVapSvgaManager().releaseSvgaEntity(acquiredMovieEntity);
|
|
||||||
if (!mounted || widget.resource.trim() != resource) {
|
if (!mounted || widget.resource.trim() != resource) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setState(() {
|
setState(() {
|
||||||
_controller.videoItem = null;
|
|
||||||
_releaseRetainedMovieEntity();
|
|
||||||
_hasError = true;
|
_hasError = true;
|
||||||
_loadedResource = null;
|
_loadedResource = null;
|
||||||
|
_controller.videoItem = null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -164,20 +145,9 @@ class _SCNetworkSvgaWidgetState extends State<SCNetworkSvgaWidget>
|
|||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_controller.dispose();
|
_controller.dispose();
|
||||||
_releaseRetainedMovieEntity();
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _releaseRetainedMovieEntity({MovieEntity? except}) {
|
|
||||||
final entity = _retainedMovieEntity;
|
|
||||||
if (entity == null || identical(entity, except)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_retainedMovieEntity = null;
|
|
||||||
_retainedResource = null;
|
|
||||||
SCGiftVapSvgaManager().releaseSvgaEntity(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final videoItem = _controller.videoItem;
|
final videoItem = _controller.videoItem;
|
||||||
|
|||||||
@ -8,11 +8,9 @@ typedef SCSvgaMovieConfigurer =
|
|||||||
FutureOr<void> Function(MovieEntity movieEntity);
|
FutureOr<void> Function(MovieEntity movieEntity);
|
||||||
|
|
||||||
class SCSvgaAssetWidget extends StatefulWidget {
|
class SCSvgaAssetWidget extends StatefulWidget {
|
||||||
static const int _maxCacheEntries = 8;
|
|
||||||
static final Map<String, MovieEntity> _cache = <String, MovieEntity>{};
|
static final Map<String, MovieEntity> _cache = <String, MovieEntity>{};
|
||||||
static final Map<String, Future<MovieEntity>> _loadingTasks =
|
static final Map<String, Future<MovieEntity>> _loadingTasks =
|
||||||
<String, Future<MovieEntity>>{};
|
<String, Future<MovieEntity>>{};
|
||||||
static final Map<MovieEntity, int> _activeUseCounts = <MovieEntity, int>{};
|
|
||||||
|
|
||||||
const SCSvgaAssetWidget({
|
const SCSvgaAssetWidget({
|
||||||
super.key,
|
super.key,
|
||||||
@ -51,26 +49,8 @@ class SCSvgaAssetWidget extends StatefulWidget {
|
|||||||
return _obtainMovieEntity(assetPath);
|
return _obtainMovieEntity(assetPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void clearMemoryCache({bool includeActive = false}) {
|
|
||||||
final entries = _cache.entries.toList(growable: false);
|
|
||||||
for (final entry in entries) {
|
|
||||||
final entity = entry.value;
|
|
||||||
if (!includeActive && _isActive(entity)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
_cache.remove(entry.key);
|
|
||||||
_disposeMovieEntity(entity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<MovieEntity> _acquireMovieEntity(String assetPath) async {
|
|
||||||
final entity = await _obtainMovieEntity(assetPath);
|
|
||||||
_retainMovieEntity(entity);
|
|
||||||
return entity;
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<MovieEntity> _obtainMovieEntity(String assetPath) async {
|
static Future<MovieEntity> _obtainMovieEntity(String assetPath) async {
|
||||||
final cached = _touchCachedMovieEntity(assetPath);
|
final cached = _cache[assetPath];
|
||||||
if (cached != null) {
|
if (cached != null) {
|
||||||
return cached;
|
return cached;
|
||||||
}
|
}
|
||||||
@ -91,7 +71,7 @@ class SCSvgaAssetWidget extends StatefulWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
entity.autorelease = false;
|
entity.autorelease = false;
|
||||||
_cacheMovieEntity(assetPath, entity);
|
_cache[assetPath] = entity;
|
||||||
return entity;
|
return entity;
|
||||||
}();
|
}();
|
||||||
|
|
||||||
@ -103,77 +83,6 @@ class SCSvgaAssetWidget extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static MovieEntity? _touchCachedMovieEntity(String assetPath) {
|
|
||||||
final cached = _cache.remove(assetPath);
|
|
||||||
if (cached != null) {
|
|
||||||
_cache[assetPath] = cached;
|
|
||||||
}
|
|
||||||
return cached;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void _cacheMovieEntity(String assetPath, MovieEntity entity) {
|
|
||||||
final previous = _cache.remove(assetPath);
|
|
||||||
if (previous != null && !identical(previous, entity)) {
|
|
||||||
_disposeMovieEntityIfInactive(previous);
|
|
||||||
}
|
|
||||||
_cache[assetPath] = entity;
|
|
||||||
_trimCache();
|
|
||||||
}
|
|
||||||
|
|
||||||
static void _trimCache() {
|
|
||||||
while (_cache.length > _maxCacheEntries) {
|
|
||||||
final removableEntry = _cache.entries.firstWhere(
|
|
||||||
(entry) => !_isActive(entry.value),
|
|
||||||
orElse: () => _cache.entries.first,
|
|
||||||
);
|
|
||||||
if (_isActive(removableEntry.value)) {
|
|
||||||
_cache.remove(removableEntry.key);
|
|
||||||
_cache[removableEntry.key] = removableEntry.value;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
_cache.remove(removableEntry.key);
|
|
||||||
_disposeMovieEntity(removableEntry.value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void _retainMovieEntity(MovieEntity entity) {
|
|
||||||
_activeUseCounts[entity] = (_activeUseCounts[entity] ?? 0) + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void _releaseMovieEntity(MovieEntity? entity) {
|
|
||||||
if (entity == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final count = _activeUseCounts[entity] ?? 0;
|
|
||||||
if (count <= 1) {
|
|
||||||
_activeUseCounts.remove(entity);
|
|
||||||
if (!_cache.containsValue(entity)) {
|
|
||||||
_disposeMovieEntity(entity);
|
|
||||||
} else {
|
|
||||||
_trimCache();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_activeUseCounts[entity] = count - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool _isActive(MovieEntity entity) =>
|
|
||||||
(_activeUseCounts[entity] ?? 0) > 0;
|
|
||||||
|
|
||||||
static void _disposeMovieEntityIfInactive(MovieEntity entity) {
|
|
||||||
if (_isActive(entity)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_disposeMovieEntity(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void _disposeMovieEntity(MovieEntity entity) {
|
|
||||||
try {
|
|
||||||
_activeUseCounts.remove(entity);
|
|
||||||
entity.dispose();
|
|
||||||
} catch (_) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<SCSvgaAssetWidget> createState() => _SCSvgaAssetWidgetState();
|
State<SCSvgaAssetWidget> createState() => _SCSvgaAssetWidgetState();
|
||||||
}
|
}
|
||||||
@ -182,8 +91,6 @@ class _SCSvgaAssetWidgetState extends State<SCSvgaAssetWidget>
|
|||||||
with SingleTickerProviderStateMixin {
|
with SingleTickerProviderStateMixin {
|
||||||
late final SVGAAnimationController _controller;
|
late final SVGAAnimationController _controller;
|
||||||
String? _loadedAssetPath;
|
String? _loadedAssetPath;
|
||||||
MovieEntity? _retainedMovieEntity;
|
|
||||||
String? _retainedAssetPath;
|
|
||||||
bool _hasError = false;
|
bool _hasError = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -224,44 +131,25 @@ class _SCSvgaAssetWidgetState extends State<SCSvgaAssetWidget>
|
|||||||
setState(() {
|
setState(() {
|
||||||
_hasError = false;
|
_hasError = false;
|
||||||
});
|
});
|
||||||
MovieEntity? acquiredMovieEntity;
|
|
||||||
try {
|
try {
|
||||||
final movieEntity =
|
final movieEntity = await SCSvgaAssetWidget._obtainMovieEntity(assetPath);
|
||||||
_retainedAssetPath == assetPath && _retainedMovieEntity != null
|
|
||||||
? _retainedMovieEntity!
|
|
||||||
: await SCSvgaAssetWidget._acquireMovieEntity(assetPath);
|
|
||||||
final acquiredNewEntity = !identical(movieEntity, _retainedMovieEntity);
|
|
||||||
if (acquiredNewEntity) {
|
|
||||||
acquiredMovieEntity = movieEntity;
|
|
||||||
}
|
|
||||||
if (widget.movieConfigurer != null) {
|
if (widget.movieConfigurer != null) {
|
||||||
movieEntity.dynamicItem.reset();
|
movieEntity.dynamicItem.reset();
|
||||||
await widget.movieConfigurer!(movieEntity);
|
await widget.movieConfigurer!(movieEntity);
|
||||||
}
|
}
|
||||||
if (!mounted || widget.assetPath != assetPath) {
|
if (!mounted || widget.assetPath != assetPath) {
|
||||||
if (acquiredNewEntity) {
|
|
||||||
SCSvgaAssetWidget._releaseMovieEntity(movieEntity);
|
|
||||||
acquiredMovieEntity = null;
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setState(() {
|
setState(() {
|
||||||
_releaseRetainedMovieEntity(except: movieEntity);
|
|
||||||
_retainedMovieEntity = movieEntity;
|
|
||||||
_retainedAssetPath = assetPath;
|
|
||||||
_loadedAssetPath = assetPath;
|
_loadedAssetPath = assetPath;
|
||||||
_controller.videoItem = movieEntity;
|
_controller.videoItem = movieEntity;
|
||||||
});
|
});
|
||||||
acquiredMovieEntity = null;
|
|
||||||
_syncPlayback(restartIfActive: true);
|
_syncPlayback(restartIfActive: true);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
SCSvgaAssetWidget._releaseMovieEntity(acquiredMovieEntity);
|
|
||||||
if (!mounted || widget.assetPath != assetPath) {
|
if (!mounted || widget.assetPath != assetPath) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setState(() {
|
setState(() {
|
||||||
_controller.videoItem = null;
|
|
||||||
_releaseRetainedMovieEntity();
|
|
||||||
_hasError = true;
|
_hasError = true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -297,20 +185,9 @@ class _SCSvgaAssetWidgetState extends State<SCSvgaAssetWidget>
|
|||||||
void dispose() {
|
void dispose() {
|
||||||
_controller.removeStatusListener(_handleAnimationStatusChanged);
|
_controller.removeStatusListener(_handleAnimationStatusChanged);
|
||||||
_controller.dispose();
|
_controller.dispose();
|
||||||
_releaseRetainedMovieEntity();
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _releaseRetainedMovieEntity({MovieEntity? except}) {
|
|
||||||
final entity = _retainedMovieEntity;
|
|
||||||
if (entity == null || identical(entity, except)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_retainedMovieEntity = null;
|
|
||||||
_retainedAssetPath = null;
|
|
||||||
SCSvgaAssetWidget._releaseMovieEntity(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (_hasError) {
|
if (_hasError) {
|
||||||
|
|||||||
@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
|||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
# In Windows, build-name is used as the major, minor, and patch parts
|
# In Windows, build-name is used as the major, minor, and patch parts
|
||||||
# of the product and file versions while build-number is used as the build suffix.
|
# of the product and file versions while build-number is used as the build suffix.
|
||||||
version: 1.3.1+12
|
version: 1.3.1+131
|
||||||
|
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user