You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

410 lines
12 KiB

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<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> 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,
),
),
),
);
}
}