video_play_item_widget.dart 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. import 'dart:io';
  2. import 'package:better_player_plus/better_player_plus.dart';
  3. import 'package:flutter/material.dart';
  4. import 'package:flutter_riverpod/flutter_riverpod.dart';
  5. import 'package:flutter_screenutil/flutter_screenutil.dart';
  6. import 'package:go_router/go_router.dart';
  7. import 'package:news_app/ui/video/video_detail_page.dart';
  8. import '../../constant/config.dart';
  9. import '../../gen/assets.gen.dart';
  10. import '../../model/video_new_model.dart';
  11. import '../../util/device_util.dart';
  12. import '../../util/log.util.dart';
  13. import '../../util/share_util.dart';
  14. import '../../widget/auth_gesture_detector.dart';
  15. import '../../widget/load_image.dart';
  16. import '../../widget/my_txt.dart';
  17. import '../video/video_recommend_list_page.dart';
  18. class VideoPlayItemWidget extends ConsumerStatefulWidget {
  19. final VideoNewModel item;
  20. final bool isActive;
  21. final int index;
  22. const VideoPlayItemWidget({
  23. super.key,
  24. required this.item,
  25. required this.isActive,
  26. required this.index,
  27. });
  28. @override
  29. ConsumerState<VideoPlayItemWidget> createState() => _VideoItemWidgetState();
  30. }
  31. class _VideoItemWidgetState extends ConsumerState<VideoPlayItemWidget>
  32. with AutomaticKeepAliveClientMixin {
  33. bool _isPlaying = false;
  34. bool _isWeChatInstalled = false;
  35. Future<void> _checkWeChatInstallation() async {
  36. if (Platform.isAndroid) {
  37. final installed = await isWeChatInstalledOnlyAndroid();
  38. if (mounted) {
  39. setState(() => _isWeChatInstalled = installed);
  40. }
  41. } else if (Platform.isIOS) {
  42. final installed = await fluwx.isWeChatInstalled;
  43. if (mounted) {
  44. setState(() => _isWeChatInstalled = installed);
  45. }
  46. }
  47. }
  48. Future<void> shareAction(VideoNewModel data) async {
  49. if (!mounted) return;
  50. showModalBottomSheet(
  51. context: context,
  52. builder:
  53. (context) => SafeArea(
  54. child: Column(
  55. mainAxisSize: MainAxisSize.min,
  56. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  57. children: [
  58. SizedBox(height: 10.h),
  59. SizedBox(
  60. width: double.infinity,
  61. height: 80.h,
  62. child: Row(
  63. crossAxisAlignment: CrossAxisAlignment.center,
  64. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  65. children: [
  66. if (_isWeChatInstalled)
  67. GestureDetector(
  68. onTap: () {
  69. Navigator.pop(context);
  70. shareWeiXinUrl(
  71. title: data.shareDesc ?? "",
  72. url: data.shareUrl ?? "",
  73. );
  74. },
  75. child: SizedBox(
  76. width: 100.w,
  77. height: 80.h,
  78. child: Column(
  79. crossAxisAlignment: CrossAxisAlignment.center,
  80. mainAxisAlignment: MainAxisAlignment.center,
  81. children: [
  82. LoadAssetImage(
  83. 'share_wxhy',
  84. width: 40.w,
  85. height: 40.h,
  86. ),
  87. SizedBox(height: 10.h),
  88. myTxt(
  89. text: "微信好友",
  90. color: Colors.black,
  91. fontSize: 12.sp,
  92. fontWeight: FontWeight.bold,
  93. ),
  94. ],
  95. ),
  96. ),
  97. ),
  98. if (_isWeChatInstalled)
  99. GestureDetector(
  100. onTap: () {
  101. Navigator.pop(context);
  102. shareWeiXinPYUrl(
  103. title: data.shareDesc ?? "",
  104. url: data.shareUrl ?? "",
  105. );
  106. },
  107. child: SizedBox(
  108. width: 100.w,
  109. height: 80.h,
  110. child: Column(
  111. mainAxisAlignment: CrossAxisAlignment.center,
  112. children: [
  113. LoadAssetImage(
  114. 'share_pyq',
  115. width: 40.w,
  116. height: 40.h,
  117. ),
  118. SizedBox(height: 10.h),
  119. myTxt(
  120. text: "朋友圈",
  121. color: Colors.black,
  122. fontSize: 12.sp,
  123. fontWeight: FontWeight.bold,
  124. ),
  125. ],
  126. ),
  127. ),
  128. ),
  129. GestureDetector(
  130. onTap: () {
  131. Navigator.pop(context);
  132. shareUrl(
  133. title: data.shareDesc ?? "",
  134. url: data.shareUrl ?? "",
  135. );
  136. },
  137. child: SizedBox(
  138. width: 100.w,
  139. height: 80.h,
  140. child: Column(
  141. mainAxisAlignment: MainAxisAlignment.center,
  142. children: [
  143. LoadAssetImage(
  144. 'share_xtfx',
  145. width: 40.w,
  146. height: 40.h,
  147. ),
  148. SizedBox(height: 10.h),
  149. myTxt(
  150. text: "系统分享",
  151. color: Colors.black,
  152. fontSize: 12.sp,
  153. fontWeight: FontWeight.bold,
  154. ),
  155. ],
  156. ),
  157. ),
  158. ),
  159. ],
  160. ),
  161. ),
  162. ],
  163. ),
  164. ),
  165. );
  166. }
  167. @override
  168. void initState() {
  169. super.initState();
  170. consoleLog("VideoPlayItemWidget: initState for video ${widget.item.contentId}, url: ${widget.item.url}, isActive: ${widget.isActive}");
  171. _checkWeChatInstallation();
  172. }
  173. void _togglePlay() {
  174. if (!mounted) return;
  175. final controller = globalVideoController;
  176. if (controller == null) return;
  177. // 分别处理每个操作,确保错误被捕获
  178. if (_isPlaying) {
  179. try {
  180. controller.pause();
  181. } catch (e) {
  182. // 控制器可能已被释放,忽略错误
  183. }
  184. } else {
  185. try {
  186. controller.play();
  187. } catch (e) {
  188. // 控制器可能已被释放,忽略错误
  189. }
  190. }
  191. }
  192. @override
  193. Widget build(BuildContext context) {
  194. super.build(context);
  195. final controller = globalVideoController;
  196. return GestureDetector(
  197. onTap: _togglePlay,
  198. child: Stack(
  199. fit: StackFit.expand,
  200. children: [
  201. ColoredBox(
  202. color: Colors.black,
  203. child: controller != null && widget.isActive
  204. ? AspectRatio(
  205. aspectRatio: 16 / 9,
  206. child: BetterPlayer(controller: controller),
  207. )
  208. : const SizedBox.shrink(),
  209. ),
  210. if (!_isPlaying && widget.isActive && controller != null)
  211. Center(
  212. child: Image.asset(
  213. Assets.images.playIcon.path,
  214. width: 60.w,
  215. height: 60.w,
  216. ),
  217. ),
  218. Positioned(
  219. right: 10.h,
  220. bottom: 100.h,
  221. child: Column(
  222. mainAxisAlignment: MainAxisAlignment.center,
  223. children: [
  224. AuthGestureDetector(
  225. onTap: () {
  226. if (!mounted) return;
  227. ref
  228. .read(recommendListProvider.notifier)
  229. .fetchVideoLike(
  230. videoId: widget.item.contentId,
  231. current: widget.item.isLiked ?? false,
  232. );
  233. },
  234. child: Icon(
  235. Icons.favorite,
  236. color:
  237. widget.item.isLiked == true ? Colors.red : Colors.white,
  238. size: 25.sp,
  239. ),
  240. ),
  241. myTxt(
  242. text:
  243. widget.item.likeCount == null
  244. ? ""
  245. : widget.item.likeCount.toString(),
  246. color: Colors.white,
  247. fontSize: 12.sp,
  248. ),
  249. SizedBox(height: 15.h),
  250. AuthGestureDetector(
  251. onTap: () {
  252. if (!mounted) return;
  253. context.push(
  254. "/video/detail",
  255. extra: VideoParam(
  256. id: widget.item.contentId ?? "",
  257. videoUrl: widget.item.url,
  258. ),
  259. );
  260. },
  261. child: Icon(Icons.message, color: Colors.white, size: 25.sp),
  262. ),
  263. myTxt(
  264. text:
  265. widget.item.commentCount == null
  266. ? "0"
  267. : widget.item.commentCount.toString(),
  268. color: Colors.white,
  269. fontSize: 12.sp,
  270. ),
  271. SizedBox(height: 15.h),
  272. AuthGestureDetector(
  273. onTap: () {
  274. if (!mounted) return;
  275. ref
  276. .read(recommendListProvider.notifier)
  277. .fetchVideoFavorite(
  278. videoId: widget.item.contentId,
  279. current: widget.item.isFavorite ?? false,
  280. );
  281. },
  282. child: Icon(
  283. Icons.star,
  284. color:
  285. widget.item.isFavorite == true
  286. ? Colors.red
  287. : Colors.white,
  288. size: 25.sp,
  289. ),
  290. ),
  291. myTxt(
  292. text:
  293. widget.item.favoriteCount == null
  294. ? "0"
  295. : widget.item.favoriteCount.toString(),
  296. color: Colors.white,
  297. fontSize: 12.sp,
  298. ),
  299. SizedBox(height: 15.h),
  300. GestureDetector(
  301. onTap: () {
  302. if (!mounted) return;
  303. shareAction(widget.item);
  304. if (uuid.isNotEmpty) {
  305. ref
  306. .read(recommendListProvider.notifier)
  307. .fetchVideoShare(contentId: widget.item.contentId);
  308. }
  309. },
  310. child: Icon(
  311. Icons.screen_share,
  312. color: Colors.white,
  313. size: 25.sp,
  314. ),
  315. ),
  316. myTxt(
  317. text:
  318. widget.item.shareCount == null
  319. ? "0"
  320. : widget.item.shareCount.toString(),
  321. color: Colors.white,
  322. fontSize: 12.sp,
  323. ),
  324. ],
  325. ),
  326. ),
  327. ],
  328. ),
  329. );
  330. }
  331. @override
  332. bool get wantKeepAlive => true;
  333. }