info_card.dart 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. import 'package:flutter/material.dart';
  2. import 'package:flutter_screenutil/flutter_screenutil.dart';
  3. import 'package:get/get.dart';
  4. import 'package:nomo/app/widgets/ix_image.dart';
  5. import 'package:nomo/config/theme/theme_extensions/theme_extension.dart';
  6. /// 信息卡片组件 - 用于显示带图标的信息列表
  7. /// 常用于订阅说明、功能介绍等场景
  8. class InfoCard extends StatelessWidget {
  9. final String? title;
  10. final List<InfoItem> items;
  11. final Color? backgroundColor;
  12. final EdgeInsets? padding;
  13. const InfoCard({
  14. super.key,
  15. this.title,
  16. required this.items,
  17. this.backgroundColor,
  18. this.padding,
  19. });
  20. @override
  21. Widget build(BuildContext context) {
  22. return Column(
  23. crossAxisAlignment: CrossAxisAlignment.start,
  24. children: [
  25. if (title != null) ...[
  26. Text(
  27. title!,
  28. style: TextStyle(
  29. fontSize: 16.sp,
  30. color: Get.reactiveTheme.hintColor,
  31. fontWeight: FontWeight.w500,
  32. ),
  33. ),
  34. 16.verticalSpace,
  35. ],
  36. Container(
  37. padding: padding ?? EdgeInsets.all(16.w),
  38. decoration: BoxDecoration(
  39. color: backgroundColor ?? Get.reactiveTheme.cardColor,
  40. borderRadius: BorderRadius.circular(12.r),
  41. ),
  42. child: Column(
  43. children: items
  44. .asMap()
  45. .entries
  46. .map(
  47. (entry) => _InfoItemWidget(
  48. item: entry.value,
  49. isLast: entry.key == items.length - 1,
  50. ),
  51. )
  52. .toList(),
  53. ),
  54. ),
  55. ],
  56. );
  57. }
  58. }
  59. /// 信息项数据模型
  60. class InfoItem {
  61. final IconData? icon;
  62. final String? imageSource;
  63. final String title;
  64. final String description;
  65. final Color? iconColor;
  66. final Gradient? iconGradient;
  67. final double? iconSize;
  68. final double? imageWidth;
  69. final double? imageHeight;
  70. const InfoItem({
  71. this.icon,
  72. this.imageSource,
  73. required this.title,
  74. required this.description,
  75. this.iconColor,
  76. this.iconGradient,
  77. this.iconSize,
  78. this.imageWidth,
  79. this.imageHeight,
  80. }) : assert(
  81. icon != null || imageSource != null,
  82. 'Either icon or imageSource must be provided',
  83. );
  84. }
  85. /// 信息项组件(内部使用)
  86. class _InfoItemWidget extends StatelessWidget {
  87. final InfoItem item;
  88. final bool isLast;
  89. const _InfoItemWidget({required this.item, this.isLast = false});
  90. @override
  91. Widget build(BuildContext context) {
  92. return IntrinsicHeight(
  93. child: Row(
  94. crossAxisAlignment: CrossAxisAlignment.start,
  95. children: [
  96. // 左侧时间线(图标/图片 + 竖线)
  97. Column(
  98. children: [
  99. // 图标或图片容器
  100. _buildIconOrImage(),
  101. // 连接线(最后一个不显示)
  102. if (!isLast)
  103. Expanded(
  104. child: Container(
  105. width: 1,
  106. margin: EdgeInsets.only(top: 8.h, bottom: 8.h),
  107. color: Get.reactiveTheme.dividerColor,
  108. ),
  109. ),
  110. ],
  111. ),
  112. 12.horizontalSpace,
  113. // 文本内容
  114. Expanded(
  115. child: Padding(
  116. padding: EdgeInsets.only(bottom: isLast ? 0 : 10.w),
  117. child: Column(
  118. crossAxisAlignment: CrossAxisAlignment.start,
  119. children: [
  120. Text(
  121. item.title,
  122. style: TextStyle(
  123. fontSize: 14.sp,
  124. height: 1.6,
  125. color: Get.reactiveTheme.textTheme.bodyLarge!.color,
  126. fontWeight: FontWeight.w500,
  127. ),
  128. ),
  129. 4.verticalSpace,
  130. Text(
  131. item.description,
  132. style: TextStyle(
  133. fontSize: 12.sp,
  134. color: Get.reactiveTheme.textTheme.bodyLarge!.color,
  135. height: 1.7,
  136. ),
  137. ),
  138. ],
  139. ),
  140. ),
  141. ),
  142. ],
  143. ),
  144. );
  145. }
  146. /// 构建图标或图片
  147. Widget _buildIconOrImage() {
  148. if (item.imageSource != null) {
  149. // 显示图片
  150. return IXImage(
  151. source: item.imageSource!,
  152. width: item.imageWidth ?? 20.w,
  153. height: item.imageHeight ?? 20.w,
  154. sourceType: _getImageSourceType(item.imageSource!),
  155. );
  156. } else {
  157. // 显示图标
  158. return Icon(
  159. item.icon,
  160. size: item.iconSize ?? 20.w,
  161. color: item.iconColor ?? Colors.white,
  162. );
  163. }
  164. }
  165. /// 判断图片资源类型
  166. ImageSourceType _getImageSourceType(String source) {
  167. if (source.startsWith('http://') || source.startsWith('https://')) {
  168. return ImageSourceType.network;
  169. } else if (source.startsWith('assets/')) {
  170. return ImageSourceType.asset;
  171. } else {
  172. return ImageSourceType.asset;
  173. }
  174. }
  175. }