view.dart 12 KB

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