import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/widgets/custom_height_widget.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/scroll_physics.dart'; import 'package:PiliPlus/pages/common/common_page.dart'; import 'package:PiliPlus/pages/home/controller.dart'; import 'package:PiliPlus/pages/main/controller.dart'; import 'package:PiliPlus/pages/mine/controller.dart'; import 'package:PiliPlus/utils/extension/get_ext.dart'; import 'package:PiliPlus/utils/extension/size_ext.dart'; import 'package:PiliPlus/utils/feed_back.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; class HomePage extends StatefulWidget { const HomePage({super.key}); @override State createState() => _HomePageState(); } class _HomePageState extends CommonPageState with AutomaticKeepAliveClientMixin { final _homeController = Get.putOrFind(HomeController.new); final _mainController = Get.find(); @override bool get wantKeepAlive => true; @override Widget build(BuildContext context) { super.build(context); final theme = Theme.of(context); Widget tabBar; if (_homeController.tabs.length > 1) { tabBar = Padding( padding: const EdgeInsets.only(top: 4), child: SizedBox( height: 42, width: double.infinity, child: TabBar( controller: _homeController.tabController, tabs: _homeController.tabs.map((e) => Tab(text: e.label)).toList(), isScrollable: true, dividerColor: Colors.transparent, dividerHeight: 0, splashBorderRadius: StyleString.mdRadius, tabAlignment: TabAlignment.center, onTap: (_) { feedBack(); if (!_homeController.tabController.indexIsChanging) { _homeController.animateToTop(); } }, ), ), ); if (_homeController.hideTopBar && _mainController.barHideType == .instant) { tabBar = Material( color: theme.colorScheme.surface, child: tabBar, ); } } else { tabBar = const SizedBox(height: 6); } return Column( children: [ if (!_mainController.useSideBar && MediaQuery.sizeOf(context).isPortrait) customAppBar(theme), tabBar, Expanded( child: onBuild( tabBarView( controller: _homeController.tabController, children: _homeController.tabs.map((e) => e.page).toList(), ), ), ), ], ); } Widget customAppBar(ThemeData theme) { const padding = EdgeInsets.fromLTRB(14, 6, 14, 0); final child = Row( children: [ searchBar(theme), const SizedBox(width: 4), msgBadge(_mainController), const SizedBox(width: 8), userAvatar(theme: theme, mainController: _mainController), ], ); if (_homeController.hideTopBar) { if (_mainController.barOffset case final barOffset?) { return Obx( () { final offset = barOffset.value; return CustomHeightWidget( offset: Offset(0, -offset), height: StyleString.topBarHeight - offset, child: Padding( padding: padding, child: child, ), ); }, ); } if (_homeController.showTopBar case final showTopBar?) { return Obx(() { final showSearchBar = showTopBar.value; return AnimatedOpacity( opacity: showSearchBar ? 1 : 0, duration: const Duration(milliseconds: 300), child: AnimatedContainer( curve: Curves.easeInOutCubicEmphasized, duration: const Duration(milliseconds: 500), height: showSearchBar ? StyleString.topBarHeight : 0, padding: padding, child: child, ), ); }); } } return Container( height: StyleString.topBarHeight, padding: padding, child: child, ); } Widget searchBar(ThemeData theme) { const borderRadius = BorderRadius.all(Radius.circular(25)); return Expanded( child: SizedBox( height: 44, child: Material( borderRadius: borderRadius, color: theme.colorScheme.onSecondaryContainer.withValues(alpha: 0.05), child: InkWell( borderRadius: borderRadius, splashColor: theme.colorScheme.primaryContainer.withValues( alpha: 0.3, ), onTap: () => Get.toNamed( '/search', parameters: _homeController.enableSearchWord ? {'hintText': _homeController.defaultSearch.value} : null, ), child: Row( children: [ const SizedBox(width: 14), Icon( Icons.search_outlined, color: theme.colorScheme.onSecondaryContainer, semanticLabel: '搜索', ), const SizedBox(width: 10), Expanded( child: Obx( () => Text( _homeController.defaultSearch.value, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle(color: theme.colorScheme.outline), ), ), ), const SizedBox(width: 5), ], ), ), ), ), ); } } Widget userAvatar({ required ThemeData theme, required MainController mainController, }) { return Semantics( label: "我的", child: Obx( () { if (mainController.accountService.isLogin.value) { return Stack( clipBehavior: .none, children: [ NetworkImgLayer( type: .avatar, width: 34, height: 34, src: mainController.accountService.face.value, ), Positioned.fill( child: Material( type: .transparency, child: InkWell( onTap: mainController.toMinePage, splashColor: theme.colorScheme.primaryContainer.withValues( alpha: 0.3, ), customBorder: const CircleBorder(), ), ), ), Positioned( right: -4, bottom: -4, child: Obx( () => MineController.anonymity.value ? IgnorePointer( child: Container( padding: const .all(2), decoration: BoxDecoration( shape: .circle, color: theme.colorScheme.secondaryContainer, ), child: Icon( size: 14, MdiIcons.incognito, color: theme.colorScheme.onSecondaryContainer, ), ), ) : const SizedBox.shrink(), ), ), ], ); } return SizedBox( width: 38, height: 38, child: IconButton( tooltip: '点击登录', style: IconButton.styleFrom( padding: .zero, backgroundColor: theme.colorScheme.onInverseSurface, ), onPressed: mainController.toMinePage, icon: Icon( Icons.person_rounded, size: 22, color: theme.colorScheme.primary, ), ), ); }, ), ); } Widget msgBadge(MainController mainController) { return Obx( () { if (mainController.accountService.isLogin.value) { final count = mainController.msgUnReadCount.value; final isNumBadge = mainController.msgBadgeMode == .number; return IconButton( tooltip: '消息', onPressed: () { mainController ..msgUnReadCount.value = '' ..lastCheckUnreadAt = DateTime.now().millisecondsSinceEpoch; Get.toNamed('/whisper'); }, icon: Badge( isLabelVisible: mainController.msgBadgeMode != .hidden && count.isNotEmpty, alignment: isNumBadge ? const Alignment(0.0, -0.85) : const Alignment(1.0, -0.85), label: isNumBadge && count.isNotEmpty ? Text(count) : null, child: const Icon(Icons.notifications_none), ), ); } return const SizedBox.shrink(); }, ); }