intimacy_analyse_report_widget.dart 18 KB

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