rwadurian/frontend/mobile-app/lib/features/ranking/presentation/pages/ranking_page.dart

631 lines
18 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../../core/di/injection_container.dart';
import '../../../../core/services/leaderboard_service.dart';
/// 排行榜类型枚举
enum RankingType { daily, weekly, monthly }
/// 筛选类型枚举
enum FilterType { all, previousDay, previousWeek, previousMonth }
/// 排行榜数据模型
class RankingItem {
final int rank;
final String name;
final String province;
final String city;
final int teamPlantingAmount;
final String? avatarUrl;
RankingItem({
required this.rank,
required this.name,
required this.province,
required this.city,
required this.teamPlantingAmount,
this.avatarUrl,
});
}
/// 榜单状态 Provider
final leaderboardStatusProvider = FutureProvider<LeaderboardStatus>((ref) async {
final leaderboardService = ref.watch(leaderboardServiceProvider);
return leaderboardService.getStatus();
});
/// 龙虎榜页面 - 显示用户排行榜
/// 支持日榜、周榜、月榜切换,以及筛选功能
class RankingPage extends ConsumerStatefulWidget {
const RankingPage({super.key});
@override
ConsumerState<RankingPage> createState() => _RankingPageState();
}
class _RankingPageState extends ConsumerState<RankingPage> {
// 当前选中的排行榜类型
RankingType _selectedRankingType = RankingType.daily;
// 当前选中的筛选类型
FilterType _selectedFilterType = FilterType.all;
// 模拟排行榜数据
final List<RankingItem> _mockRankingData = [
RankingItem(
rank: 1,
name: '环保先锋',
province: '广东',
city: '深圳',
teamPlantingAmount: 1234567,
),
RankingItem(
rank: 2,
name: '绿色卫士',
province: '广东',
city: '深圳',
teamPlantingAmount: 1123456,
),
RankingItem(
rank: 3,
name: '低碳达人',
province: '广东',
city: '深圳',
teamPlantingAmount: 987654,
),
RankingItem(
rank: 4,
name: '节能小能手',
province: '广东',
city: '深圳',
teamPlantingAmount: 876543,
),
RankingItem(
rank: 5,
name: '分类高手',
province: '广东',
city: '深圳',
teamPlantingAmount: 765432,
),
];
/// 切换排行榜类型
void _selectRankingType(RankingType type) {
setState(() {
_selectedRankingType = type;
});
}
/// 切换筛选类型
void _selectFilterType(FilterType type) {
setState(() {
_selectedFilterType = type;
});
}
/// 格式化数字(添加千分位)
String _formatNumber(int number) {
return number.toString().replaceAllMapped(
RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))'),
(Match m) => '${m[1]},',
);
}
/// 检查当前选中的榜单是否开启
bool _isCurrentBoardEnabled(LeaderboardStatus status) {
switch (_selectedRankingType) {
case RankingType.daily:
return status.dailyEnabled;
case RankingType.weekly:
return status.weeklyEnabled;
case RankingType.monthly:
return status.monthlyEnabled;
}
}
@override
Widget build(BuildContext context) {
final statusAsync = ref.watch(leaderboardStatusProvider);
return Scaffold(
body: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color(0xFFFFF5E6), // 浅米色
Color(0xFFEAE0C8), // 浅橙色
],
),
),
child: statusAsync.when(
data: (status) => _buildContent(status),
loading: () => _buildLoadingContent(),
error: (error, stack) => _buildContent(LeaderboardStatus.allDisabled()),
),
),
);
}
/// 构建加载中内容
Widget _buildLoadingContent() {
return Column(
children: [
_buildHeaderWithStatus(null),
const Expanded(
child: Center(
child: CircularProgressIndicator(
color: Color(0xFFD4AF37),
),
),
),
],
);
}
/// 构建主内容
Widget _buildContent(LeaderboardStatus status) {
final isCurrentEnabled = _isCurrentBoardEnabled(status);
return Column(
children: [
// 顶部标题和Tab栏
_buildHeaderWithStatus(status),
// 如果当前榜单开启,显示筛选栏和列表
if (isCurrentEnabled) ...[
_buildFilterBar(),
Expanded(child: _buildRankingList()),
] else ...[
// 显示待开启状态
Expanded(child: _buildDisabledState()),
],
],
);
}
/// 构建待开启状态
Widget _buildDisabledState() {
String boardName;
switch (_selectedRankingType) {
case RankingType.daily:
boardName = '日榜';
break;
case RankingType.weekly:
boardName = '周榜';
break;
case RankingType.monthly:
boardName = '月榜';
break;
}
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 图标
Container(
width: 80,
height: 80,
decoration: BoxDecoration(
color: const Color(0x1A8B5A2B),
borderRadius: BorderRadius.circular(40),
),
child: const Icon(
Icons.hourglass_empty,
size: 40,
color: Color(0xFF8B5A2B),
),
),
const SizedBox(height: 24),
// 标题
Text(
'$boardName待开启',
style: const TextStyle(
fontSize: 18,
fontFamily: 'Inter',
fontWeight: FontWeight.w700,
color: Color(0xFF5D4037),
),
),
const SizedBox(height: 8),
// 描述
const Text(
'该榜单暂未开启,请稍后再来',
style: TextStyle(
fontSize: 14,
fontFamily: 'Inter',
color: Color(0xFF8B5A2B),
),
),
],
),
);
}
/// 构建顶部标题和Tab栏带状态
Widget _buildHeaderWithStatus(LeaderboardStatus? status) {
return Container(
color: const Color(0xCCFFF5E6),
child: SafeArea(
bottom: false,
child: Column(
children: [
// 标题
Container(
height: 56,
padding: const EdgeInsets.symmetric(horizontal: 16),
child: const Center(
child: Text(
'龙虎榜',
style: TextStyle(
fontSize: 18,
fontFamily: 'Inter',
fontWeight: FontWeight.w700,
height: 1.25,
letterSpacing: -0.27,
color: Color(0xFF5D4037),
),
),
),
),
// Tab栏
_buildTabBarWithStatus(status),
],
),
),
);
}
/// 构建Tab栏带状态
Widget _buildTabBarWithStatus(LeaderboardStatus? status) {
return Container(
height: 45,
padding: const EdgeInsets.symmetric(horizontal: 16),
decoration: const BoxDecoration(
border: Border(
bottom: BorderSide(
width: 1,
color: Color(0x338B5A2B),
),
),
),
child: Row(
children: [
// 日榜
Expanded(
child: _buildTabItemWithStatus(
title: '日榜',
type: RankingType.daily,
isSelected: _selectedRankingType == RankingType.daily,
isEnabled: status?.dailyEnabled ?? false,
onTap: () => _selectRankingType(RankingType.daily),
),
),
// 周榜
Expanded(
child: _buildTabItemWithStatus(
title: '周榜',
type: RankingType.weekly,
isSelected: _selectedRankingType == RankingType.weekly,
isEnabled: status?.weeklyEnabled ?? false,
onTap: () => _selectRankingType(RankingType.weekly),
),
),
// 月榜
Expanded(
child: _buildTabItemWithStatus(
title: '月榜',
type: RankingType.monthly,
isSelected: _selectedRankingType == RankingType.monthly,
isEnabled: status?.monthlyEnabled ?? false,
onTap: () => _selectRankingType(RankingType.monthly),
),
),
],
),
);
}
/// 构建Tab项带状态
Widget _buildTabItemWithStatus({
required String title,
required RankingType type,
required bool isSelected,
required bool isEnabled,
required VoidCallback onTap,
}) {
// 如果榜单未开启,显示灰色且带"待开启"标记
final textColor = isSelected
? const Color(0xFFD4AF37)
: isEnabled
? const Color(0xFF8B5A2B)
: const Color(0xFFB0A090);
return GestureDetector(
onTap: onTap,
child: Container(
height: 44,
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
width: 3,
color: isSelected ? const Color(0xFFD4AF37) : Colors.transparent,
),
),
),
child: Center(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
title,
style: TextStyle(
fontSize: 14,
fontFamily: 'Inter',
fontWeight: FontWeight.w700,
height: 1.5,
letterSpacing: 0.21,
color: textColor,
),
),
if (!isEnabled) ...[
const SizedBox(width: 4),
Container(
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 1),
decoration: BoxDecoration(
color: const Color(0x1A8B5A2B),
borderRadius: BorderRadius.circular(4),
),
child: const Text(
'待开启',
style: TextStyle(
fontSize: 10,
fontFamily: 'Inter',
color: Color(0xFF8B5A2B),
),
),
),
],
],
),
),
),
);
}
/// 构建筛选栏
Widget _buildFilterBar() {
return Container(
height: 56,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: [
_buildFilterChip(
title: '全部',
isSelected: _selectedFilterType == FilterType.all,
onTap: () => _selectFilterType(FilterType.all),
),
const SizedBox(width: 12),
_buildFilterChip(
title: '上一日榜',
isSelected: _selectedFilterType == FilterType.previousDay,
onTap: () => _selectFilterType(FilterType.previousDay),
),
const SizedBox(width: 12),
_buildFilterChip(
title: '上一周榜',
isSelected: _selectedFilterType == FilterType.previousWeek,
onTap: () => _selectFilterType(FilterType.previousWeek),
),
const SizedBox(width: 12),
_buildFilterChip(
title: '上一月榜',
isSelected: _selectedFilterType == FilterType.previousMonth,
onTap: () => _selectFilterType(FilterType.previousMonth),
),
],
),
),
);
}
/// 构建筛选标签
Widget _buildFilterChip({
required String title,
required bool isSelected,
required VoidCallback onTap,
}) {
return GestureDetector(
onTap: onTap,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
decoration: BoxDecoration(
color: isSelected ? const Color(0xFFD4AF37) : const Color(0x1A8B5A2B),
borderRadius: BorderRadius.circular(9999),
),
child: Text(
title,
style: TextStyle(
fontSize: 14,
fontFamily: 'Inter',
fontWeight: FontWeight.w500,
height: 1.5,
color: isSelected ? Colors.white : const Color(0xFF8B5A2B),
),
),
),
);
}
/// 构建排行榜列表
Widget _buildRankingList() {
return ListView.builder(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
itemCount: _mockRankingData.length,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.only(bottom: 4),
child: _buildRankingItem(_mockRankingData[index]),
);
},
);
}
/// 构建排行榜项
Widget _buildRankingItem(RankingItem item) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xFFFFFDF8), // 浅白色背景,与页面背景区分
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [
// 排名
SizedBox(
width: 32,
child: _buildRankBadge(item.rank),
),
const SizedBox(width: 16),
// 头像
_buildAvatar(item),
const SizedBox(width: 16),
// 用户信息
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
item.name,
style: const TextStyle(
fontSize: 16,
fontFamily: 'Inter',
fontWeight: FontWeight.w700,
height: 1.5,
color: Color(0xFF5D4037),
),
),
Text(
'省份: ${item.province}',
style: const TextStyle(
fontSize: 14,
fontFamily: 'Inter',
height: 1.5,
color: Color(0xFF8B5A2B),
),
),
Text(
'城市: ${item.city}',
style: const TextStyle(
fontSize: 14,
fontFamily: 'Inter',
height: 1.5,
color: Color(0xFF8B5A2B),
),
),
],
),
),
// 团队认种量
ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 100),
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
FittedBox(
fit: BoxFit.scaleDown,
alignment: Alignment.centerRight,
child: Text(
_formatNumber(item.teamPlantingAmount),
style: const TextStyle(
fontSize: 16,
fontFamily: 'Inter',
fontWeight: FontWeight.w700,
height: 1.5,
color: Color(0xFFD4AF37),
),
),
),
const Text(
'团队认种量',
style: TextStyle(
fontSize: 12,
fontFamily: 'Inter',
height: 1.5,
color: Color(0xFF8B5A2B),
),
),
],
),
),
],
),
);
}
/// 构建排名徽章
Widget _buildRankBadge(int rank) {
// 前三名使用皇冠图标
if (rank <= 3) {
Color crownColor;
switch (rank) {
case 1:
crownColor = const Color(0xFFFFD700); // 金色
break;
case 2:
crownColor = const Color(0xFFC0C0C0); // 银色
break;
case 3:
crownColor = const Color(0xFFCD7F32); // 铜色
break;
default:
crownColor = const Color(0xFF8B5A2B);
}
return Icon(
Icons.workspace_premium,
size: 32,
color: crownColor,
);
}
// 其他排名显示数字
return Text(
rank.toString(),
style: const TextStyle(
fontSize: 20,
fontFamily: 'Inter',
fontWeight: FontWeight.w700,
height: 1.4,
color: Color(0xFF8B5A2B),
),
textAlign: TextAlign.center,
);
}
/// 构建头像
Widget _buildAvatar(RankingItem item) {
return Container(
width: 56,
height: 56,
decoration: BoxDecoration(
color: const Color(0x33D4AF37),
borderRadius: BorderRadius.circular(28),
),
child: item.avatarUrl != null
? ClipRRect(
borderRadius: BorderRadius.circular(28),
child: Image.network(
item.avatarUrl!,
fit: BoxFit.cover,
),
)
: const Icon(
Icons.person,
size: 32,
color: Color(0xFF8B5A2B),
),
);
}
}