message_item.dart 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. import 'package:flutter/material.dart';
  2. import 'package:tdesign_flutter/tdesign_flutter.dart';
  3. import '../../core/theme/app_colors.dart';
  4. /// Pencil Component/MessageItem — 消息列表项组件
  5. ///
  6. /// 布局:左侧圆形图标(40x40),中间三行文字,右侧未读红点。
  7. /// 卡片圆角8,背景bgCard,padding 12,gap 12,高度88。
  8. /// 左滑显示"置顶""已读""删除"操作。
  9. class MessageItem extends StatelessWidget {
  10. final IconData icon;
  11. final String title;
  12. final String time;
  13. final String sender;
  14. final String summary;
  15. final bool unread;
  16. final VoidCallback? onTap;
  17. final Color? iconColor;
  18. final Color? iconBackground;
  19. final VoidCallback? onPin;
  20. final VoidCallback? onToggleRead;
  21. final VoidCallback? onDelete;
  22. final String swipeGroupTag;
  23. const MessageItem({
  24. super.key,
  25. required this.icon,
  26. required this.title,
  27. required this.time,
  28. required this.sender,
  29. required this.summary,
  30. this.unread = false,
  31. this.onTap,
  32. this.iconColor,
  33. this.iconBackground,
  34. this.onPin,
  35. this.onToggleRead,
  36. this.onDelete,
  37. this.swipeGroupTag = 'messages',
  38. });
  39. @override
  40. Widget build(BuildContext context) {
  41. final screenWidth = MediaQuery.of(context).size.width;
  42. final card = GestureDetector(
  43. onTap: onTap,
  44. child: Container(
  45. height: 88,
  46. padding: const EdgeInsets.all(12),
  47. decoration: BoxDecoration(
  48. color: AppColors.bgCard,
  49. borderRadius: BorderRadius.circular(8),
  50. ),
  51. child: Row(
  52. children: [
  53. // 左侧圆形图标
  54. Container(
  55. width: 40,
  56. height: 40,
  57. decoration: BoxDecoration(
  58. color: iconBackground ?? AppColors.primaryLight,
  59. shape: BoxShape.circle,
  60. ),
  61. child: Icon(
  62. icon,
  63. color: iconColor ?? AppColors.primary,
  64. size: 22,
  65. ),
  66. ),
  67. const SizedBox(width: 12),
  68. // 中间文字区域
  69. Expanded(
  70. child: Column(
  71. crossAxisAlignment: CrossAxisAlignment.start,
  72. mainAxisAlignment: MainAxisAlignment.center,
  73. children: [
  74. // 第一行:标题 + 时间
  75. Row(
  76. children: [
  77. Expanded(
  78. child: Text(
  79. title,
  80. style: const TextStyle(
  81. fontSize: AppFontSizes.body,
  82. fontWeight: FontWeight.w600,
  83. color: AppColors.textPrimary,
  84. ),
  85. maxLines: 1,
  86. overflow: TextOverflow.ellipsis,
  87. ),
  88. ),
  89. const SizedBox(width: 4),
  90. Text(
  91. time,
  92. style: const TextStyle(
  93. fontSize: AppFontSizes.caption,
  94. color: AppColors.textPlaceholder,
  95. ),
  96. ),
  97. ],
  98. ),
  99. const SizedBox(height: 2),
  100. // 第二行:发送人
  101. Text(
  102. sender,
  103. style: const TextStyle(
  104. fontSize: AppFontSizes.caption,
  105. color: AppColors.textSecondary,
  106. ),
  107. maxLines: 1,
  108. overflow: TextOverflow.ellipsis,
  109. ),
  110. const SizedBox(height: 2),
  111. // 第三行:摘要
  112. Text(
  113. summary,
  114. style: const TextStyle(
  115. fontSize: AppFontSizes.caption,
  116. color: AppColors.textPlaceholder,
  117. ),
  118. maxLines: 1,
  119. overflow: TextOverflow.ellipsis,
  120. ),
  121. ],
  122. ),
  123. ),
  124. const SizedBox(width: 12),
  125. // 右侧未读红点
  126. if (unread)
  127. Container(
  128. width: 8,
  129. height: 8,
  130. decoration: const BoxDecoration(
  131. color: AppColors.danger,
  132. shape: BoxShape.circle,
  133. ),
  134. )
  135. else
  136. const SizedBox(width: 8),
  137. ],
  138. ),
  139. ),
  140. );
  141. // 没有操作时直接返回卡片,不包裹 SwipeCell
  142. if (onPin == null && onToggleRead == null && onDelete == null) {
  143. return card;
  144. }
  145. return TDSwipeCell(
  146. groupTag: swipeGroupTag,
  147. right: TDSwipeCellPanel(
  148. extentRatio: 210 / screenWidth,
  149. children: [
  150. if (onPin != null)
  151. TDSwipeCellAction(
  152. label: '',
  153. backgroundColor: Colors.transparent,
  154. builder: (_) =>
  155. _swipeCircle(Icons.push_pin, '置顶', AppColors.primary),
  156. onPressed: (_) => onPin?.call(),
  157. ),
  158. if (onToggleRead != null)
  159. TDSwipeCellAction(
  160. label: '',
  161. backgroundColor: Colors.transparent,
  162. builder: (_) => _swipeCircle(
  163. unread ? Icons.done_all : Icons.markunread,
  164. unread ? '已读' : '未读',
  165. AppColors.textSecondary,
  166. ),
  167. onPressed: (_) => onToggleRead?.call(),
  168. ),
  169. if (onDelete != null)
  170. TDSwipeCellAction(
  171. label: '',
  172. backgroundColor: Colors.transparent,
  173. builder: (_) =>
  174. _swipeCircle(Icons.delete_outline, '删除', AppColors.danger),
  175. onPressed: (_) => onDelete?.call(),
  176. ),
  177. ],
  178. ),
  179. cell: card,
  180. );
  181. }
  182. Widget _swipeCircle(IconData icon, String label, Color color) {
  183. return Padding(
  184. padding: const EdgeInsets.symmetric(horizontal: 6),
  185. child: Column(
  186. mainAxisAlignment: MainAxisAlignment.center,
  187. children: [
  188. Container(
  189. width: 40,
  190. height: 40,
  191. decoration: BoxDecoration(color: color, shape: BoxShape.circle),
  192. child: Icon(icon, color: Colors.white, size: 20),
  193. ),
  194. const SizedBox(height: 4),
  195. Text(
  196. label,
  197. style: TextStyle(color: color, fontSize: 11),
  198. overflow: TextOverflow.ellipsis,
  199. ),
  200. ],
  201. ),
  202. );
  203. }
  204. }