intimacy_analyse_report_widget.dart 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614
  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 (!unlock) {
  40. return UnlockReportCardWidget();
  41. }
  42. // 报告生成中
  43. if (isReportCreating) {
  44. return CreatingReportCardWidget();
  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. _buildValueItem(
  284. iconProvider: Assets.images.iconEmojiLike.provider(),
  285. title: StringName.intimacyInteraction,
  286. value: _convertPercentValue2ProgressBar(
  287. analyzeResult.interaction ?? "",
  288. ),
  289. progressColors: [ColorName.blueGradient1, ColorName.blueGradient2],
  290. ),
  291. SizedBox(height: 18.h),
  292. _buildValueItem(
  293. iconProvider: Assets.images.iconEmojiChat.provider(),
  294. title: StringName.intimacyTopic,
  295. value: _convertPercentValue2ProgressBar(analyzeResult.topic ?? ""),
  296. progressColors: [ColorName.greenGradient1, ColorName.greenGradient2],
  297. ),
  298. SizedBox(height: 18.h),
  299. _buildValueItem(
  300. iconProvider: Assets.images.iconEmojiLike.provider(),
  301. title: StringName.intimacyRespond,
  302. value: _convertPercentValue2ProgressBar(analyzeResult.respond ?? ""),
  303. progressColors: [
  304. ColorName.yellowGradient1,
  305. ColorName.yellowGradient2,
  306. ],
  307. ),
  308. SizedBox(height: 18.h),
  309. _buildValueItem(
  310. iconProvider: Assets.images.iconEmojiPercent.provider(),
  311. title: StringName.intimacyintimacyRatio,
  312. value: _convertPercentValue2ProgressBar(
  313. analyzeResult.intimacyRatio ?? "",
  314. ),
  315. progressColors: [ColorName.pinkGradient1, ColorName.pinkGradient2],
  316. ),
  317. SizedBox(height: 18.h),
  318. _buildValueItem(
  319. iconProvider: Assets.images.iconEmojiLove.provider(),
  320. title: analyzeResult.directionName ?? "",
  321. value: _convertPercentValue2ProgressBar(
  322. analyzeResult.direction ?? "",
  323. ),
  324. progressColors: [
  325. ColorName.purpleGradient1,
  326. ColorName.purpleGradient2,
  327. ],
  328. ),
  329. SizedBox(height: 12, width: double.infinity),
  330. ],
  331. );
  332. }
  333. /// 构建数值Item
  334. /// [iconProvider] icon图标的ImageProvider
  335. /// [title] 标题
  336. /// [progressColors] 进度条渐变色
  337. /// [value] 进度条的数值,从0到1.0
  338. Widget _buildValueItem({
  339. required ImageProvider iconProvider,
  340. required String title,
  341. required List<Color> progressColors,
  342. required double value,
  343. }) {
  344. return Row(
  345. mainAxisSize: MainAxisSize.min,
  346. crossAxisAlignment: CrossAxisAlignment.center,
  347. children: [
  348. // 图标
  349. Container(
  350. padding: EdgeInsets.all(6.w),
  351. decoration: BoxDecoration(
  352. // 圆形背景
  353. shape: BoxShape.circle,
  354. color: ColorName.bgReportValueIcon,
  355. ),
  356. child: ClipOval(
  357. child: Image(
  358. image: iconProvider,
  359. width: 15.w,
  360. height: 15.w,
  361. fit: BoxFit.fill,
  362. ),
  363. ),
  364. ),
  365. SizedBox(width: 5.w),
  366. // 文字
  367. Text(title),
  368. SizedBox(width: 10.w),
  369. // 进度条
  370. Expanded(
  371. child: AnimatedGradientProgressBar(
  372. // 进度值
  373. targetValue: value,
  374. // 渐变色
  375. gradient: LinearGradient(colors: progressColors),
  376. // 动画时长
  377. duration: const Duration(milliseconds: 1500),
  378. ),
  379. ),
  380. ],
  381. );
  382. }
  383. /// 报告详情,使用Markdown
  384. Widget _buildReportDetailByMarkdown() {
  385. return Container(
  386. decoration: ShapeDecoration(
  387. color: ColorName.white,
  388. shape: RoundedRectangleBorder(
  389. borderRadius: BorderRadius.circular(30.r),
  390. ),
  391. ),
  392. child: MarkdownViewer(
  393. // 内容
  394. content: reportContent,
  395. // 不允许滚动
  396. enableContentScroll: false,
  397. ),
  398. );
  399. }
  400. /// 报告详情,使用组件渲染
  401. Widget _buildReportDetail() {
  402. if (intimacyAnalyzeResult == null) {
  403. return SizedBox();
  404. }
  405. // 组装数据
  406. IntimacyAnalyseReport reportPreviewData = IntimacyAnalyseReport(
  407. list: [
  408. AnalyseItem(
  409. title: StringName.intimacyintimacyEmotionNeed,
  410. sections: [intimacyAnalyzeResult?.need ?? ""],
  411. ),
  412. AnalyseItem(
  413. title: StringName.intimacyintimacyChatStrategy,
  414. sections: [intimacyAnalyzeResult?.chatStrategy ?? ""],
  415. ),
  416. AnalyseItem(
  417. title: StringName.intimacyintimacySummary,
  418. sections: [intimacyAnalyzeResult?.summary ?? ""],
  419. ),
  420. ],
  421. );
  422. return _buildReportContent(reportPreviewData);
  423. }
  424. /// 构建报告内容
  425. Widget _buildReportContent(IntimacyAnalyseReport report) {
  426. return Container(
  427. padding: EdgeInsets.all(14.w),
  428. decoration: ShapeDecoration(
  429. color: ColorName.white,
  430. shape: RoundedRectangleBorder(
  431. borderRadius: BorderRadius.circular(30.r),
  432. ),
  433. ),
  434. child: Column(
  435. children: [
  436. // 报告列表
  437. ...report.list.map((ele) {
  438. return ReportItemWidget(item: ele);
  439. }),
  440. ],
  441. ),
  442. );
  443. }
  444. }
  445. /// 报告生成中的卡片
  446. class CreatingReportCardWidget extends StatelessWidget {
  447. const CreatingReportCardWidget({super.key});
  448. @override
  449. Widget build(BuildContext context) {
  450. return ReportCardFrameWidget(
  451. child: Container(
  452. padding: EdgeInsets.only(top: 33.h, bottom: 52.h),
  453. decoration: ShapeDecoration(
  454. color: ColorName.white,
  455. shape: RoundedRectangleBorder(
  456. borderRadius: BorderRadius.circular(30.r),
  457. ),
  458. ),
  459. child: Center(
  460. child: Column(
  461. // 垂直水平都居中
  462. mainAxisAlignment: MainAxisAlignment.center,
  463. crossAxisAlignment: CrossAxisAlignment.center,
  464. children: [
  465. // 图标
  466. Lottie.asset(
  467. Assets.anim.animIntimacyAnalyseCreatingReportData,
  468. repeat: true,
  469. width: 82.w,
  470. height: 82.w,
  471. ),
  472. SizedBox(height: 3.h),
  473. // 文字
  474. Text(
  475. StringName.intimacyAnalyseReportCreating,
  476. style: TextStyle(
  477. fontSize: 14.sp,
  478. color: ColorName.black60,
  479. fontWeight: FontWeight.w400,
  480. ),
  481. ),
  482. ],
  483. ),
  484. ),
  485. ),
  486. );
  487. }
  488. }
  489. /// 未解锁时的报告卡片
  490. class UnlockReportCardWidget extends StatelessWidget {
  491. const UnlockReportCardWidget({super.key});
  492. @override
  493. Widget build(BuildContext context) {
  494. return ReportCardFrameWidget(
  495. child: Container(
  496. padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 6.w),
  497. decoration: ShapeDecoration(
  498. color: ColorName.white,
  499. shape: RoundedRectangleBorder(
  500. borderRadius: BorderRadius.circular(30.r),
  501. ),
  502. ),
  503. child: Center(
  504. child: Column(
  505. // 垂直水平都居中
  506. mainAxisAlignment: MainAxisAlignment.center,
  507. crossAxisAlignment: CrossAxisAlignment.center,
  508. children: [
  509. // 占位图
  510. Assets.images.iconIntimacyAnalyseReportUnlockPlaceholder.image(
  511. width: 300.w,
  512. height: 303.h,
  513. ),
  514. ],
  515. ),
  516. ),
  517. ),
  518. );
  519. }
  520. }
  521. /// 报告卡片框架组件,只定义报告卡片的基本框架,例如卡片背景和顶部的标题,内容由子组件来实现
  522. class ReportCardFrameWidget extends StatelessWidget {
  523. /// 内容子组件
  524. final Widget child;
  525. const ReportCardFrameWidget({super.key, required this.child});
  526. @override
  527. Widget build(BuildContext context) {
  528. // 卡片背景
  529. return Container(
  530. margin: EdgeInsets.only(left: 12, right: 12, bottom: 0),
  531. padding: EdgeInsets.only(bottom: 12),
  532. decoration: BoxDecoration(
  533. // image: DecorationImage(
  534. // image: Assets.images.bgIntimacyAnalyseReportPreview.provider(),
  535. // fit: BoxFit.fill,
  536. // ),
  537. // 渐变背景
  538. gradient: LinearGradient(
  539. colors: [Color(0xFFE9E2FF), Color(0xFFF1F5FF)],
  540. begin: Alignment.centerLeft,
  541. end: Alignment.centerRight,
  542. ),
  543. borderRadius: BorderRadius.all(Radius.circular(20.r)),
  544. ),
  545. child: Column(
  546. mainAxisSize: MainAxisSize.min,
  547. children: [
  548. // 顶部的图标和标题
  549. _buildReportTopLayout(),
  550. // 内容区域
  551. Container(
  552. padding: EdgeInsets.only(top: 3, left: 12, right: 12, bottom: 5),
  553. child: child,
  554. ),
  555. ],
  556. ),
  557. );
  558. }
  559. /// 报告的顶部布局-包含:图标和标题
  560. Widget _buildReportTopLayout() {
  561. return Container(
  562. margin: EdgeInsets.only(left: 12.w, top: 12.h),
  563. child: Row(
  564. children: [
  565. Assets.images.iconIntimacyAnalyseReportPreviewLove.image(
  566. width: 50,
  567. height: 48,
  568. ),
  569. Assets.images.iconIntimacyAnalyseReportPreviewTitle.image(
  570. width: 178,
  571. height: 24,
  572. ),
  573. ],
  574. ),
  575. );
  576. }
  577. }