intimacy_analyse_report_widget.dart 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558
  1. import 'package:flutter/material.dart';
  2. import 'package:flutter_screenutil/flutter_screenutil.dart';
  3. import 'package:keyboard/resource/string.gen.dart';
  4. import 'package:lottie/lottie.dart';
  5. import '../../../data/api/response/intimacy_analyze_response.dart';
  6. import '../../../resource/assets.gen.dart';
  7. import '../../../resource/colors.gen.dart';
  8. import '../../../utils/string_format_util.dart';
  9. import '../../../widget/animated_progress_bar.dart';
  10. import '../../../widget/markdown/markdown_viewer.dart';
  11. /// 亲密度报告组件
  12. class IntimacyAnalyseReportWidget extends StatelessWidget {
  13. /// 报告内容,Markdown格式
  14. final String reportContent;
  15. /// 分析结果
  16. final IntimacyAnalyzeResponse? intimacyAnalyzeResult;
  17. /// 是否已解锁
  18. final bool unlock;
  19. /// 是否是预览
  20. final bool isPreview;
  21. const IntimacyAnalyseReportWidget({
  22. super.key,
  23. required this.reportContent,
  24. this.intimacyAnalyzeResult,
  25. this.unlock = false,
  26. this.isPreview = false,
  27. });
  28. @override
  29. Widget build(BuildContext context) {
  30. if (isPreview) {
  31. return PreviewReportCardWidget(previewReportContent: reportContent);
  32. }
  33. // 未解锁
  34. if (!unlock) {
  35. return UnlockReportCardWidget();
  36. }
  37. // 报告生成中
  38. if (intimacyAnalyzeResult == null || reportContent.isEmpty) {
  39. return CreatingReportCardWidget();
  40. }
  41. // 已出报告
  42. return ExistReportCardWidget(
  43. reportContent: reportContent,
  44. intimacyAnalyzeResult: intimacyAnalyzeResult,
  45. );
  46. }
  47. }
  48. /// 预览报告卡片
  49. class PreviewReportCardWidget extends StatelessWidget {
  50. final String previewReportContent;
  51. const PreviewReportCardWidget({
  52. super.key,
  53. required this.previewReportContent,
  54. });
  55. @override
  56. Widget build(BuildContext context) {
  57. return ReportCardFrameWidget(
  58. child: Column(
  59. mainAxisSize: MainAxisSize.min,
  60. children: [
  61. SizedBox(height: 10.h),
  62. Container(
  63. // 最小高度
  64. constraints: BoxConstraints(minHeight: 360.h),
  65. decoration: ShapeDecoration(
  66. color: ColorName.white,
  67. shape: RoundedRectangleBorder(
  68. borderRadius: BorderRadius.circular(30.r),
  69. ),
  70. ),
  71. child: MarkdownViewer(
  72. // 内容
  73. content: previewReportContent,
  74. // 不允许滚动
  75. enableContentScroll: false,
  76. ),
  77. ),
  78. ],
  79. ),
  80. );
  81. }
  82. }
  83. /// 已有报告组件
  84. class ExistReportCardWidget extends StatelessWidget {
  85. /// 报告内容,Markdown格式
  86. final String reportContent;
  87. /// 分析结果
  88. final IntimacyAnalyzeResponse? intimacyAnalyzeResult;
  89. const ExistReportCardWidget({
  90. super.key,
  91. required this.reportContent,
  92. this.intimacyAnalyzeResult,
  93. });
  94. @override
  95. Widget build(BuildContext context) {
  96. return ReportCardFrameWidget(
  97. child: Column(
  98. mainAxisSize: MainAxisSize.min,
  99. children: [
  100. _buildReportOverview(),
  101. SizedBox(height: 10.h),
  102. _buildReportDetail(),
  103. ],
  104. ),
  105. );
  106. }
  107. /// 报告概览,包含概览数值和图表
  108. Widget _buildReportOverview() {
  109. if (intimacyAnalyzeResult == null) {
  110. return SizedBox();
  111. }
  112. var analyzeResult = intimacyAnalyzeResult!;
  113. // 圆角背景
  114. return Container(
  115. padding: EdgeInsets.only(left: 12.w, right: 12.w, top: 12.h),
  116. decoration: BoxDecoration(
  117. color: ColorName.white,
  118. borderRadius: BorderRadius.circular(20.r),
  119. ),
  120. child: Column(
  121. mainAxisSize: MainAxisSize.min,
  122. children: [
  123. // 概览数值
  124. Container(
  125. padding: EdgeInsets.symmetric(horizontal: 18.w),
  126. decoration: BoxDecoration(
  127. color: ColorName.bgReportOverview,
  128. borderRadius: BorderRadius.all(Radius.circular(10.r)),
  129. ),
  130. child: Row(
  131. children: [
  132. _buildIntimacyOverviewItem(
  133. StringFormatUtil.removePercentSymbol(
  134. analyzeResult.emotion ?? "",
  135. ),
  136. ),
  137. _buildOverviewDivider(),
  138. _buildCurrentStageOverviewItem(
  139. analyzeResult.intimacyPhase ?? "",
  140. ),
  141. ],
  142. ),
  143. ),
  144. SizedBox(height: 36.h),
  145. // 图表
  146. _buildChart(analyzeResult),
  147. ],
  148. ),
  149. );
  150. }
  151. /// 概览数值的分割线
  152. Widget _buildOverviewDivider() {
  153. return Container(
  154. height: 44.h,
  155. width: 1.w,
  156. decoration: BoxDecoration(color: ColorName.black5),
  157. margin: EdgeInsets.symmetric(vertical: 10.h),
  158. );
  159. }
  160. /// 概览亲密度的数值项
  161. Widget _buildIntimacyOverviewItem(String value) {
  162. return Expanded(
  163. child: Row(
  164. // 左对齐
  165. mainAxisAlignment: MainAxisAlignment.start,
  166. children: [
  167. // 图表
  168. Assets.images.iconIntimacyAnalyseReportOverviewLove.image(
  169. height: 22.w,
  170. width: 22.w,
  171. ),
  172. SizedBox(width: 14.w),
  173. Column(
  174. crossAxisAlignment: CrossAxisAlignment.start,
  175. children: [
  176. // 数值区域
  177. Row(
  178. children: [
  179. // 数值
  180. Text(
  181. value,
  182. style: TextStyle(
  183. color: ColorName.black80,
  184. fontSize: 20.sp,
  185. fontWeight: FontWeight.w700,
  186. ),
  187. ),
  188. SizedBox(width: 2.w),
  189. // 百分比符号
  190. Text(
  191. StringName.intimacyValuePercent,
  192. style: TextStyle(
  193. color: ColorName.black46,
  194. fontSize: 13.sp,
  195. fontWeight: FontWeight.w700,
  196. ),
  197. ),
  198. ],
  199. ),
  200. // 描述
  201. Text(
  202. StringName.intimacyValue,
  203. style: TextStyle(
  204. color: ColorName.textReportOverviewValueDesc,
  205. fontSize: 12.sp,
  206. fontWeight: FontWeight.w400,
  207. ),
  208. ),
  209. ],
  210. ),
  211. ],
  212. ),
  213. );
  214. }
  215. /// 概览目前阶段的数值项
  216. Widget _buildCurrentStageOverviewItem(String value) {
  217. return Expanded(
  218. child: Row(
  219. // 右对齐
  220. mainAxisAlignment: MainAxisAlignment.end,
  221. children: [
  222. // 图标
  223. Assets.images.iconIntimacyAnalyseReportOverviewStage.image(
  224. height: 22.w,
  225. width: 22.w,
  226. ),
  227. SizedBox(width: 14.w),
  228. Column(
  229. crossAxisAlignment: CrossAxisAlignment.start,
  230. children: [
  231. // 数值
  232. Text(
  233. value,
  234. style: TextStyle(
  235. color: ColorName.black80,
  236. fontSize: 14.sp,
  237. fontWeight: FontWeight.w700,
  238. ),
  239. ),
  240. // 描述
  241. Text(
  242. StringName.intimacyCurrentStage,
  243. style: TextStyle(
  244. color: ColorName.textReportOverviewValueDesc,
  245. fontSize: 12.sp,
  246. fontWeight: FontWeight.w400,
  247. ),
  248. ),
  249. ],
  250. ),
  251. ],
  252. ),
  253. );
  254. }
  255. /// 将百分比转换为进度条需要的数值
  256. double _convertPercentValue2ProgressBar(String percentValue) {
  257. if (percentValue.isEmpty) {
  258. return 0;
  259. }
  260. // 去掉%号后的数值
  261. String valueStr = StringFormatUtil.removePercentSymbol(percentValue);
  262. double noPercentValue = double.tryParse(valueStr) ?? 0;
  263. if (noPercentValue == 0) {
  264. return 0;
  265. } else {
  266. // 除以100,转换为0-1之间的数值,才是进度条需要的数值
  267. double progressValue = noPercentValue / 100;
  268. return progressValue;
  269. }
  270. }
  271. /// 报告图表
  272. Widget _buildChart(IntimacyAnalyzeResponse analyzeResult) {
  273. return Column(
  274. mainAxisSize: MainAxisSize.min,
  275. children: [
  276. _buildValueItem(
  277. iconProvider: Assets.images.iconEmojiLike.provider(),
  278. title: StringName.intimacyInteraction,
  279. value: _convertPercentValue2ProgressBar(
  280. analyzeResult.interaction ?? "",
  281. ),
  282. progressColors: [ColorName.blueGradient1, ColorName.blueGradient2],
  283. ),
  284. SizedBox(height: 18.h),
  285. _buildValueItem(
  286. iconProvider: Assets.images.iconEmojiChat.provider(),
  287. title: StringName.intimacyTopic,
  288. value: _convertPercentValue2ProgressBar(analyzeResult.topic ?? ""),
  289. progressColors: [ColorName.greenGradient1, ColorName.greenGradient2],
  290. ),
  291. SizedBox(height: 18.h),
  292. _buildValueItem(
  293. iconProvider: Assets.images.iconEmojiLike.provider(),
  294. title: StringName.intimacyRespond,
  295. value: _convertPercentValue2ProgressBar(analyzeResult.respond ?? ""),
  296. progressColors: [
  297. ColorName.yellowGradient1,
  298. ColorName.yellowGradient2,
  299. ],
  300. ),
  301. SizedBox(height: 18.h),
  302. _buildValueItem(
  303. iconProvider: Assets.images.iconEmojiPercent.provider(),
  304. title: StringName.intimacyintimacyRatio,
  305. value: _convertPercentValue2ProgressBar(
  306. analyzeResult.intimacyRatio ?? "",
  307. ),
  308. progressColors: [ColorName.pinkGradient1, ColorName.pinkGradient2],
  309. ),
  310. SizedBox(height: 18.h),
  311. _buildValueItem(
  312. iconProvider: Assets.images.iconEmojiLove.provider(),
  313. title: analyzeResult.directionName ?? "",
  314. value: _convertPercentValue2ProgressBar(
  315. analyzeResult.direction ?? "",
  316. ),
  317. progressColors: [
  318. ColorName.purpleGradient1,
  319. ColorName.purpleGradient2,
  320. ],
  321. ),
  322. SizedBox(height: 12, width: double.infinity),
  323. ],
  324. );
  325. }
  326. /// 构建数值Item
  327. /// [iconProvider] icon图标的ImageProvider
  328. /// [title] 标题
  329. /// [progressColors] 进度条渐变色
  330. /// [value] 进度条的数值,从0到1.0
  331. Widget _buildValueItem({
  332. required ImageProvider iconProvider,
  333. required String title,
  334. required List<Color> progressColors,
  335. required double value,
  336. }) {
  337. return Row(
  338. mainAxisSize: MainAxisSize.min,
  339. crossAxisAlignment: CrossAxisAlignment.center,
  340. children: [
  341. // 图标
  342. Container(
  343. padding: EdgeInsets.all(6.w),
  344. decoration: BoxDecoration(
  345. // 圆形背景
  346. shape: BoxShape.circle,
  347. color: ColorName.bgReportValueIcon,
  348. ),
  349. child: ClipOval(
  350. child: Image(
  351. image: iconProvider,
  352. width: 15.w,
  353. height: 15.w,
  354. fit: BoxFit.fill,
  355. ),
  356. ),
  357. ),
  358. SizedBox(width: 5.w),
  359. // 文字
  360. Text(title),
  361. SizedBox(width: 10.w),
  362. // 进度条
  363. Expanded(
  364. child: AnimatedGradientProgressBar(
  365. // 进度值
  366. targetValue: value,
  367. // 渐变色
  368. gradient: LinearGradient(colors: progressColors),
  369. // 动画时长
  370. duration: const Duration(milliseconds: 1500),
  371. ),
  372. ),
  373. ],
  374. );
  375. }
  376. /// 报告详情
  377. Widget _buildReportDetail() {
  378. return Container(
  379. decoration: ShapeDecoration(
  380. color: ColorName.white,
  381. shape: RoundedRectangleBorder(
  382. borderRadius: BorderRadius.circular(30.r),
  383. ),
  384. ),
  385. child: MarkdownViewer(
  386. // 内容
  387. content: reportContent,
  388. // 不允许滚动
  389. enableContentScroll: false,
  390. ),
  391. );
  392. }
  393. }
  394. /// 报告生成中的卡片
  395. class CreatingReportCardWidget extends StatelessWidget {
  396. const CreatingReportCardWidget({super.key});
  397. @override
  398. Widget build(BuildContext context) {
  399. return ReportCardFrameWidget(
  400. child: Container(
  401. padding: EdgeInsets.only(top: 33.h, bottom: 52.h),
  402. decoration: ShapeDecoration(
  403. color: ColorName.white,
  404. shape: RoundedRectangleBorder(
  405. borderRadius: BorderRadius.circular(30.r),
  406. ),
  407. ),
  408. child: Center(
  409. child: Column(
  410. // 垂直水平都居中
  411. mainAxisAlignment: MainAxisAlignment.center,
  412. crossAxisAlignment: CrossAxisAlignment.center,
  413. children: [
  414. // 图标
  415. Lottie.asset(
  416. Assets.anim.animIntimacyAnalyseCreatingReportData,
  417. repeat: true,
  418. width: 82.w,
  419. height: 82.w,
  420. ),
  421. SizedBox(height: 3.h),
  422. // 文字
  423. Text(
  424. StringName.intimacyAnalyseReportCreating,
  425. style: TextStyle(
  426. fontSize: 14.sp,
  427. color: ColorName.black60,
  428. fontWeight: FontWeight.w400,
  429. ),
  430. ),
  431. ],
  432. ),
  433. ),
  434. ),
  435. );
  436. }
  437. }
  438. /// 未解锁时的报告卡片
  439. class UnlockReportCardWidget extends StatelessWidget {
  440. const UnlockReportCardWidget({super.key});
  441. @override
  442. Widget build(BuildContext context) {
  443. return ReportCardFrameWidget(
  444. child: Container(
  445. padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 6.w),
  446. decoration: ShapeDecoration(
  447. color: ColorName.white,
  448. shape: RoundedRectangleBorder(
  449. borderRadius: BorderRadius.circular(30.r),
  450. ),
  451. ),
  452. child: Center(
  453. child: Column(
  454. // 垂直水平都居中
  455. mainAxisAlignment: MainAxisAlignment.center,
  456. crossAxisAlignment: CrossAxisAlignment.center,
  457. children: [
  458. // 占位图
  459. Assets.images.iconIntimacyAnalyseReportUnlockPlaceholder.image(
  460. width: 300.w,
  461. height: 303.h,
  462. ),
  463. ],
  464. ),
  465. ),
  466. ),
  467. );
  468. }
  469. }
  470. /// 报告卡片框架组件,只定义报告卡片的基本框架,例如卡片背景和顶部的标题,内容由子组件来实现
  471. class ReportCardFrameWidget extends StatelessWidget {
  472. /// 内容子组件
  473. final Widget child;
  474. const ReportCardFrameWidget({super.key, required this.child});
  475. @override
  476. Widget build(BuildContext context) {
  477. // 卡片背景
  478. return Container(
  479. margin: EdgeInsets.only(left: 12, right: 12, bottom: 0),
  480. padding: EdgeInsets.only(bottom: 12),
  481. decoration: BoxDecoration(
  482. // image: DecorationImage(
  483. // image: Assets.images.bgIntimacyAnalyseReportPreview.provider(),
  484. // fit: BoxFit.fill,
  485. // ),
  486. // 渐变背景
  487. gradient: LinearGradient(
  488. colors: [Color(0xFFE9E2FF), Color(0xFFF1F5FF)],
  489. begin: Alignment.centerLeft,
  490. end: Alignment.centerRight,
  491. ),
  492. borderRadius: BorderRadius.all(Radius.circular(20.r)),
  493. ),
  494. child: Column(
  495. mainAxisSize: MainAxisSize.min,
  496. children: [
  497. // 顶部的图标和标题
  498. _buildReportTopLayout(),
  499. // 内容区域
  500. Container(
  501. padding: EdgeInsets.only(top: 3, left: 12, right: 12, bottom: 5),
  502. child: child,
  503. ),
  504. ],
  505. ),
  506. );
  507. }
  508. /// 报告的顶部布局-包含:图标和标题
  509. Widget _buildReportTopLayout() {
  510. return Container(
  511. margin: EdgeInsets.only(left: 12.w, top: 12.h),
  512. child: Row(
  513. children: [
  514. Assets.images.iconIntimacyAnalyseReportPreviewLove.image(
  515. width: 50,
  516. height: 48,
  517. ),
  518. Assets.images.iconIntimacyAnalyseReportPreviewTitle.image(
  519. width: 178,
  520. height: 24,
  521. ),
  522. ],
  523. ),
  524. );
  525. }
  526. }