message_item.dart 6.6 KB

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