intimacy_analyse_report_widget.dart 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621
  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. AnalyseItem(
  416. title: StringName.intimacyintimacyEmotionNeed,
  417. sections: [intimacyAnalyzeResult?.need ?? ""],
  418. ),
  419. AnalyseItem(
  420. title: StringName.intimacyintimacyChatStrategy,
  421. sections: [intimacyAnalyzeResult?.chatStrategy ?? ""],
  422. ),
  423. AnalyseItem(
  424. title: StringName.intimacyintimacySummary,
  425. sections: [intimacyAnalyzeResult?.summary ?? ""],
  426. ),
  427. ],
  428. );
  429. return _buildReportContent(reportPreviewData);
  430. }
  431. /// 构建报告内容
  432. Widget _buildReportContent(IntimacyAnalyseReport report) {
  433. return Container(
  434. padding: EdgeInsets.all(14.w),
  435. decoration: ShapeDecoration(
  436. color: ColorName.white,
  437. shape: RoundedRectangleBorder(
  438. borderRadius: BorderRadius.circular(30.r),
  439. ),
  440. ),
  441. child: Column(
  442. children: [
  443. // 报告列表
  444. ...report.list.map((ele) {
  445. return ReportItemWidget(item: ele);
  446. }),
  447. ],
  448. ),
  449. );
  450. }
  451. }
  452. /// 报告生成中的卡片
  453. class CreatingReportCardWidget extends StatelessWidget {
  454. const CreatingReportCardWidget({super.key});
  455. @override
  456. Widget build(BuildContext context) {
  457. return ReportCardFrameWidget(
  458. child: Container(
  459. padding: EdgeInsets.only(top: 33.h, bottom: 52.h),
  460. decoration: ShapeDecoration(
  461. color: ColorName.white,
  462. shape: RoundedRectangleBorder(
  463. borderRadius: BorderRadius.circular(30.r),
  464. ),
  465. ),
  466. child: Center(
  467. child: Column(
  468. // 垂直水平都居中
  469. mainAxisAlignment: MainAxisAlignment.center,
  470. crossAxisAlignment: CrossAxisAlignment.center,
  471. children: [
  472. // 图标
  473. Lottie.asset(
  474. Assets.anim.animIntimacyAnalyseCreatingReportData,
  475. repeat: true,
  476. width: 82.w,
  477. height: 82.w,
  478. ),
  479. SizedBox(height: 3.h),
  480. // 文字
  481. Text(
  482. StringName.intimacyAnalyseReportCreating,
  483. style: TextStyle(
  484. fontSize: 14.sp,
  485. color: ColorName.black60,
  486. fontWeight: FontWeight.w400,
  487. ),
  488. ),
  489. ],
  490. ),
  491. ),
  492. ),
  493. );
  494. }
  495. }
  496. /// 未解锁时的报告卡片
  497. class UnlockReportCardWidget extends StatelessWidget {
  498. const UnlockReportCardWidget({super.key});
  499. @override
  500. Widget build(BuildContext context) {
  501. return ReportCardFrameWidget(
  502. child: Container(
  503. padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 6.w),
  504. decoration: ShapeDecoration(
  505. color: ColorName.white,
  506. shape: RoundedRectangleBorder(
  507. borderRadius: BorderRadius.circular(30.r),
  508. ),
  509. ),
  510. child: Center(
  511. child: Column(
  512. // 垂直水平都居中
  513. mainAxisAlignment: MainAxisAlignment.center,
  514. crossAxisAlignment: CrossAxisAlignment.center,
  515. children: [
  516. // 占位图
  517. Assets.images.iconIntimacyAnalyseReportUnlockPlaceholder.image(
  518. width: 300.w,
  519. height: 303.h,
  520. ),
  521. ],
  522. ),
  523. ),
  524. ),
  525. );
  526. }
  527. }
  528. /// 报告卡片框架组件,只定义报告卡片的基本框架,例如卡片背景和顶部的标题,内容由子组件来实现
  529. class ReportCardFrameWidget extends StatelessWidget {
  530. /// 内容子组件
  531. final Widget child;
  532. const ReportCardFrameWidget({super.key, required this.child});
  533. @override
  534. Widget build(BuildContext context) {
  535. // 卡片背景
  536. return Container(
  537. margin: EdgeInsets.only(left: 12, right: 12, bottom: 0),
  538. padding: EdgeInsets.only(bottom: 12),
  539. decoration: BoxDecoration(
  540. // image: DecorationImage(
  541. // image: Assets.images.bgIntimacyAnalyseReportPreview.provider(),
  542. // fit: BoxFit.fill,
  543. // ),
  544. // 渐变背景
  545. gradient: LinearGradient(
  546. colors: [Color(0xFFE9E2FF), Color(0xFFF1F5FF)],
  547. begin: Alignment.centerLeft,
  548. end: Alignment.centerRight,
  549. ),
  550. borderRadius: BorderRadius.all(Radius.circular(20.r)),
  551. ),
  552. child: Column(
  553. mainAxisSize: MainAxisSize.min,
  554. children: [
  555. // 顶部的图标和标题
  556. _buildReportTopLayout(),
  557. // 内容区域
  558. Container(
  559. padding: EdgeInsets.only(top: 3, left: 12, right: 12, bottom: 5),
  560. child: child,
  561. ),
  562. ],
  563. ),
  564. );
  565. }
  566. /// 报告的顶部布局-包含:图标和标题
  567. Widget _buildReportTopLayout() {
  568. return Container(
  569. margin: EdgeInsets.only(left: 12.w, top: 12.h),
  570. child: Row(
  571. children: [
  572. Assets.images.iconIntimacyAnalyseReportPreviewLove.image(
  573. width: 50,
  574. height: 48,
  575. ),
  576. Assets.images.iconIntimacyAnalyseReportPreviewTitle.image(
  577. width: 178,
  578. height: 24,
  579. ),
  580. ],
  581. ),
  582. );
  583. }
  584. }