import 'dart:async'; import 'dart:math'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:isekai_wiki/components/isekai_nav_bar.dart'; import 'package:isekai_wiki/components/recent_page_list.dart'; import 'package:isekai_wiki/models/user.dart'; import 'package:isekai_wiki/pages/tab_page.dart'; import 'package:web_smooth_scroll/web_smooth_scroll.dart'; import '../components/collapsed_tab.dart'; import '../global.dart'; import '../styles.dart'; enum HomeTabs { newest, followed } class HomeController extends GetxController with GetSingleTickerProviderStateMixin { double _navSearchButtonOffset = 90; var showNavSearchButton = false.obs; var isScrolling = false.obs; StreamController? streamController; StreamSubscription? _streamSubscription; late TabController tabController; late TabsPageController tabsPageController; final ScrollController scrollController = ScrollController(); VoidFutureCallback? onRefresh; @override void onInit() { tabController = TabController(length: 2, vsync: this); _navSearchButtonOffset = 48 * MediaQuery.of(Get.context!).textScaleFactor + 48; scrollController.addListener(() { if (scrollController.offset >= _navSearchButtonOffset && !showNavSearchButton.value) { showNavSearchButton.value = true; } else if (scrollController.offset < _navSearchButtonOffset && showNavSearchButton.value) { showNavSearchButton.value = false; } }); tabsPageController = Get.find(); tabsPageController.streamController.stream.listen((event) { if (event == "tap:home") { handleTapHomeTab(); } }); super.onInit(); } @override void dispose() { _streamSubscription?.cancel(); super.dispose(); } Future handleTapHomeTab() async { if (isScrolling.value) return; if (scrollController.offset != 0) { isScrolling.value = true; await scrollController.animateTo( 0, duration: const Duration(milliseconds: 500), curve: Curves.fastOutSlowIn, ); isScrolling.value = false; } } Future handleRefresh() async { if (onRefresh != null) { await onRefresh!(); } } } class HomeTab extends StatelessWidget { final VoidCallback? onSearchClick; const HomeTab({super.key, this.onSearchClick}); Widget _buildNotificationIconButton() { final uc = Get.find(); return Obx( () => AnimatedOpacity( opacity: uc.isLoggedIn ? 1.0 : 0.0, duration: const Duration(milliseconds: 100), child: CupertinoButton( padding: EdgeInsets.zero, child: const Icon(CupertinoIcons.bell, size: 26, color: Styles.themeNavTitleColor), onPressed: () {}, ), ), ); } Widget _buildSearchIconButton() { final c = Get.find(); return Obx( () => AnimatedOpacity( opacity: c.showNavSearchButton.value ? 1.0 : 0.0, duration: const Duration(milliseconds: 100), child: CupertinoButton( padding: EdgeInsets.zero, child: const Icon(CupertinoIcons.search, size: 26, color: Styles.themeNavTitleColor), onPressed: () { onSearchClick?.call(); }, ), ), ); } @override Widget build(BuildContext context) { final c = Get.put(HomeController()); return WebSmoothScroll( controller: c.scrollController, child: CustomScrollView( controller: c.scrollController, physics: const BouncingScrollPhysics( parent: AlwaysScrollableScrollPhysics(), ), slivers: [ IsekaiSliverNavigationBar( leading: _buildSearchIconButton(), backgroundColor: Styles.themeMainColor, brightness: Brightness.dark, largeTitle: const Text('首页', style: TextStyle(color: Styles.themeNavTitleColor)), border: Border.all(style: BorderStyle.none), trailing: _buildNotificationIconButton(), ), SliverPersistentHeader( delegate: _SliverAppBarDelegate( minHeight: 48, maxHeight: 48, child: Container( decoration: const BoxDecoration( color: Styles.themeMainColor, boxShadow: [ BoxShadow( color: Styles.themeMainColor, blurRadius: 0.0, spreadRadius: 0.0, offset: Offset(0, -2), ), ], ), padding: const EdgeInsets.only(bottom: 10, left: 12, right: 12), child: CupertinoButton( color: Colors.white, padding: const EdgeInsets.all(0), onPressed: () { onSearchClick?.call(); }, child: Wrap( spacing: 14, alignment: WrapAlignment.spaceBetween, children: [ Container( padding: const EdgeInsets.all(1), child: const Icon(CupertinoIcons.search, color: Colors.black54), ), const Text("搜索页面...", textAlign: TextAlign.center, style: TextStyle(color: Colors.black54)) ], ), ), ), ), ), SliverPersistentHeader( pinned: true, delegate: _SliverAppBarDelegate( minHeight: 40.0, maxHeight: 40.0, child: Container( decoration: BoxDecoration(color: Colors.white, boxShadow: [ BoxShadow( color: Colors.grey.withOpacity(0.2), spreadRadius: 2, blurRadius: 4, offset: const Offset(0, 2), ) ]), child: Row( children: [ SizedBox( child: TabBar( isScrollable: true, controller: c.tabController, indicatorColor: Styles.themeMainColor, labelColor: Styles.themeMainColor, unselectedLabelColor: Colors.black45, tabs: const [CollapsedTabText('最新'), CollapsedTabText('关注')], onTap: (int selected) {}, ), ), const Expanded(child: Text('')), ], ), ), ), ), CupertinoSliverRefreshControl( onRefresh: c.handleRefresh, ), SliverSafeArea( top: false, bottom: false, minimum: const EdgeInsets.only(top: 12, bottom: 12), sliver: RecentPageList( scrollController: c.scrollController, ), ), ], ), ); } } class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate { _SliverAppBarDelegate({ required this.minHeight, required this.maxHeight, required this.child, }); final double minHeight; final double maxHeight; final Widget child; @override double get minExtent => minHeight; @override double get maxExtent => max(maxHeight, minHeight); @override Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) { return SizedBox.expand(child: child); } @override bool shouldRebuild(_SliverAppBarDelegate oldDelegate) { return maxHeight != oldDelegate.maxHeight || minHeight != oldDelegate.minHeight || child != oldDelegate.child; } }