import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_svg/svg.dart'; import 'package:get/get.dart'; import 'package:get/get_rx/src/rx_typedefs/rx_typedefs.dart'; import 'package:loopin/IM/im_service.dart'; import 'package:loopin/components/custom_sticky_header.dart'; import 'package:loopin/components/only_down_scroll_physics.dart'; import 'package:loopin/controller/video_module_controller.dart'; import 'package:nested_scroll_view_plus/nested_scroll_view_plus.dart'; import 'package:shirne_dialog/shirne_dialog.dart'; import '../../utils/common.dart'; class PageParams { int page; int pageSize; bool isLoading; bool hasMore; PageParams({ this.page = 1, this.pageSize = 10, this.isLoading = false, this.hasMore = true, }); } class MyPage extends StatefulWidget { const MyPage({super.key}); @override State createState() => MyPageState(); } class MyPageState extends State with SingleTickerProviderStateMixin { late RxInt currentTabIndex = 0.obs; late RxList items = [].obs; late RxList favoriteItems = [].obs; late RxMap userInfo = {}.obs; RxBool get shouldFixHeader => (currentTabIndex.value == 0 && items.isEmpty) || (currentTabIndex.value == 1 && favoriteItems.isEmpty) ? true.obs : false.obs; List tabList = [ {'name': "作品", 'badge': 99}, {'name': "喜欢"}, ]; late PageParams itemsParams; late PageParams favoriteParams; late TabController tabController; late ScrollController scrollController; late Callback tabListener; late Callback scrollListener; @override void initState() { super.initState(); itemsParams = PageParams(); favoriteParams = PageParams(); initControllers(); scrollListener = () { final pos = scrollController.position; final isNearBottom = pos.pixels >= pos.maxScrollExtent - 100; if (!isNearBottom) return; if (currentTabIndex.value == 0 && !itemsParams.isLoading && itemsParams.hasMore) { loadData(0); } else if (currentTabIndex.value == 1 && !favoriteParams.isLoading && favoriteParams.hasMore) { loadData(1); } }; scrollController.addListener(scrollListener); tabListener = () { currentTabIndex.value = tabController.index; scrollController.animateTo(0, duration: const Duration(milliseconds: 300), curve: Curves.easeIn); if (tabController.index == 0 && items.isEmpty) { loadData(0); } else if (tabController.index == 1 && favoriteItems.isEmpty) { loadData(1); } }; tabController.addListener(tabListener); loadData(0); } @override void dispose() { tabController.removeListener(tabListener); scrollController.removeListener(scrollListener); tabController.dispose(); scrollController.dispose(); super.dispose(); } void loadData([int? tabIndex]) async { final index = tabIndex ?? currentTabIndex.value; if (index == 0) { if (itemsParams.isLoading || !itemsParams.hasMore) return; itemsParams.isLoading = true; await Future.delayed(const Duration(seconds: 1)); // 模拟生成新数据 List newItems = List.generate( itemsParams.pageSize, (i) => '作品 ${(itemsParams.page - 1) * itemsParams.pageSize + i + 1}', ); // 模拟判断是否还有更多数据 if (itemsParams.page >= 2) { itemsParams.hasMore = false; } // 添加新数据,触发响应式更新 items.addAll(newItems); // 页码加一 itemsParams.page++; itemsParams.isLoading = false; } else if (index == 1) { // 喜欢列表同理 if (favoriteParams.isLoading || !favoriteParams.hasMore) return; favoriteParams.isLoading = true; await Future.delayed(const Duration(seconds: 1)); List newFavorites = List.generate( favoriteParams.pageSize, (i) => '喜欢 ${(favoriteParams.page - 1) * favoriteParams.pageSize + i + 1}', ); if (favoriteParams.page >= 2) { favoriteParams.hasMore = false; } favoriteItems.addAll(newFavorites); favoriteParams.page++; favoriteParams.isLoading = false; } } void initControllers() { tabController = TabController(initialIndex: 0, length: tabList.length, vsync: this); scrollController = ScrollController(); } // 初始化页面数据 void refreshData() { if (!mounted) { logger.i('未挂载'); return; } if (!Common.isLogin()) return; itemsParams = PageParams(); favoriteParams = PageParams(); currentTabIndex.value = 0; items.clear(); favoriteItems.clear(); scrollController.animateTo(0, duration: const Duration(milliseconds: 100), curve: Curves.easeIn); selfInfo(); loadData(); } // 获取当前登录用户基本信息 void selfInfo() async { final resIm = await ImService.instance.selfInfo(); if (resIm.success) { for (var user in resIm.data ?? []) { logger.i(user.toLogString()); } } } // 二维码名片弹窗 void qrcodeAlertDialog(BuildContext context) { showDialog( context: context, builder: (context) { return UnconstrainedBox( constrainedAxis: Axis.vertical, child: SizedBox( width: 350.0, child: AlertDialog( contentPadding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 20.0), backgroundColor: const Color(0xff07c160), surfaceTintColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15.0)), content: Padding( padding: const EdgeInsets.symmetric(horizontal: 10.0), child: Column( mainAxisSize: MainAxisSize.min, children: [ Image.asset('assets/images/pic1.jpg', width: 250.0, fit: BoxFit.contain), const SizedBox(height: 15.0), const Text('扫一扫,加好友', style: TextStyle( color: Colors.white38, fontSize: 14.0, )), ], ), ), ), ), ); }, ); } // 退出登录弹窗 void showLogoutDialog(BuildContext context) { showDialog( context: context, builder: (context) { return AlertDialog( content: const Text('确认退出当前账号吗?', style: TextStyle(fontSize: 16.0)), backgroundColor: Colors.white, surfaceTintColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15.0)), elevation: 2.0, actionsPadding: const EdgeInsets.all(15.0), actions: [ TextButton( onPressed: () { Get.back(); }, child: const Text('取消', style: TextStyle(color: Colors.black54)), ), TextButton(onPressed: handleLogout, child: const Text('退出登录', style: TextStyle(color: Colors.red))), ], ); }, ); } // 退出登录 void handleLogout() async { final loginRes = await ImService.instance.logout(); if (loginRes.success) { // 清除存储信息 Common.logout(); // 初始化视频 final videoController = Get.find(); videoController.init(); Get.back(); } } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: const Color(0xFFFAF6F9), body: Obx(() { return NestedScrollViewPlus( controller: scrollController, physics: shouldFixHeader.value ? const OnlyDownScrollPhysics(parent: AlwaysScrollableScrollPhysics()) : const AlwaysScrollableScrollPhysics(), overscrollBehavior: OverscrollBehavior.outer, headerSliverBuilder: (context, innerBoxIsScrolled) { return [ SliverAppBar( backgroundColor: Colors.transparent, surfaceTintColor: Colors.transparent, expandedHeight: 180.0, collapsedHeight: 120.0, pinned: true, stretch: true, onStretchTrigger: () async { logger.i('触发 stretch 拉伸'); // 加载刷新逻辑 }, actions: [ _buildIcon('assets/images/svg/service.svg', () { logger.i('点击客服按钮'); }), const SizedBox(width: 8.0), _buildIcon('assets/images/svg/setting.svg', () { logger.i('点击设置按钮'); }), const SizedBox(width: 10.0), ], flexibleSpace: _buildFlexibleSpace(), ), SliverToBoxAdapter( child: Padding( padding: const EdgeInsets.all(10.0), child: Column( children: [ _buildStatsCard(), const SizedBox(height: 10.0), _buildOrderCard(context), const SizedBox(height: 10.0), ], ), ), ), SliverPersistentHeader( pinned: true, delegate: CustomStickyHeader( child: PreferredSize( preferredSize: const Size.fromHeight(48.0), child: Container( color: Colors.white, child: TabBar( controller: tabController, tabs: tabList.map((item) { return Tab( child: Badge.count( backgroundColor: Colors.red, count: item['badge'] ?? 0, isLabelVisible: item['badge'] != null, alignment: Alignment.topRight, offset: const Offset(14, -6), child: Text(item['name'], style: const TextStyle(fontWeight: FontWeight.bold)), ), ); }).toList(), isScrollable: false, overlayColor: WidgetStateProperty.all(Colors.transparent), unselectedLabelColor: Colors.black87, labelColor: const Color(0xFFFF5000), indicator: const UnderlineTabIndicator(borderSide: BorderSide(color: Color(0xFFFF5000), width: 2.0)), indicatorSize: TabBarIndicatorSize.tab, unselectedLabelStyle: const TextStyle(fontSize: 16.0, fontFamily: 'Microsoft YaHei'), labelStyle: const TextStyle(fontSize: 18.0, fontFamily: 'Microsoft YaHei', fontWeight: FontWeight.bold), dividerHeight: 0, padding: const EdgeInsets.symmetric(horizontal: 10.0), labelPadding: const EdgeInsets.symmetric(horizontal: 15.0), ), ), ), ), ), ]; }, body: TabBarView( controller: tabController, children: [ // Tab 1: Obx(() => _buildGridTab(0)), // Tab 2: Obx(() => _buildGridTab(1)) ], ), ); }), ); } // 空状态提示 Widget emptyTip(String text) { return CustomScrollView( physics: const OnlyDownScrollPhysics(), slivers: [ SliverFillRemaining( hasScrollBody: false, child: Align( alignment: Alignment.topCenter, child: Padding( padding: const EdgeInsets.only(top: 50.0), child: Column( mainAxisSize: MainAxisSize.min, children: [ Image.asset('assets/images/empty.png', width: 100.0), const SizedBox(height: 8.0), Text( text, style: const TextStyle(color: Colors.grey, fontSize: 13.0), ), ], ), ), ), ), ], ); } Widget _buildGridTab(int tabIndex) { final listToShow = tabIndex == 0 ? items : favoriteItems; final params = tabIndex == 0 ? itemsParams : favoriteParams; if (listToShow.isEmpty) { return emptyTip('暂无相关数据'); } return CustomScrollView( slivers: [ SliverPadding( padding: const EdgeInsets.all(10.0), sliver: SliverGrid( delegate: SliverChildBuilderDelegate( (context, index) { return Container( decoration: BoxDecoration( color: Colors.blue[100 * ((index % 8) + 1)], borderRadius: BorderRadius.circular(10.0), ), alignment: Alignment.center, child: Text(listToShow[index], style: const TextStyle(fontWeight: FontWeight.bold)), ); }, childCount: listToShow.length, ), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, crossAxisSpacing: 10.0, mainAxisSpacing: 10.0, childAspectRatio: 1.0, ), ), ), SliverToBoxAdapter( child: Padding( padding: const EdgeInsets.symmetric(vertical: 20.0), child: Center( child: params.hasMore ? const CircularProgressIndicator() : const Text('没有更多数据了'), ), ), ), ], ); } Widget _buildIcon(String assetPath, VoidCallback onTap) { return InkWell( onTap: onTap, child: Container( padding: const EdgeInsets.symmetric(horizontal: 5.0, vertical: 5.0), decoration: BoxDecoration(color: Colors.black.withAlpha((0.3 * 255).round()), borderRadius: BorderRadius.circular(20.0)), child: SvgPicture.asset(assetPath, height: 20.0, width: 20.0, colorFilter: const ColorFilter.mode(Colors.white70, BlendMode.srcIn)), ), ); } Widget _buildFlexibleSpace() { return LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { final double maxHeight = 180.0; final double minHeight = 100.0; final double currentHeight = constraints.maxHeight; double ratio = (currentHeight - minHeight) / (maxHeight - minHeight); ratio = ratio.clamp(0.0, 1.0); return Stack( fit: StackFit.expand, children: [ Positioned.fill(child: Opacity(opacity: 1.0, child: Image.asset('assets/images/pic2.jpg', fit: BoxFit.cover))), Positioned( left: 15.0, bottom: 0, right: 15.0, child: Container( padding: const EdgeInsets.symmetric(vertical: 10.0), child: Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ ClipOval(child: Image.asset('assets/images/avatar/img11.jpg', height: 60.0, width: 60.0, fit: BoxFit.cover)), const SizedBox(width: 15.0), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Container( padding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 5.0), decoration: BoxDecoration(color: Colors.black.withAlpha((0.3 * 255).round()), borderRadius: BorderRadius.circular(20.0)), child: const Text( '新用户2025', style: TextStyle(fontSize: 20.0, fontWeight: FontWeight.bold, fontFamily: 'Arial', color: Colors.white), ), ), const SizedBox(width: 8.0), InkWell( onTap: () { qrcodeAlertDialog(context); }, child: Container( padding: const EdgeInsets.symmetric(horizontal: 5.0, vertical: 5.0), decoration: BoxDecoration(color: Colors.black.withAlpha((0.3 * 255).round()), borderRadius: BorderRadius.circular(20.0)), child: const Icon(Icons.qr_code_outlined, size: 18.0, color: Colors.white), ), ), ], ), const SizedBox(height: 8.0), Container( padding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 5.0), decoration: BoxDecoration(color: Colors.black.withAlpha((0.3 * 255).round()), borderRadius: BorderRadius.circular(20.0)), child: InkWell( onTap: () { logger.i('点击个人简介'); Clipboard.setData(const ClipboardData(text: '1234')); MyDialog.toast('ID已复制', icon: const Icon(Icons.check_circle), style: ToastStyle(backgroundColor: Colors.green.withAlpha(200))); }, child: const Text('ID:32938293892839232', style: TextStyle(fontSize: 12.0, color: Colors.white)), ), ), ], ), ), ], ), ), ), ], ); }, ); } Widget _buildStatsCard() { return Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(15.0), boxShadow: [BoxShadow(color: Colors.black.withAlpha(10), offset: const Offset(0.0, 1.0), blurRadius: 2.0, spreadRadius: 0.0)], ), clipBehavior: Clip.antiAlias, child: Padding( padding: const EdgeInsets.all(10.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ Column(children: const [Text('9999', style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold)), SizedBox(height: 3.0), Text('获赞')]), Column(children: const [Text('25', style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold)), SizedBox(height: 3.0), Text('互关')]), Column(children: const [Text('11', style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold)), SizedBox(height: 3.0), Text('关注')]), Column(children: const [Text('10', style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold)), SizedBox(height: 3.0), Text('粉丝')]), ], ), ), ); } Widget _buildOrderCard(BuildContext context) { return Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(15.0), boxShadow: [BoxShadow(color: Colors.black.withAlpha(10), offset: const Offset(0.0, 1.0), blurRadius: 2.0, spreadRadius: 0.0)], ), clipBehavior: Clip.antiAlias, child: Column( children: [ GestureDetector( child: Container( padding: const EdgeInsets.all(10.0), child: Row( children: [ Row( children: [ Image.asset('assets/images/more.png', width: 16.0), const SizedBox(width: 5.0), const Text('常用功能', style: TextStyle(fontSize: 15.0, fontWeight: FontWeight.bold)), ], ), const Spacer(), const Text('全部', style: TextStyle(color: Colors.grey, fontSize: 12.0)), const Icon(Icons.arrow_forward_ios_rounded, color: Colors.grey, size: 12.0), ], ), ), onTap: () {}, ), Padding( padding: const EdgeInsets.all(10.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ _buildOrderIcon('assets/images/ico_order.png', '订单', () { Get.toNamed('/order'); }), _buildOrderIcon('assets/images/ico_dhx.png', '余额', () { showLogoutDialog(context); }), _buildOrderIcon('assets/images/ico_sh.png', '提现', () {}), _buildOrderIcon('assets/images/ico_tgm.png', '推广码', () {}), ], ), ), ], ), ); } Widget _buildOrderIcon(String assetPath, String label, VoidCallback onTap) { return GestureDetector( onTap: onTap, child: Column( children: [ Image.asset(assetPath, width: 24.0), const SizedBox(height: 3.0), Text(label), ], ), ); } }