challenge_card.dart 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. import 'package:flutter/material.dart';
  2. import 'package:intl/intl.dart';
  3. import 'package:japp_flutter/core/models/challenge_model.dart';
  4. class ChallengeCard extends StatelessWidget {
  5. final ChallengeModel challenge;
  6. const ChallengeCard({super.key, required this.challenge});
  7. @override
  8. Widget build(BuildContext context) {
  9. final theme = Theme.of(context);
  10. return Card(
  11. elevation: 2,
  12. shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
  13. margin: const EdgeInsets.only(bottom: 16),
  14. child: Padding(
  15. padding: const EdgeInsets.all(16),
  16. child: Column(
  17. crossAxisAlignment: CrossAxisAlignment.start,
  18. children: [
  19. Row(
  20. children: [
  21. Expanded(
  22. child: Text(
  23. challenge.title,
  24. style: const TextStyle(
  25. fontSize: 18,
  26. fontWeight: FontWeight.bold,
  27. ),
  28. ),
  29. ),
  30. _DifficultyChip(difficulty: challenge.difficulty),
  31. ],
  32. ),
  33. const SizedBox(height: 8),
  34. Text(
  35. challenge.description,
  36. style: TextStyle(color: Colors.grey[700], fontSize: 14),
  37. ),
  38. const SizedBox(height: 16),
  39. _ProgressBar(
  40. startDate: challenge.startDate,
  41. endDate: challenge.endDate,
  42. remainingDays: challenge.remainingDays?? 0,
  43. ),
  44. const SizedBox(height: 12),
  45. _CardFooter(
  46. participants: challenge.participants,
  47. remainingDays: challenge.remainingDays ?? 0,
  48. ),
  49. ],
  50. ),
  51. ),
  52. );
  53. }
  54. }
  55. // 将复杂子组件拆分为独立组件
  56. class _DifficultyChip extends StatelessWidget {
  57. final String difficulty;
  58. const _DifficultyChip({required this.difficulty});
  59. Color _getColor() {
  60. switch (difficulty) {
  61. case '简单':
  62. return Colors.green;
  63. case '中等':
  64. return Colors.orange;
  65. case '困难':
  66. return Colors.red;
  67. default:
  68. return Colors.grey;
  69. }
  70. }
  71. @override
  72. Widget build(BuildContext context) {
  73. return Chip(
  74. label: Text(difficulty, style: const TextStyle(color: Colors.white)),
  75. backgroundColor: _getColor(),
  76. );
  77. }
  78. }
  79. class _ProgressBar extends StatelessWidget {
  80. final DateTime startDate;
  81. final DateTime endDate;
  82. final int remainingDays;
  83. const _ProgressBar({
  84. required this.startDate,
  85. required this.endDate,
  86. required this.remainingDays,
  87. });
  88. @override
  89. Widget build(BuildContext context) {
  90. final theme = Theme.of(context);
  91. final totalDays = endDate.difference(startDate).inDays;
  92. final passedDays = totalDays - remainingDays;
  93. final progress = passedDays / totalDays;
  94. return Column(
  95. crossAxisAlignment: CrossAxisAlignment.start,
  96. children: [
  97. Text(
  98. '${(progress * 100).toStringAsFixed(0)}%',
  99. style: const TextStyle(fontSize: 12),
  100. ),
  101. const SizedBox(height: 4),
  102. LinearProgressIndicator(
  103. value: progress,
  104. backgroundColor: Colors.grey[200],
  105. borderRadius: BorderRadius.circular(10),
  106. minHeight: 8,
  107. color: theme.primaryColor,
  108. ),
  109. const SizedBox(height: 4),
  110. Row(
  111. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  112. children: [
  113. Text(
  114. DateFormat('MM/dd').format(startDate),
  115. style: const TextStyle(fontSize: 12),
  116. ),
  117. Text(
  118. DateFormat('MM/dd').format(endDate),
  119. style: const TextStyle(fontSize: 12),
  120. ),
  121. ],
  122. ),
  123. ],
  124. );
  125. }
  126. }
  127. class _CardFooter extends StatelessWidget {
  128. final int participants;
  129. final int remainingDays;
  130. const _CardFooter({required this.participants, required this.remainingDays});
  131. @override
  132. Widget build(BuildContext context) {
  133. return Row(
  134. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  135. children: [
  136. _InfoChip(icon: Icons.people, text: '$participants人参加'),
  137. _InfoChip(icon: Icons.calendar_today, text: '剩余${remainingDays}天'),
  138. ],
  139. );
  140. }
  141. }
  142. class _InfoChip extends StatelessWidget {
  143. final IconData icon;
  144. final String text;
  145. const _InfoChip({required this.icon, required this.text});
  146. @override
  147. Widget build(BuildContext context) {
  148. return Container(
  149. padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
  150. decoration: BoxDecoration(
  151. color: Colors.grey[100],
  152. borderRadius: BorderRadius.circular(20),
  153. ),
  154. child: Row(
  155. children: [
  156. Icon(icon, size: 16, color: Colors.grey[600]),
  157. const SizedBox(width: 4),
  158. Text(text, style: TextStyle(color: Colors.grey[700])),
  159. ],
  160. ),
  161. );
  162. }
  163. }