view.dart 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  1. import 'package:electronic_assistant/base/base_page.dart';
  2. import 'package:electronic_assistant/module/talk/controller.dart';
  3. import 'package:electronic_assistant/resource/colors.gen.dart';
  4. import 'package:electronic_assistant/utils/expand.dart';
  5. import 'package:electronic_assistant/utils/fixed_size_tab_indicator.dart';
  6. import 'package:flutter/material.dart';
  7. import 'package:flutter/services.dart';
  8. import 'package:flutter_screenutil/flutter_screenutil.dart';
  9. import 'package:get/get.dart';
  10. import '../../data/bean/talks.dart';
  11. import '../../resource/assets.gen.dart';
  12. import '../../resource/string.gen.dart';
  13. import '../../router/app_pages.dart';
  14. import '../../utils/common_style.dart';
  15. class TalkPage extends BasePage<TalkController> {
  16. const TalkPage({super.key});
  17. static void start(TalkBean item) {
  18. Get.toNamed(RoutePath.talkDetail, arguments: item);
  19. }
  20. @override
  21. Widget buildBody(BuildContext context) {
  22. return DefaultTabController(
  23. length: controller.tabBeans.length,
  24. child: Stack(
  25. children: [
  26. buildTopGradient(),
  27. Scaffold(
  28. backgroundColor: Colors.transparent,
  29. appBar: AppBar(
  30. systemOverlayStyle: SystemUiOverlayStyle.dark,
  31. backgroundColor: Colors.transparent,
  32. leading: IconButton(
  33. icon: SizedBox(
  34. width: 24.w,
  35. height: 24.w,
  36. child: Assets.images.iconBack.image()),
  37. // Custom icon
  38. onPressed: () {
  39. Get.back();
  40. },
  41. ),
  42. ),
  43. body: Column(
  44. crossAxisAlignment: CrossAxisAlignment.start,
  45. children: [
  46. Padding(
  47. padding: EdgeInsets.symmetric(horizontal: 12.w),
  48. child: Obx(() {
  49. return Column(
  50. crossAxisAlignment: CrossAxisAlignment.start,
  51. children: [
  52. SizedBox(height: 8.h),
  53. Text(controller.talkBean.value?.title ?? '',
  54. style: TextStyle(
  55. fontSize: 22.sp,
  56. fontWeight: FontWeight.bold,
  57. color: ColorName.primaryTextColor)),
  58. SizedBox(height: 4.h),
  59. Text(controller.talkBean.value?.createTime ?? '',
  60. style: TextStyle(
  61. fontSize: 12.sp,
  62. color: ColorName.secondaryTextColor)),
  63. SizedBox(height: 14.h),
  64. ],
  65. );
  66. }),
  67. ),
  68. buildTabBar(),
  69. Divider(
  70. height: 1,
  71. color: const Color(0xFFf6f6f6),
  72. indent: 12.w,
  73. endIndent: 12.w),
  74. SizedBox(height: 8.h),
  75. buildTalkContentView()
  76. ],
  77. ),
  78. ),
  79. buildBottomView(),
  80. buildAIAnalysisView()
  81. ],
  82. ),
  83. );
  84. }
  85. Container buildTabBar() {
  86. return Container(
  87. decoration: const BoxDecoration(
  88. color: Colors.white,
  89. borderRadius: BorderRadius.only(
  90. topLeft: Radius.circular(12),
  91. topRight: Radius.circular(12),
  92. )),
  93. child: TabBar(
  94. labelStyle: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.bold),
  95. unselectedLabelStyle: TextStyle(fontSize: 14.sp),
  96. labelColor: ColorName.primaryTextColor,
  97. unselectedLabelColor: ColorName.secondaryTextColor,
  98. labelPadding: EdgeInsets.only(top: 4.h),
  99. dividerHeight: 0,
  100. indicator: FixedSizeTabIndicator(
  101. width: 16.w,
  102. height: 3.w,
  103. radius: 3,
  104. color: ColorName.colorPrimary),
  105. tabs: controller.tabBeans.map((txt) => Tab(text: txt)).toList()),
  106. );
  107. }
  108. @override
  109. bool immersive() {
  110. return true;
  111. }
  112. Container buildTopGradient() {
  113. return Container(
  114. width: 1.sw,
  115. height: 180.h,
  116. decoration: BoxDecoration(
  117. gradient: LinearGradient(
  118. colors: ['#E1E9FF'.toColor(), '#F9FAFE'.toColor()],
  119. begin: Alignment.topCenter,
  120. end: Alignment.bottomCenter,
  121. stops: const [0.3, 1.0],
  122. ),
  123. ));
  124. }
  125. Widget buildTalkContentView() {
  126. return Obx(() {
  127. if (controller.talkBean.value?.status == TalkStatus.notAnalysis) {
  128. if (controller.isShowElectricLow.value) {
  129. return buildElectricLowView();
  130. } else {
  131. return buildNotAnalysisView();
  132. }
  133. } else {
  134. return buildTabContentView();
  135. }
  136. });
  137. }
  138. Widget buildTabContentView() {
  139. return Expanded(
  140. child: TabBarView(
  141. children: controller.pages,
  142. ),
  143. );
  144. }
  145. Widget buildNotAnalysisView() {
  146. return SizedBox(
  147. width: double.infinity,
  148. child: Column(
  149. children: [
  150. SizedBox(height: 119.h),
  151. SizedBox(
  152. width: 100.w,
  153. height: 100.w,
  154. child: Assets.images.iconTalkSummaryUnanalyzed.image()),
  155. SizedBox(height: 4.h),
  156. Text(StringName.talkUnAnalyzed.tr,
  157. style: TextStyle(
  158. fontSize: 15.sp, color: ColorName.primaryTextColor)),
  159. SizedBox(height: 2.h),
  160. Text(StringName.talkUnAnalyzedTips.tr,
  161. style: TextStyle(
  162. fontSize: 12.sp, color: ColorName.secondaryTextColor)),
  163. SizedBox(height: 24.h),
  164. GestureDetector(
  165. onTap: () {
  166. controller.checkCanAnalyze();
  167. },
  168. child: Container(
  169. decoration: getPrimaryBtnDecoration(8),
  170. width: 240.w,
  171. height: 48.w,
  172. child: Center(
  173. child: Text(
  174. StringName.talkAnalyzedBtnTxt.tr,
  175. style: TextStyle(fontSize: 16.sp, color: ColorName.white),
  176. ),
  177. ),
  178. ),
  179. )
  180. ],
  181. ),
  182. );
  183. }
  184. Widget buildElectricLowView() {
  185. return Container(
  186. color: const Color(0xFFDFE4FC),
  187. padding: EdgeInsets.only(left: 16.w, right: 12.w, top: 8.h, bottom: 8.h),
  188. child: Row(
  189. children: [
  190. SizedBox(
  191. width: 46.w,
  192. height: 56.w,
  193. child: Assets.images.iconTalkElectricLow.image()),
  194. SizedBox(width: 10.w),
  195. IntrinsicHeight(
  196. child: Column(
  197. crossAxisAlignment: CrossAxisAlignment.start,
  198. children: [
  199. SizedBox(
  200. width: 90.w,
  201. height: 21.w,
  202. child: Assets.images.iconTalkElectricLowTxt.image()),
  203. SizedBox(width: 1.w),
  204. Text(StringName.talkElectricLow.tr,
  205. style: TextStyle(
  206. fontSize: 12.sp, color: ColorName.secondaryTextColor)),
  207. ],
  208. ),
  209. ),
  210. const Spacer(),
  211. GestureDetector(
  212. onTap: () {
  213. controller.goElectricStore();
  214. },
  215. child: Container(
  216. decoration: getPrimaryBtnDecoration(8),
  217. width: 100.w,
  218. height: 36.w,
  219. child: Center(
  220. child: Text(StringName.talkGoStore.tr,
  221. style:
  222. TextStyle(fontSize: 16.sp, color: ColorName.white)),
  223. )),
  224. )
  225. ],
  226. ),
  227. );
  228. }
  229. Widget buildBottomView() {
  230. return Align(
  231. alignment: Alignment.bottomCenter,
  232. child: Container(
  233. margin: EdgeInsets.only(bottom: 20.h), // 设置底部偏移距离
  234. child: IntrinsicHeight(
  235. child: Column(
  236. crossAxisAlignment: CrossAxisAlignment.end,
  237. children: [
  238. Obx(() {
  239. return Visibility(
  240. visible: controller.talkBean.value?.status ==
  241. TalkStatus.analysisSuccess,
  242. child: GestureDetector(
  243. onTap: () {
  244. controller.clickAIAnalysis();
  245. },
  246. child: Container(
  247. margin: EdgeInsets.only(right: 8.w),
  248. width: 64.w,
  249. height: 64.w,
  250. child: Assets.images.iconTalkLogo.image()),
  251. ),
  252. );
  253. }),
  254. SizedBox(height: 24.h),
  255. buildAudioView()
  256. ],
  257. ),
  258. ),
  259. ),
  260. );
  261. }
  262. buildAIAnalysisView() {
  263. return Container();
  264. }
  265. buildAudioView() {
  266. return Container(
  267. decoration: BoxDecoration(
  268. color: Colors.white,
  269. borderRadius: BorderRadius.circular(100),
  270. boxShadow: [
  271. BoxShadow(
  272. color: ColorName.black5.withOpacity(0.1),
  273. spreadRadius: 2,
  274. blurRadius: 12,
  275. offset: const Offset(0, 3),
  276. ),
  277. ],
  278. ),
  279. margin: EdgeInsets.symmetric(horizontal: 12.w),
  280. padding: EdgeInsets.symmetric(vertical: 6.w),
  281. child: Row(
  282. children: [
  283. SizedBox(width: 9.w),
  284. GestureDetector(
  285. onTap: () {
  286. controller.clickPlayAudio();
  287. },
  288. child: Obx(() {
  289. return SizedBox(
  290. width: 36.w,
  291. height: 36.w,
  292. child: (controller.isAudioPlaying.value)
  293. ? Assets.images.iconTalkAudioPlaying.image()
  294. : Assets.images.iconTalkAudioPause.image());
  295. }),
  296. ),
  297. SizedBox(width: 15.w),
  298. Builder(builder: (context) {
  299. return SizedBox(
  300. width: 226.w,
  301. height: 18.w,
  302. child: Obx(() {
  303. return SliderTheme(
  304. data: SliderTheme.of(context).copyWith(
  305. thumbColor: Colors.white,
  306. overlayShape: SliderComponentShape.noOverlay,
  307. trackHeight: 8,
  308. activeTrackColor: "#8A89E9".toColor(),
  309. inactiveTrackColor: "#F6F5F8".toColor(),
  310. trackShape: CustomTrackShape(),
  311. ),
  312. child: Slider(
  313. value: controller.audioProgressValue.value,
  314. min: 0.0,
  315. max: controller.sliderMax,
  316. onChanged: (value) {
  317. controller.updateProgress(value);
  318. },
  319. ),
  320. );
  321. }),
  322. );
  323. }),
  324. SizedBox(width: 11.w),
  325. Obx(() {
  326. return Text(controller.audioDuration.value.toFormattedString(),
  327. style: TextStyle(
  328. fontSize: 10.sp, color: ColorName.secondaryTextColor));
  329. })
  330. ],
  331. ),
  332. );
  333. }
  334. }
  335. class CustomTrackShape extends RoundedRectSliderTrackShape {
  336. @override
  337. Rect getPreferredRect({
  338. required RenderBox parentBox,
  339. Offset offset = Offset.zero,
  340. required SliderThemeData sliderTheme,
  341. bool isEnabled = false,
  342. bool isDiscrete = false,
  343. }) {
  344. final trackHeight = sliderTheme.trackHeight;
  345. final trackLeft = offset.dx;
  346. final trackTop = offset.dy + (parentBox.size.height - trackHeight!) / 2;
  347. final trackWidth = parentBox.size.width;
  348. return Rect.fromLTWH(trackLeft, trackTop, trackWidth, trackHeight);
  349. }
  350. }
  351. // class EnhancedShadowSliderThumbShape extends RoundSliderThumbShape {
  352. // final double thumbRadius;
  353. // final double elevation;
  354. // final double shadowOffsetY;
  355. //
  356. // EnhancedShadowSliderThumbShape({
  357. // required this.thumbRadius,
  358. // this.elevation = 8.0,
  359. // this.shadowOffsetY = 0, // 阴影向下偏移量
  360. // });
  361. //
  362. // @override
  363. // void paint(
  364. // PaintingContext context,
  365. // Offset center, {
  366. // required Animation<double> activationAnimation,
  367. // required Animation<double> enableAnimation,
  368. // required bool isDiscrete,
  369. // required TextPainter labelPainter,
  370. // required RenderBox parentBox,
  371. // required SliderThemeData sliderTheme,
  372. // required TextDirection textDirection,
  373. // required double value,
  374. // required double textScaleFactor,
  375. // required Size sizeWithOverflow,
  376. // }) {
  377. // final Canvas canvas = context.canvas;
  378. // final Paint shadowPaint = Paint()
  379. // ..color = Colors.black.withOpacity(0.25)
  380. // ..maskFilter = MaskFilter.blur(BlurStyle.normal, elevation);
  381. //
  382. // final Paint thumbPaint = Paint()
  383. // ..color = sliderTheme.thumbColor!
  384. // ..style = PaintingStyle.fill;
  385. //
  386. // // 调整阴影的绘制位置,使其向下偏移
  387. // canvas.drawCircle(center.translate(0, shadowOffsetY), thumbRadius + 2, shadowPaint);
  388. // canvas.drawCircle(center, thumbRadius, thumbPaint);
  389. // }
  390. // }