import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:yumi/app_localizations.dart'; import 'package:yumi/app/constants/sc_global_config.dart'; import 'package:yumi/shared/business_logic/models/res/sc_sign_in_res.dart'; import 'package:yumi/shared/data_sources/sources/local/floating_screen_manager.dart'; import 'package:yumi/modules/home/index_home_page.dart'; import 'package:yumi/modules/chat/message/sc_message_page.dart'; import 'package:yumi/services/audio/rtc_manager.dart'; import 'package:yumi/services/audio/rtm_manager.dart'; import 'package:provider/provider.dart'; import 'package:wakelock_plus/wakelock_plus.dart'; import 'package:yumi/ui_kit/components/text/sc_text.dart'; import 'package:yumi/shared/data_sources/sources/local/data_persistence.dart'; import 'package:yumi/shared/data_sources/sources/repositories/sc_user_repository_impl.dart'; import 'package:yumi/services/general/sc_app_general_manager.dart'; import 'package:yumi/services/auth/user_profile_manager.dart'; import 'package:yumi/ui_kit/widgets/svga/sc_svga_asset_widget.dart'; import '../../shared/tools/sc_heartbeat_utils.dart'; import '../../shared/tools/sc_lk_event_bus.dart'; import '../../shared/data_sources/models/enum/sc_heartbeat_status.dart'; import '../../ui_kit/components/sc_float_ichart.dart'; import '../../ui_kit/widgets/daily_sign_in/daily_sign_in_dialog.dart'; import '../../ui_kit/widgets/register_reward/register_reward_dialog.dart'; import '../home/popular/event/home_event_page.dart'; import '../user/me_page2.dart'; /// 首页 class SCIndexPage extends StatefulWidget { const SCIndexPage({super.key}); @override State createState() => _SCIndexPageState(); } class _SCIndexPageState extends State { static const Duration _registerRewardSocketWaitTimeout = Duration(seconds: 6); int _currentIndex = 0; final List _pages = []; final List _bottomItems = []; SCAppGeneralManager? generalProvider; Locale? _lastLocale; bool _hasShownEntryDialogs = false; bool _hasShownRegisterRewardDialog = false; bool _isShowingDailySignInDialog = false; bool _showRegisterRewardAfterDailySignIn = false; StreamSubscription? _registerRewardSubscription; Completer? _registerRewardSocketCompleter; @override void initState() { super.initState(); _registerRewardSubscription = eventBus .on() .listen(_onRegisterRewardGranted); _initializePages(); generalProvider = Provider.of(context, listen: false); Provider.of( context, listen: false, ).initializeRealTimeCommunicationManager(context); Provider.of(context, listen: false).init(context); Provider.of( context, listen: false, ).getUserIdentity(); Provider.of(context, listen: false).balance(); SCHeartbeatUtils.scheduleHeartbeat(SCHeartbeatStatus.ONLINE.name, false); DataPersistence.setLang(SCGlobalConfig.lang); OverlayManager().activate(); WidgetsBinding.instance.addPostFrameCallback((_) { WakelockPlus.enable(); _showEntryDialogs(); }); String roomId = DataPersistence.getLastTimeRoomId(); if (roomId.isNotEmpty) { SCAccountRepository().quitRoom(roomId).catchError((e) { return true; // 错误已处理 }); } } @override void dispose() { _registerRewardSubscription?.cancel(); WakelockPlus.disable(); super.dispose(); } @override void didChangeDependencies() { super.didChangeDependencies(); SCFloatIchart().init(context); _rebuildBottomItemsIfNeeded(); } @override Widget build(BuildContext context) { return PopScope( canPop: false, onPopInvokedWithResult: (bool didPop, Object? result) async { if (didPop) { return; } final shouldPop = await _doubleExit(); if (shouldPop && context.mounted) { Navigator.of(context).pop(result); } }, child: Stack( children: [ Image.asset( "sc_images/index/sc_icon_index_bg.png", width: ScreenUtil().screenWidth, height: ScreenUtil().screenHeight, fit: BoxFit.fill, ), SafeArea( top: false, child: Scaffold( resizeToAvoidBottomInset: false, backgroundColor: Colors.transparent, body: _pages[_currentIndex], bottomNavigationBar: Container( height: 85.w, decoration: BoxDecoration( image: DecorationImage( image: AssetImage( "sc_images/index/sc_index_bottom_navigation_bar_bg.png", ), fit: BoxFit.fill, ), ), child: Theme( data: Theme.of(context).copyWith( splashFactory: NoSplash.splashFactory, splashColor: Colors.transparent, highlightColor: Colors.transparent, hoverColor: Colors.transparent, ), child: BottomNavigationBar( elevation: 0, enableFeedback: false, backgroundColor: Colors.transparent, selectedLabelStyle: TextStyle( fontWeight: FontWeight.w600, fontSize: 14.sp, ), unselectedLabelStyle: TextStyle( fontWeight: FontWeight.w500, fontSize: 13.sp, ), type: BottomNavigationBarType.fixed, selectedItemColor: Color(0xffBF854A), unselectedItemColor: Color(0xffC4C4C4), showUnselectedLabels: true, showSelectedLabels: true, items: _bottomItems, currentIndex: _currentIndex, onTap: (index) => setState(() => _currentIndex = index), ), ), ), ), ), ], ), ); } ///双击退出 int _lastClickTime = 0; Future _doubleExit() { int nowTime = DateTime.now().microsecondsSinceEpoch; if (_lastClickTime != 0 && nowTime - _lastClickTime > 1500) { return Future.value(true); } else { _lastClickTime = DateTime.now().microsecondsSinceEpoch; Future.delayed(const Duration(milliseconds: 1500), () { _lastClickTime = 0; }); return Future.value(false); } } void _initializePages() { if (_pages.isNotEmpty) { return; } _pages.add(SCIndexHomePage()); _pages.add(HomeEventPage()); _pages.add(SCMessagePage()); _pages.add(MePage2()); } void _rebuildBottomItemsIfNeeded() { final locale = Localizations.localeOf(context); if (_lastLocale == locale && _bottomItems.isNotEmpty) { return; } _lastLocale = locale; _bottomItems.clear(); _bottomItems.add( BottomNavigationBarItem( icon: _buildBottomTabIcon( active: false, svgaPath: "sc_images/index/sc_icon_home_anim.svga", fallbackPath: "sc_images/index/sc_icon_home_no.png", ), activeIcon: _buildBottomTabIcon( active: true, svgaPath: "sc_images/index/sc_icon_home_anim.svga", fallbackPath: "sc_images/index/sc_icon_home_en.png", ), label: SCAppLocalizations.of(context)!.home, ), ); _bottomItems.add( BottomNavigationBarItem( icon: _buildBottomTabIcon( active: false, svgaPath: "sc_images/index/sc_icon_explore_anim.svga", fallbackPath: "sc_images/index/sc_icon_explore_no.png", ), activeIcon: _buildBottomTabIcon( active: true, svgaPath: "sc_images/index/sc_icon_explore_anim.svga", fallbackPath: "sc_images/index/sc_icon_explore_en.png", ), label: SCAppLocalizations.of(context)!.explore, ), ); _bottomItems.add( BottomNavigationBarItem( icon: Selector( selector: (c, p) => p.allUnReadCount, shouldRebuild: (prev, next) => prev != next, builder: (_, allUnReadCount, __) { return allUnReadCount > 0 ? Badge( backgroundColor: Colors.red, label: text( "${allUnReadCount > 99 ? "99+" : allUnReadCount}", fontSize: 9.sp, textColor: Colors.white, fontWeight: FontWeight.w600, ), alignment: AlignmentDirectional.topEnd, child: _buildBottomTabIcon( active: false, svgaPath: "sc_images/index/sc_icon_message_anim.svga", fallbackPath: "sc_images/index/sc_icon_message_no.png", ), ) : _buildBottomTabIcon( active: false, svgaPath: "sc_images/index/sc_icon_message_anim.svga", fallbackPath: "sc_images/index/sc_icon_message_no.png", ); }, ), activeIcon: Selector( selector: (c, p) => p.allUnReadCount, shouldRebuild: (prev, next) => prev != next, builder: (_, allUnReadCount, __) { return allUnReadCount > 0 ? Badge( backgroundColor: Colors.red, label: text( "${allUnReadCount > 99 ? "99+" : allUnReadCount}", fontSize: 9.sp, textColor: Colors.white, fontWeight: FontWeight.w600, ), alignment: AlignmentDirectional.topEnd, child: _buildBottomTabIcon( active: true, svgaPath: "sc_images/index/sc_icon_message_anim.svga", fallbackPath: "sc_images/index/sc_icon_message_en.png", ), ) : _buildBottomTabIcon( active: true, svgaPath: "sc_images/index/sc_icon_message_anim.svga", fallbackPath: "sc_images/index/sc_icon_message_en.png", ); }, ), label: SCAppLocalizations.of(context)!.message, ), ); _bottomItems.add( BottomNavigationBarItem( icon: _buildBottomTabIcon( active: false, svgaPath: "sc_images/index/sc_icon_me_anim.svga", fallbackPath: "sc_images/index/sc_icon_me_no.png", ), activeIcon: _buildBottomTabIcon( active: true, svgaPath: "sc_images/index/sc_icon_me_anim.svga", fallbackPath: "sc_images/index/sc_icon_me_en.png", ), label: SCAppLocalizations.of(context)!.me, ), ); } Future _showEntryDialogs() async { if (_hasShownEntryDialogs || !mounted) { return; } _hasShownEntryDialogs = true; await _waitForRegisterRewardSocketIfNeeded(); if (!mounted) { return; } await _showRegisterRewardDialogIfNeeded(); if (!mounted) { return; } final dialogData = await _loadDailySignInDialogData(); if (!mounted) { return; } _isShowingDailySignInDialog = true; await DailySignInDialog.show(context, data: dialogData); _isShowingDailySignInDialog = false; if (!mounted) { return; } if (_showRegisterRewardAfterDailySignIn) { _showRegisterRewardAfterDailySignIn = false; await _showRegisterRewardDialogIfNeeded(); } } Future _waitForRegisterRewardSocketIfNeeded() async { if (!DataPersistence.getAwaitRegisterRewardSocket() || DataPersistence.getPendingRegisterRewardDialog()) { return; } final completer = Completer(); _registerRewardSocketCompleter = completer; await Future.any([ completer.future, Future.delayed(_registerRewardSocketWaitTimeout), ]); if (identical(_registerRewardSocketCompleter, completer)) { _registerRewardSocketCompleter = null; } if (!DataPersistence.getPendingRegisterRewardDialog()) { await DataPersistence.clearAwaitRegisterRewardSocket(); } } Future _showRegisterRewardDialogIfNeeded() async { if (_hasShownRegisterRewardDialog || !DataPersistence.getPendingRegisterRewardDialog()) { return; } await DataPersistence.clearPendingRegisterRewardDialog(); await DataPersistence.clearAwaitRegisterRewardSocket(); if (!mounted) { return; } _hasShownRegisterRewardDialog = true; await RegisterRewardDialog.show(context); } void _onRegisterRewardGranted(RegisterRewardGrantedEvent event) { final completer = _registerRewardSocketCompleter; if (completer != null && !completer.isCompleted) { completer.complete(); } if (!mounted || _hasShownRegisterRewardDialog) { return; } if (_isShowingDailySignInDialog) { _showRegisterRewardAfterDailySignIn = true; return; } if (_hasShownEntryDialogs) { unawaited(_showRegisterRewardDialogIfNeeded()); } } Future _loadDailySignInDialogData() async { DailySignInDialogData dialogData; try { final result = await Future.wait([ SCAccountRepository().checkInToday(), SCAccountRepository().sginListAward(), ]); final checkedToday = result[0] as bool; final signInRes = result[1] as SCSignInRes; dialogData = DailySignInDialogData.fromLegacy( signInRes: signInRes, checkedToday: checkedToday, ); } catch (_) { dialogData = DailySignInDialogData.mock(); } return dialogData; } Widget _buildBottomTabIcon({ required bool active, required String svgaPath, required String fallbackPath, }) { return SCSvgaAssetWidget( assetPath: svgaPath, width: 35.w, height: 35.w, active: active, loop: false, fallback: Image.asset(fallbackPath, width: 35.w, height: 35.w), ); } }