import 'package:dating_touchme_app/generated/assets.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.offline, content: '我想找一个有缘的异性,快来联系我吧快来……'), // 不设置图片 _FeedItem(type: _FeedType.online, content: '我想找一个有缘的异性,快来联系我吧快来……'), _FeedItem(type: _FeedType.live, content: '正在直播,快来互动~'), _FeedItem(type: _FeedType.offline, content: '大家好,很高兴认识新朋友!'), // 不设置图片 ]; final List<_FeedItem> nearbyFeed = [ _FeedItem(type: _FeedType.online, content: '同城的朋友,有空一起出来玩呀~'), _FeedItem(type: _FeedType.offline, content: '周末有什么好去处推荐吗?'), // 不设置图片 _FeedItem(type: _FeedType.live, content: '正在直播,快来互动~'), ]; @override Widget build(BuildContext context) { super.build(context); final List<_FeedItem> dataSource = selectedTabIndex == 0 ? recommendFeed : nearbyFeed; // 获取 AppBar 高度(状态栏 + toolbar + bottom) final statusBarHeight = MediaQuery.of(context).padding.top; final appBarHeight = statusBarHeight + 56 + 4; // toolbarHeight 56 + bottom 4 // 获取底部安全区域高度和 tabbar 高度(约64) final bottomPadding = MediaQuery.of(context).padding.bottom; final tabBarHeight = 64.0; final totalBottomPadding = bottomPadding + tabBarHeight; return Stack( children: [ // 背景图 - 覆盖整个屏幕包括状态栏和导航栏 Image.asset( Assets.imagesBgInformation, fit: BoxFit.cover, width: double.infinity, height: double.infinity, ), Scaffold( backgroundColor: Colors.transparent, appBar: _buildAppBar(), body: SafeArea( child: ListView.separated( physics: const AlwaysScrollableScrollPhysics(parent: BouncingScrollPhysics()), itemBuilder: (context, index) { final item = dataSource[index]; return ContentCard(item: item); }, separatorBuilder: (context, index) => const SizedBox(height: 12), itemCount: dataSource.length, padding: EdgeInsets.only( left: 12, right: 12, top: 12, // 从 AppBar 下方开始 bottom: totalBottomPadding + 12, // 避免被 tabbar 遮挡 ), ), ), ), ], ); } PreferredSizeWidget _buildAppBar() { return AppBar( backgroundColor: Colors.transparent, 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), ], ), // actions: [ // Padding( // padding: const EdgeInsets.only(right: 16), // child: Image.asset( // Assets.imagesOnlineIcon, // width: 20, // height: 20, // ), // ), // ], 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 { online, // 在线 + HI按钮 offline, // 下线 + 发消息按钮 live // 直播 + 直播间按钮(放在HI按钮位置) } // 接口数据模型 class _FeedItem { final _FeedType type; final String? nickname; // 昵称 final String? avatar; // 头像URL final int? age; // 年龄 final String? location; // 地区 final bool? isVerified; // 是否实名认证 final String? content; // 内容文本 final List? images; // 图片列表 // 从接口数据构造 const _FeedItem({ required this.type, this.nickname = '林园园', this.avatar, this.age = 23, this.location = '白云区', this.isVerified = true, this.content = '我想找一个有缘的异性,快来联系我吧快来……', this.images, }); // 模拟从API响应创建实例的工厂方法 factory _FeedItem.fromApi(Map apiData) { // 根据接口参数判断类型 final bool isLive = apiData['isLive'] ?? false; final bool isOnline = apiData['isOnline'] ?? false; _FeedType type; if (isLive) { type = _FeedType.live; } else if (isOnline) { type = _FeedType.online; } else { type = _FeedType.offline; } return _FeedItem( type: type, nickname: apiData['nickname'], avatar: apiData['avatar'], age: apiData['age'], location: apiData['location'], isVerified: apiData['isVerified'], content: apiData['content'], images: List.from(apiData['images'] ?? []), // viewerCount已移除 ); } } // 通用头部组件:头像/昵称/在线/认证/Hi/直播中徽标 class _CardHeader extends StatelessWidget { final _FeedItem item; const _CardHeader({required this.item}); @override Widget build(BuildContext context) { final bool isLive = item.type == _FeedType.live; final bool isOnline = item.type == _FeedType.online; return Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ // 头像 + 状态小圆点 Stack( children: [ // 头像 + 可选紫色描边 Container( padding: isLive ? const EdgeInsets.all(2) : EdgeInsets.zero, decoration: isLive ? BoxDecoration( borderRadius: BorderRadius.circular(32), border: Border.all(color: const Color(0xFFB58BFF), width: 2), ) : null, child: ClipRRect( borderRadius: BorderRadius.circular(30), child: item.avatar != null ? Image.network( item.avatar!, width: 60, height: 60, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) => Image.asset( Assets.imagesAvatarsExample, width: 60, height: 60, fit: BoxFit.cover, ), ) : Image.asset( Assets.imagesAvatarsExample, width: 60, height: 60, fit: BoxFit.cover, ), ), ), if (isOnline) Positioned( right: 6, bottom: 1, child: Container( width: 12, height: 12, decoration: BoxDecoration( color: isOnline ? const Color(0xFF2ED573) : const Color(0xFFCCCCCC), // 在线绿色,离线灰色 shape: BoxShape.circle, boxShadow: isOnline ? const [ BoxShadow(color: Color(0x332ED573), blurRadius: 4, offset: Offset(0, 2)), ] : [], ), ), ), if (isLive) Positioned( bottom: 0, left: 0, right: 0, child: Container( width: 37, height: 14, alignment: Alignment.center, // 关键:让子内容居中 decoration: BoxDecoration( image: DecorationImage( image: AssetImage(Assets.imagesBtnBgIcon), ), ), child: const Text( '直播中', style: TextStyle(color: Colors.white, fontSize: 8, fontWeight: FontWeight.w600), ), ), ), ], ), const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Text( item.nickname ?? '用户', style: const TextStyle( fontSize: 15, fontWeight: FontWeight.w500, color: Color.fromRGBO(51, 51, 51, 1), ), ), const SizedBox(width: 6), // 在线/离线徽标 if (isOnline == true) Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), decoration: BoxDecoration( color: Color.fromRGBO(234, 255, 219, 1) , borderRadius: BorderRadius.circular(12), ), child: Text( isOnline ? '在线' : '', style: TextStyle(fontSize: 9, color: Color.fromRGBO(38, 199, 124, 1) ), ), ), const SizedBox(width: 6), // 实名徽标 if (item.isVerified == true) 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(width: 6), // 直播状态下显示视频相亲中标签 if (isLive) Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), decoration: BoxDecoration( color: Color.fromRGBO(234, 255, 219, 1), borderRadius: BorderRadius.circular(12), ), child: Text( '视频相亲中', style: TextStyle(fontSize: 9, color: Color.fromRGBO(38, 199, 124, 1)), ), ), ], ), const SizedBox(height: 4), Text( '${item.age}岁 · ${item.location}', style: TextStyle(fontSize: 12, color: Color.fromRGBO(51, 51, 51, 1)), ), ], ), ), // 根据不同状态显示不同按钮 - 都放在HI按钮相同位置 InkWell( onTap: () { // 点击事件处理 if (isLive) { // 进入直播间逻辑 print('进入直播间'); } else if (isOnline) { // HI按钮点击逻辑 print('HI点击'); } else { // 发消息按钮点击逻辑 print('发送消息'); } }, child: Image.asset( _getButtonImage(), width: item.type == _FeedType.live ? 60 : 40, height: 20, ), ), ], ); } String _getButtonImage() { switch (item.type) { case _FeedType.offline: return Assets.imagesSendMessageIcon; // 下线显示发消息按钮(放在HI位置) case _FeedType.live: return Assets.imagesLiveIcon; // 直播显示直播间按钮(放在HI位置) case _FeedType.online: default: return Assets.imagesHiIcon; // 在线显示HI按钮 } } } // 统一的内容卡片组件,根据接口数据显示不同状态 class ContentCard extends StatelessWidget { final _FeedItem item; const ContentCard({required this.item}); @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: [ // 头部:头像、昵称、在线状态、按钮等 - 所有按钮都放在HI位置 _CardHeader(item: item), const SizedBox(height: 8), // 内容区域 - 根据类型显示不同内容 _buildContent(), ], ), ); } Widget _buildContent() { final List contentWidgets = []; // 内容文本 if (item.content != null && item.content!.isNotEmpty) { contentWidgets.add( Text( item.content!, style: TextStyle( fontSize: 13, color: Color.fromRGBO(51, 51, 51, 0.6), ), ), ); } // 根据接口返回的图片列表动态显示图片 if (item.images != null && item.images!.isNotEmpty) { contentWidgets.add(const SizedBox(height: 16)); // 根据图片数量决定显示方式 if (item.images!.length == 1) { // 单张图片:显示大图 contentWidgets.add( _NetworkImageWidget(imageUrl: item.images![0], aspectRatio: 2.5), ); } else { // 多张图片:最多显示3张 contentWidgets.add( Row( children: item.images!.take(3).toList().asMap().entries.map((entry) { int index = entry.key; String imageUrl = entry.value; return Expanded( child: Padding( padding: EdgeInsets.only(left: index > 0 ? 12 : 0), child: _NetworkImageWidget(imageUrl: imageUrl, aspectRatio: 1.05), ), ); }).toList(), ), ); } } return Column( crossAxisAlignment: CrossAxisAlignment.start, children: contentWidgets, ); } } // 占位组件已被带描边版本替代,保留空实现会触发未使用告警,故移除 // 网络图片组件,支持加载网络图片并显示占位图 - 修复可能的闪退问题 class _NetworkImageWidget extends StatelessWidget { final String imageUrl; final double aspectRatio; const _NetworkImageWidget({required this.imageUrl, required this.aspectRatio}); @override Widget build(BuildContext context) { return AspectRatio( aspectRatio: aspectRatio, child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(14), ), clipBehavior: Clip.antiAlias, child: Image.asset( // 暂时使用本地图片替代网络图片,避免网络加载失败导致闪退 Assets.imagesExampleContent, fit: BoxFit.cover, ), ), ); } }