intimacy_analyse_report_widget.dart 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619
  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. style: TextStyle(
  190. color: ColorName.black80,
  191. fontSize: 20.sp,
  192. fontWeight: FontWeight.w700,
  193. ),
  194. ),
  195. SizedBox(width: 2.w),
  196. // 百分比符号
  197. Text(
  198. StringName.intimacyValuePercent,
  199. style: TextStyle(
  200. color: ColorName.black46,
  201. fontSize: 13.sp,
  202. fontWeight: FontWeight.w700,
  203. ),
  204. ),
  205. ],
  206. ),
  207. // 描述
  208. Text(
  209. StringName.intimacyValue,
  210. style: TextStyle(
  211. color: ColorName.textReportOverviewValueDesc,
  212. fontSize: 12.sp,
  213. fontWeight: FontWeight.w400,
  214. ),
  215. ),
  216. ],
  217. ),
  218. ],
  219. ),
  220. );
  221. }
  222. /// 概览目前阶段的数值项
  223. Widget _buildCurrentStageOverviewItem(String value) {
  224. return Expanded(
  225. child: Row(
  226. // 右对齐
  227. mainAxisAlignment: MainAxisAlignment.end,
  228. children: [
  229. // 图标
  230. Assets.images.iconIntimacyAnalyseReportOverviewStage.image(
  231. height: 22.w,
  232. width: 22.w,
  233. ),
  234. SizedBox(width: 14.w),
  235. Column(
  236. crossAxisAlignment: CrossAxisAlignment.start,
  237. children: [
  238. // 数值
  239. Text(
  240. value,
  241. style: TextStyle(
  242. color: ColorName.black80,
  243. fontSize: 14.sp,
  244. fontWeight: FontWeight.w700,
  245. ),
  246. ),
  247. // 描述
  248. Text(
  249. StringName.intimacyCurrentStage,
  250. style: TextStyle(
  251. color: ColorName.textReportOverviewValueDesc,
  252. fontSize: 12.sp,
  253. fontWeight: FontWeight.w400,
  254. ),
  255. ),
  256. ],
  257. ),
  258. ],
  259. ),
  260. );
  261. }
  262. /// 将百分比转换为进度条需要的数值
  263. double _convertPercentValue2ProgressBar(String percentValue) {
  264. if (percentValue.isEmpty) {
  265. return 0;
  266. }
  267. // 去掉%号后的数值
  268. String valueStr = StringFormatUtil.removePercentSymbol(percentValue);
  269. double noPercentValue = double.tryParse(valueStr) ?? 0;
  270. if (noPercentValue == 0) {
  271. return 0;
  272. } else {
  273. // 除以100,转换为0-1之间的数值,才是进度条需要的数值
  274. double progressValue = noPercentValue / 100;
  275. return progressValue;
  276. }
  277. }
  278. /// 报告图表
  279. Widget _buildChart(IntimacyAnalyzeResponse analyzeResult) {
  280. return Column(
  281. mainAxisSize: MainAxisSize.min,
  282. children: [
  283. // 互动好感度
  284. _buildValueItem(
  285. iconProvider: Assets.images.iconEmojiLike.provider(),
  286. title: StringName.intimacyInteraction,
  287. value: _convertPercentValue2ProgressBar(
  288. analyzeResult.interaction ?? "",
  289. ),
  290. progressColors: [ColorName.blueGradient1, ColorName.blueGradient2],
  291. ),
  292. SizedBox(height: 18.h),
  293. // 话题好感度
  294. _buildValueItem(
  295. iconProvider: Assets.images.iconEmojiChat.provider(),
  296. title: StringName.intimacyTopic,
  297. value: _convertPercentValue2ProgressBar(analyzeResult.topic ?? ""),
  298. progressColors: [ColorName.greenGradient1, ColorName.greenGradient2],
  299. ),
  300. SizedBox(height: 18.h),
  301. // 情绪回应
  302. _buildValueItem(
  303. iconProvider: Assets.images.iconEmojiLike.provider(),
  304. title: StringName.intimacyRespond,
  305. value: _convertPercentValue2ProgressBar(analyzeResult.respond ?? ""),
  306. progressColors: [
  307. ColorName.yellowGradient1,
  308. ColorName.yellowGradient2,
  309. ],
  310. ),
  311. SizedBox(height: 18.h),
  312. // 亲密词占比
  313. _buildValueItem(
  314. iconProvider: Assets.images.iconEmojiPercent.provider(),
  315. title: StringName.intimacyintimacyRatio,
  316. value: _convertPercentValue2ProgressBar(
  317. analyzeResult.intimacyRatio ?? "",
  318. ),
  319. progressColors: [ColorName.pinkGradient1, ColorName.pinkGradient2],
  320. ),
  321. SizedBox(height: 18.h),
  322. // 预测方向占比
  323. _buildValueItem(
  324. iconProvider: Assets.images.iconEmojiLove.provider(),
  325. title: analyzeResult.directionName ?? "",
  326. value: _convertPercentValue2ProgressBar(
  327. analyzeResult.directionRatio ?? "",
  328. ),
  329. progressColors: [
  330. ColorName.purpleGradient1,
  331. ColorName.purpleGradient2,
  332. ],
  333. ),
  334. SizedBox(height: 12, width: double.infinity),
  335. ],
  336. );
  337. }
  338. /// 构建数值Item
  339. /// [iconProvider] icon图标的ImageProvider
  340. /// [title] 标题
  341. /// [progressColors] 进度条渐变色
  342. /// [value] 进度条的数值,从0到1.0
  343. Widget _buildValueItem({
  344. required ImageProvider iconProvider,
  345. required String title,
  346. required List<Color> progressColors,
  347. required double value,
  348. }) {
  349. return Row(
  350. mainAxisSize: MainAxisSize.min,
  351. crossAxisAlignment: CrossAxisAlignment.center,
  352. children: [
  353. // 图标
  354. Container(
  355. padding: EdgeInsets.all(6.w),
  356. decoration: BoxDecoration(
  357. // 圆形背景
  358. shape: BoxShape.circle,
  359. color: ColorName.bgReportValueIcon,
  360. ),
  361. child: ClipOval(
  362. child: Image(
  363. image: iconProvider,
  364. width: 15.w,
  365. height: 15.w,
  366. fit: BoxFit.fill,
  367. ),
  368. ),
  369. ),
  370. SizedBox(width: 5.w),
  371. // 文字
  372. Text(title),
  373. SizedBox(width: 10.w),
  374. // 进度条
  375. Expanded(
  376. child: AnimatedGradientProgressBar(
  377. // 进度值
  378. targetValue: value,
  379. // 渐变色
  380. gradient: LinearGradient(colors: progressColors),
  381. // 动画时长
  382. duration: const Duration(milliseconds: 1500),
  383. ),
  384. ),
  385. ],
  386. );
  387. }
  388. /// 报告详情,使用Markdown
  389. Widget _buildReportDetailByMarkdown() {
  390. return Container(
  391. decoration: ShapeDecoration(
  392. color: ColorName.white,
  393. shape: RoundedRectangleBorder(
  394. borderRadius: BorderRadius.circular(30.r),
  395. ),
  396. ),
  397. child: MarkdownViewer(
  398. // 内容
  399. content: reportContent,
  400. // 不允许滚动
  401. enableContentScroll: false,
  402. ),
  403. );
  404. }
  405. /// 报告详情,使用组件渲染
  406. Widget _buildReportDetail() {
  407. if (intimacyAnalyzeResult == null) {
  408. return SizedBox();
  409. }
  410. // 组装数据
  411. IntimacyAnalyseReport reportPreviewData = IntimacyAnalyseReport(
  412. list: [
  413. AnalyseItem(
  414. title: StringName.intimacyintimacyEmotionNeed,
  415. sections: [intimacyAnalyzeResult?.need ?? ""],
  416. ),
  417. AnalyseItem(
  418. title: StringName.intimacyintimacyChatStrategy,
  419. sections: [intimacyAnalyzeResult?.chatStrategy ?? ""],
  420. ),
  421. AnalyseItem(
  422. title: StringName.intimacyintimacySummary,
  423. sections: [intimacyAnalyzeResult?.summary ?? ""],
  424. ),
  425. ],
  426. );
  427. return _buildReportContent(reportPreviewData);
  428. }
  429. /// 构建报告内容
  430. Widget _buildReportContent(IntimacyAnalyseReport report) {
  431. return Container(
  432. padding: EdgeInsets.all(14.w),
  433. decoration: ShapeDecoration(
  434. color: ColorName.white,
  435. shape: RoundedRectangleBorder(
  436. borderRadius: BorderRadius.circular(30.r),
  437. ),
  438. ),
  439. child: Column(
  440. children: [
  441. // 报告列表
  442. ...report.list.map((ele) {
  443. return ReportItemWidget(item: ele);
  444. }),
  445. ],
  446. ),
  447. );
  448. }
  449. }
  450. /// 报告生成中的卡片
  451. class CreatingReportCardWidget extends StatelessWidget {
  452. const CreatingReportCardWidget({super.key});
  453. @override
  454. Widget build(BuildContext context) {
  455. return ReportCardFrameWidget(
  456. child: Container(
  457. padding: EdgeInsets.only(top: 33.h, bottom: 52.h),
  458. decoration: ShapeDecoration(
  459. color: ColorName.white,
  460. shape: RoundedRectangleBorder(
  461. borderRadius: BorderRadius.circular(30.r),
  462. ),
  463. ),
  464. child: Center(
  465. child: Column(
  466. // 垂直水平都居中
  467. mainAxisAlignment: MainAxisAlignment.center,
  468. crossAxisAlignment: CrossAxisAlignment.center,
  469. children: [
  470. // 图标
  471. Lottie.asset(
  472. Assets.anim.animIntimacyAnalyseCreatingReportData,
  473. repeat: true,
  474. width: 82.w,
  475. height: 82.w,
  476. ),
  477. SizedBox(height: 3.h),
  478. // 文字
  479. Text(
  480. StringName.intimacyAnalyseReportCreating,
  481. style: TextStyle(
  482. fontSize: 14.sp,
  483. color: ColorName.black60,
  484. fontWeight: FontWeight.w400,
  485. ),
  486. ),
  487. ],
  488. ),
  489. ),
  490. ),
  491. );
  492. }
  493. }
  494. /// 未解锁时的报告卡片
  495. class UnlockReportCardWidget extends StatelessWidget {
  496. const UnlockReportCardWidget({super.key});
  497. @override
  498. Widget build(BuildContext context) {
  499. return ReportCardFrameWidget(
  500. child: Container(
  501. padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 6.w),
  502. decoration: ShapeDecoration(
  503. color: ColorName.white,
  504. shape: RoundedRectangleBorder(
  505. borderRadius: BorderRadius.circular(30.r),
  506. ),
  507. ),
  508. child: Center(
  509. child: Column(
  510. // 垂直水平都居中
  511. mainAxisAlignment: MainAxisAlignment.center,
  512. crossAxisAlignment: CrossAxisAlignment.center,
  513. children: [
  514. // 占位图
  515. Assets.images.iconIntimacyAnalyseReportUnlockPlaceholder.image(
  516. width: 300.w,
  517. height: 303.h,
  518. ),
  519. ],
  520. ),
  521. ),
  522. ),
  523. );
  524. }
  525. }
  526. /// 报告卡片框架组件,只定义报告卡片的基本框架,例如卡片背景和顶部的标题,内容由子组件来实现
  527. class ReportCardFrameWidget extends StatelessWidget {
  528. /// 内容子组件
  529. final Widget child;
  530. const ReportCardFrameWidget({super.key, required this.child});
  531. @override
  532. Widget build(BuildContext context) {
  533. // 卡片背景
  534. return Container(
  535. margin: EdgeInsets.only(left: 12, right: 12, bottom: 0),
  536. padding: EdgeInsets.only(bottom: 12),
  537. decoration: BoxDecoration(
  538. // image: DecorationImage(
  539. // image: Assets.images.bgIntimacyAnalyseReportPreview.provider(),
  540. // fit: BoxFit.fill,
  541. // ),
  542. // 渐变背景
  543. gradient: LinearGradient(
  544. colors: [Color(0xFFE9E2FF), Color(0xFFF1F5FF)],
  545. begin: Alignment.centerLeft,
  546. end: Alignment.centerRight,
  547. ),
  548. borderRadius: BorderRadius.all(Radius.circular(20.r)),
  549. ),
  550. child: Column(
  551. mainAxisSize: MainAxisSize.min,
  552. children: [
  553. // 顶部的图标和标题
  554. _buildReportTopLayout(),
  555. // 内容区域
  556. Container(
  557. padding: EdgeInsets.only(top: 3, left: 12, right: 12, bottom: 5),
  558. child: child,
  559. ),
  560. ],
  561. ),
  562. );
  563. }
  564. /// 报告的顶部布局-包含:图标和标题
  565. Widget _buildReportTopLayout() {
  566. return Container(
  567. margin: EdgeInsets.only(left: 12.w, top: 12.h),
  568. child: Row(
  569. children: [
  570. Assets.images.iconIntimacyAnalyseReportPreviewLove.image(
  571. width: 50,
  572. height: 48,
  573. ),
  574. Assets.images.iconIntimacyAnalyseReportPreviewTitle.image(
  575. width: 178,
  576. height: 24,
  577. ),
  578. ],
  579. ),
  580. );
  581. }
  582. }