import 'package:dating_touchme_app/generated/assets.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; class HomePage extends StatefulWidget { const HomePage({super.key}); @override State createState() => _HomePageState(); } class _HomePageState extends State with AutomaticKeepAliveClientMixin { int selectedTabIndex = 0; // 0: 推荐 1: 同城 // 简单的示例数据结构 final List<_FeedItem> recommendFeed = [ _FeedItem(type: _FeedType.withImages), _FeedItem(type: _FeedType.plain), _FeedItem(type: _FeedType.live), _FeedItem(type: _FeedType.withImages), ]; final List<_FeedItem> nearbyFeed = [ _FeedItem(type: _FeedType.plain), _FeedItem(type: _FeedType.withImages), _FeedItem(type: _FeedType.live), ]; @override Widget build(BuildContext context) { super.build(context); final List<_FeedItem> dataSource = selectedTabIndex == 0 ? recommendFeed : nearbyFeed; return Scaffold( backgroundColor: Colors.white, appBar: _buildAppBar(), body: ListView.separated( physics: const BouncingScrollPhysics(), itemBuilder: (context, index) { final item = dataSource[index]; switch (item.type) { case _FeedType.withImages: return ImagesCard(); case _FeedType.plain: return PlainCard(); case _FeedType.live: return LiveCard(); } }, separatorBuilder: (context, index) => const SizedBox(height: 12), itemCount: dataSource.length, padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), ), ); } PreferredSizeWidget _buildAppBar() { return AppBar( backgroundColor: Colors.white, elevation: 0, centerTitle: true, toolbarHeight: 56, titleSpacing: 0, title: Row( mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ _buildTabButton(title: '推荐', index: 0), const SizedBox(width: 28), _buildTabButton(title: '同城', index: 1), ], ), bottom: const PreferredSize( preferredSize: Size.fromHeight(4), child: SizedBox(height: 4), ), ); } Widget _buildTabButton({required String title, required int index}) { final bool selected = selectedTabIndex == index; return GestureDetector( onTap: () { if (selectedTabIndex != index) { setState(() { selectedTabIndex = index; }); } }, child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.center, children: [ Text( title, style: TextStyle( fontSize: selected ? 19 : 17, fontWeight: selected ? FontWeight.w700 : FontWeight.w400, color: selected ? const Color(0xFF333333) : const Color(0xFF999999), ), ), const SizedBox(height: 6), selected ? Image.asset( Assets.imagesTabChangeIcon, width: 32, height: 8, ) : const SizedBox(height: 8), ], ), ); } // 下面将卡片组件拆分为独立Widget,保持HomePage更简洁 @override bool get wantKeepAlive => true; } enum _FeedType { withImages, plain, live } class _FeedItem { final _FeedType type; const _FeedItem({required this.type}); } // 通用头部组件:头像/昵称/在线/认证/Hi/直播中徽标 class _CardHeader extends StatelessWidget { final bool showHi; // 是否在头像上显示直播态(紫色描边 + 头像角标) final bool liveOverlayOnAvatar; const _CardHeader({this.showHi = false, this.liveOverlayOnAvatar = false}); @override Widget build(BuildContext context) { return Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ // 头像 + 在线小圆点 Stack( children: [ // 头像 + 可选紫色描边 Container( padding: liveOverlayOnAvatar ? const EdgeInsets.all(2) : EdgeInsets.zero, decoration: liveOverlayOnAvatar ? BoxDecoration( borderRadius: BorderRadius.circular(32), border: Border.all(color: const Color(0xFFB58BFF), width: 2), ) : null, child: ClipRRect( borderRadius: BorderRadius.circular(30), child: Image.asset( Assets.imagesAvatarsExample, width: 60, height: 60, fit: BoxFit.cover, ), ), ), Positioned( left: 6, bottom: 6, child: Container( width: 12, height: 12, decoration: BoxDecoration( color: const Color(0xFF2ED573), shape: BoxShape.circle, boxShadow: const [ BoxShadow(color: Color(0x332ED573), blurRadius: 4, offset: Offset(0, 2)), ], ), ), ), if (liveOverlayOnAvatar) Positioned( left: -6, bottom: -6, child: Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), decoration: BoxDecoration( color: const Color(0xFF7A5CFF), borderRadius: BorderRadius.circular(16), boxShadow: const [ BoxShadow(color: Color(0x337A5CFF), blurRadius: 6, offset: Offset(0, 3)), ], ), child: const Text( '直播中', style: TextStyle(color: Colors.white, fontSize: 12, fontWeight: FontWeight.w600), ), ), ), ], ), const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const Text( '林园园', style: TextStyle( fontSize: 15, fontWeight: FontWeight.w500, color: Color.fromRGBO(51, 51, 51, 1), ), ), const SizedBox(width: 6), // 在线徽标 Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), decoration: BoxDecoration( color: Color.fromRGBO(234, 255, 219, 1), borderRadius: BorderRadius.circular(12), ), child: const Text( '在线', style: TextStyle(fontSize: 12, color: Color.fromRGBO(38, 199, 124, 1)), ), ), const SizedBox(width: 6), // 实名徽, Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), decoration: BoxDecoration( color: const Color(0xFFF3E9FF), borderRadius: BorderRadius.circular(12), ), child: Row( children: [ Image.asset(Assets.imagesVerifiedIcon, width: 14, height: 12), const SizedBox(width: 4), const Text( '实名', style: TextStyle(fontSize: 9, color: Color.fromRGBO(160, 92, 255, 1)), ), ], ), ), ], ), const SizedBox(height: 4), const Text( '23岁 · 白云区', style: TextStyle(fontSize: 12, color: Color.fromRGBO(51, 51, 51, 1)), ), ], ), ), if (showHi) Image.asset(Assets.imagesHiIcon, width: 40, height: 20), ], ); } } class PlainCard extends StatelessWidget { @override Widget build(BuildContext context) { return Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), boxShadow: const [ BoxShadow( color: Color(0x14000000), blurRadius: 8, offset: Offset(0, 4), ), ], ), padding: const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: const [ _CardHeader(), SizedBox(height: 8), Text( '我想找一个有缘的异性,快来联系我吧快来……', style: TextStyle(fontSize: 13, color: Color(0xFF666666)), ), ], ), ); } } class ImagesCard extends StatelessWidget { const ImagesCard(); @override Widget build(BuildContext context) { return Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: const [ BoxShadow( color: Color(0x14000000), blurRadius: 12, offset: Offset(0, 6), ), ], ), padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const _CardHeader(showHi: true), const SizedBox(height: 10), const Text( '我想找一个有缘的异性,快来联系我吧快来……', style: TextStyle(fontSize: 16, color: Color(0xFF666666)), ), const SizedBox(height: 16), Row( children: const [ _BorderedImage(), SizedBox(width: 12), _BorderedImage(), SizedBox(width: 12), _BorderedImage(), ], ), ], ), ); } } class LiveCard extends StatelessWidget { @override Widget build(BuildContext context) { return Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), boxShadow: const [ BoxShadow( color: Color(0x14000000), blurRadius: 8, offset: Offset(0, 4), ), ], ), padding: const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const _CardHeader(liveOverlayOnAvatar: true), const SizedBox(height: 8), const Text( '正在直播,快来互动~', style: TextStyle(fontSize: 13, color: Color(0xFF666666)), ), const SizedBox(height: 10), Align( alignment: Alignment.centerLeft, child: Container( padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8), decoration: BoxDecoration( gradient: const LinearGradient( colors: [Color(0xFF2D7CFF), Color(0xFF7A5CFF)], ), borderRadius: BorderRadius.circular(18), ), child: const Text( '进入直播间', style: TextStyle(color: Colors.white, fontSize: 14, fontWeight: FontWeight.w600), ), ), ), ], ), ); } } // 占位组件已被带描边版本替代,保留空实现会触发未使用告警,故移除 // 带蓝色描边的图片占位 class _BorderedImage extends StatelessWidget { const _BorderedImage(); @override Widget build(BuildContext context) { return Expanded( child: AspectRatio( aspectRatio: 1.05, child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(14), border: Border.all(color: const Color(0xFF5D7BFF), width: 2), ), clipBehavior: Clip.antiAlias, child: Image.asset( Assets.imagesDiscoverPre, fit: BoxFit.cover, ), ), ), ); } }