intimacy_analyse_report_widget.dart 19 KB

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