2026-04-18 19:00:19 +08:00

438 lines
14 KiB
Dart

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<SCIndexPage> createState() => _SCIndexPageState();
}
class _SCIndexPageState extends State<SCIndexPage> {
static const Duration _registerRewardSocketWaitTimeout = Duration(seconds: 6);
int _currentIndex = 0;
final List<Widget> _pages = [];
final List<BottomNavigationBarItem> _bottomItems = [];
SCAppGeneralManager? generalProvider;
Locale? _lastLocale;
bool _hasShownEntryDialogs = false;
bool _hasShownRegisterRewardDialog = false;
bool _isShowingDailySignInDialog = false;
bool _showRegisterRewardAfterDailySignIn = false;
StreamSubscription<RegisterRewardGrantedEvent>? _registerRewardSubscription;
Completer<void>? _registerRewardSocketCompleter;
@override
void initState() {
super.initState();
_registerRewardSubscription = eventBus
.on<RegisterRewardGrantedEvent>()
.listen(_onRegisterRewardGranted);
_initializePages();
generalProvider = Provider.of<SCAppGeneralManager>(context, listen: false);
Provider.of<RtcProvider>(
context,
listen: false,
).initializeRealTimeCommunicationManager(context);
Provider.of<RtmProvider>(context, listen: false).init(context);
Provider.of<SocialChatUserProfileManager>(
context,
listen: false,
).getUserIdentity();
Provider.of<SocialChatUserProfileManager>(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<bool> _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<RtmProvider, int>(
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<RtmProvider, int>(
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<void> _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<void> _waitForRegisterRewardSocketIfNeeded() async {
if (!DataPersistence.getAwaitRegisterRewardSocket() ||
DataPersistence.getPendingRegisterRewardDialog()) {
return;
}
final completer = Completer<void>();
_registerRewardSocketCompleter = completer;
await Future.any([
completer.future,
Future<void>.delayed(_registerRewardSocketWaitTimeout),
]);
if (identical(_registerRewardSocketCompleter, completer)) {
_registerRewardSocketCompleter = null;
}
if (!DataPersistence.getPendingRegisterRewardDialog()) {
await DataPersistence.clearAwaitRegisterRewardSocket();
}
}
Future<void> _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<DailySignInDialogData> _loadDailySignInDialogData() async {
DailySignInDialogData dialogData;
try {
final result = await Future.wait<dynamic>([
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),
);
}
}