2026-01-13 11:36:24 +05:30

698 lines
21 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:onufitness/screens/u_vault/controllers/uvault_video_controller.dart';
import 'package:onufitness/utils/helper_function.dart';
import 'package:shimmer/shimmer.dart';
import 'package:video_player/video_player.dart';
import 'package:chewie/chewie.dart';
import 'package:get/get.dart';
import 'package:onufitness/constants/color_constant.dart';
class VideoCard extends StatefulWidget {
final String? title;
final String category;
final String videoUrl;
final String? videoId;
final String? thumbnailUrl;
final bool isMyVideoScreen;
final VoidCallback? onPressed;
const VideoCard({
super.key,
this.title,
required this.category,
required this.videoUrl,
this.videoId,
this.thumbnailUrl,
required this.isMyVideoScreen,
this.onPressed,
});
@override
State<VideoCard> createState() => _VideoCardState();
}
class _VideoCardState extends State<VideoCard> {
final controller = Get.find<UvaultController>();
ChewieController? _chewieController;
VideoPlayerController? _videoPlayerController;
bool _isVideoInitialized = false;
bool _isVideoPlaying = false;
bool _isInitializing = false;
bool _isDisposed = false;
Future<void> _initializeVideo() async {
if (_isInitializing || _isVideoInitialized || _isDisposed) return;
setState(() {
_isInitializing = true;
});
try {
controller.setCurrentPlayingVideo(widget.videoUrl);
_videoPlayerController = VideoPlayerController.networkUrl(
Uri.parse(widget.videoUrl),
);
await _videoPlayerController!.initialize();
if (_isDisposed || !mounted) {
await _videoPlayerController!.dispose();
return;
}
await _videoPlayerController!.setVolume(1.0);
_chewieController = ChewieController(
// Add Android-specific Color customization
materialProgressColors: ChewieProgressColors(
playedColor: Color(primaryColor),
handleColor: Color(primaryColor),
backgroundColor: Colors.grey,
bufferedColor: Colors.grey.shade300,
),
// Add iOS-specific Color customization
cupertinoProgressColors: ChewieProgressColors(
playedColor: Color(primaryColor),
handleColor: Color(primaryColor),
backgroundColor: Colors.grey,
bufferedColor: Colors.grey.shade300,
),
videoPlayerController: _videoPlayerController!,
autoPlay: true,
looping: false,
showControls: true,
aspectRatio: _videoPlayerController!.value.aspectRatio,
deviceOrientationsAfterFullScreen: [DeviceOrientation.portraitUp],
errorBuilder: (context, errorMessage) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.error, color: Colors.red, size: 40.sp),
SizedBox(height: 8.h),
Text(
'Video playback error',
style: TextStyle(color: Colors.red, fontSize: 12.sp),
),
],
),
);
},
);
if (mounted && !_isDisposed) {
setState(() {
_isVideoInitialized = true;
_isVideoPlaying = true;
_isInitializing = false;
});
} else {
// Clean up if component was disposed during initialization
await _disposeVideoControllers();
}
} catch (e) {
if (mounted && !_isDisposed) {
setState(() {
_isInitializing = false;
});
}
await _disposeVideoControllers();
}
}
Future<void> _disposeVideoControllers() async {
if (_chewieController != null) {
_chewieController!.dispose();
_chewieController!.pause();
_chewieController = null;
}
if (_videoPlayerController != null) {
await _videoPlayerController!.dispose();
await _videoPlayerController!.pause();
_videoPlayerController = null;
}
if (mounted && !_isDisposed) {
setState(() {
_isVideoInitialized = false;
_isVideoPlaying = false;
_isInitializing = false;
});
}
}
void _onThumbnailTap() {
if (!_isInitializing && !_isVideoInitialized) {
_initializeVideo();
}
}
@override
void dispose() {
_isDisposed = true;
_disposeVideoControllers();
super.dispose();
}
Widget _buildThumbnailView() {
return Stack(
children: [
// Thumbnail Image
if (widget.thumbnailUrl != null && widget.thumbnailUrl!.isNotEmpty)
Image.network(
widget.thumbnailUrl ??
"https://placehold.co/400?text=Thumbnail&font=roboto",
height: isTablet ? 250.h : 200.h,
width: double.infinity,
fit: BoxFit.cover,
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) return child;
return _buildShimmerPlaceholder();
},
errorBuilder: (context, error, stackTrace) {
return _buildErrorPlaceholder();
},
)
else
_buildErrorPlaceholder(),
// Play Button Overlay
Positioned.fill(
child: Container(
color: Colors.black26,
child: Center(
child: GestureDetector(
onTap: _onThumbnailTap,
child: Container(
width: 60.w,
height: 60.h,
decoration: BoxDecoration(
color: Colors.black54,
shape: BoxShape.circle,
),
child: Icon(
Icons.play_arrow,
color: Colors.white,
size: 30.sp,
),
),
),
),
),
),
// Loading indicator when initializing
if (_isInitializing)
Positioned.fill(
child: Container(
color: Colors.black26,
child: Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
),
),
),
),
],
);
}
Widget _buildVideoView() {
if (_chewieController == null) {
return Container(
height: isTablet ? 250.h : 200,
width: double.infinity,
color: Colors.black,
child: Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
),
),
);
}
// Calculate proper height based on aspect ratio
final aspectRatio = _videoPlayerController?.value.aspectRatio ?? 16 / 9;
final videoHeight = MediaQuery.of(context).size.width / aspectRatio;
// Set minimum and maximum heights to prevent extreme sizes
final minHeight = 200.h;
final maxHeight = 300.h;
final finalHeight = videoHeight.clamp(minHeight, maxHeight);
return SizedBox(
height: finalHeight,
width: double.infinity,
child: AspectRatio(
aspectRatio: aspectRatio,
child: Chewie(controller: _chewieController!),
),
);
}
Widget _buildShimmerPlaceholder() {
return Shimmer.fromColors(
baseColor: Colors.grey.shade300,
highlightColor: Colors.grey.shade100,
child: Container(
height: isTablet ? 250.h : 200,
width: double.infinity,
color: Colors.grey.shade300,
),
);
}
Widget _buildErrorPlaceholder() {
return Container(
height: isTablet ? 250.h : 200,
width: double.infinity,
color: Colors.grey.shade200,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.video_library_outlined,
size: 40.sp,
color: Colors.grey.shade500,
),
SizedBox(height: 8.h),
],
),
);
}
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(color: Colors.white),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ClipRRect(
// borderRadius: BorderRadius.vertical(top: Radius.circular(12.r)),
child:
_isVideoInitialized &&
_isVideoPlaying &&
_chewieController != null &&
!_isDisposed
? _buildVideoView()
: _buildThumbnailView(),
),
widget.title != null
? Padding(
padding: EdgeInsets.all(8.0.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.category,
style: TextStyle(fontSize: 14.sp, color: greyTextColor1),
),
SizedBox(height: 4.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
widget.title!,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 15.sp,
color: Colors.black,
fontWeight: FontWeight.w600,
),
),
),
if (widget.isMyVideoScreen)
IconButton(
onPressed: widget.onPressed,
icon: Icon(Icons.more_vert),
),
],
),
],
),
)
: Container(),
],
),
);
}
}
//.........Above one is Chewie Player......................................................................................................
//.........Below one is Better Player......................................................................................................
// import 'dart:developer';
// import 'package:flutter/material.dart';
// import 'package:flutter/services.dart';
// import 'package:flutter_screenutil/flutter_screenutil.dart';
// import 'package:onufitness/screens/u_vault/controllers/uvault_video_controller.dart';
// import 'package:shimmer/shimmer.dart';
// import 'package:better_player_plus/better_player_plus.dart';
// import 'package:get/get.dart';
// import 'package:onufitness/constants/color_constant.dart';
// class VideoCard extends StatefulWidget {
// final String title;
// final String category;
// final String videoUrl;
// final String? videoId;
// final String? thumbnailUrl;
// final bool isMyVideoScreen;
// final VoidCallback? onPressed;
// const VideoCard({
// super.key,
// required this.title,
// required this.category,
// required this.videoUrl,
// this.videoId,
// this.thumbnailUrl,
// required this.isMyVideoScreen,
// this.onPressed,
// });
// @override
// State<VideoCard> createState() => _VideoCardState();
// }
// class _VideoCardState extends State<VideoCard> {
// final controller = Get.find<UvaultController>();
// BetterPlayerController? _betterPlayerController;
// bool _isVideoInitialized = false;
// bool _isVideoPlaying = false;
// bool _isInitializing = false;
// bool _isDisposed = false;
// Future<void> _initializeVideo() async {
// if (_isInitializing || _isVideoInitialized || _isDisposed) return;
// setState(() {
// _isInitializing = true;
// });
// try {
// controller.setCurrentPlayingVideo(widget.videoUrl);
// // Create BetterPlayer configuration
// BetterPlayerConfiguration betterPlayerConfiguration =
// BetterPlayerConfiguration(
// aspectRatio: 16 / 9,
// fit: BoxFit.contain,
// autoPlay: true,
// looping: false,
// fullScreenByDefault: false,
// allowedScreenSleep: false,
// deviceOrientationsAfterFullScreen: [DeviceOrientation.portraitUp],
// controlsConfiguration: BetterPlayerControlsConfiguration(
// showControls: true,
// enableOverflowMenu: false,
// enableFullscreen: true,
// enablePip: false,
// enablePlayPause: true,
// enableMute: true,
// enableProgressBar: true,
// controlBarColor: Colors.black.withValues(alpha: 0.5),
// progressBarPlayedColor: Color(primaryColor),
// progressBarHandleColor: Color(primaryColor),
// progressBarBufferedColor: Colors.grey.shade300,
// progressBarBackgroundColor: Colors.grey,
// iconsColor: Colors.white,
// ),
// errorBuilder: (context, errorMessage) {
// return Center(
// child: Column(
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// Icon(Icons.error, color: Colors.red, size: 40.sp),
// SizedBox(height: 8.h),
// Text(
// 'Video playback error',
// style: TextStyle(color: Colors.red, fontSize: 12.sp),
// ),
// ],
// ),
// );
// },
// );
// // Create data source
// BetterPlayerDataSource dataSource = BetterPlayerDataSource(
// BetterPlayerDataSourceType.network,
// widget.videoUrl,
// videoFormat: BetterPlayerVideoFormat.other,
// );
// // Initialize BetterPlayer
// _betterPlayerController = BetterPlayerController(
// betterPlayerConfiguration,
// );
// await _betterPlayerController!.setupDataSource(dataSource);
// if (_isDisposed || !mounted) {
// // If disposed during initialization, clean up
// _betterPlayerController?.dispose();
// return;
// }
// // Set volume
// await _betterPlayerController!.setVolume(1.0);
// if (mounted && !_isDisposed) {
// setState(() {
// _isVideoInitialized = true;
// _isVideoPlaying = true;
// _isInitializing = false;
// });
// } else {
// // Clean up if component was disposed during initialization
// _disposeVideoController();
// }
// } catch (e) {
// log('Error initializing video: $e');
// if (mounted && !_isDisposed) {
// setState(() {
// _isInitializing = false;
// });
// }
// _disposeVideoController();
// }
// }
// void _disposeVideoController() {
// if (_betterPlayerController != null) {
// _betterPlayerController!.dispose();
// _betterPlayerController = null;
// }
// if (mounted && !_isDisposed) {
// setState(() {
// _isVideoInitialized = false;
// _isVideoPlaying = false;
// _isInitializing = false;
// });
// }
// }
// void _onThumbnailTap() {
// if (!_isInitializing && !_isVideoInitialized) {
// _initializeVideo();
// }
// log(widget.videoUrl);
// }
// @override
// void dispose() {
// _isDisposed = true;
// // Clean up video controller
// _disposeVideoController();
// super.dispose();
// }
// Widget _buildThumbnailView() {
// return Stack(
// children: [
// // Thumbnail Image
// if (widget.thumbnailUrl != null && widget.thumbnailUrl!.isNotEmpty)
// Image.network(
// widget.thumbnailUrl ??
// "https://placehold.co/400?text=Thumbnail&font=roboto",
// height: 180.h,
// width: double.infinity,
// fit: BoxFit.cover,
// loadingBuilder: (context, child, loadingProgress) {
// if (loadingProgress == null) return child;
// return _buildShimmerPlaceholder();
// },
// errorBuilder: (context, error, stackTrace) {
// return _buildErrorPlaceholder();
// },
// )
// else
// _buildErrorPlaceholder(),
// // Play Button Overlay
// Positioned.fill(
// child: Container(
// color: Colors.black26,
// child: Center(
// child: GestureDetector(
// onTap: _onThumbnailTap,
// child: Container(
// width: 60.w,
// height: 60.h,
// decoration: BoxDecoration(
// color: Colors.black54,
// shape: BoxShape.circle,
// ),
// child: Icon(
// Icons.play_arrow,
// color: Colors.white,
// size: 30.sp,
// ),
// ),
// ),
// ),
// ),
// ),
// // Loading indicator when initializing
// if (_isInitializing)
// Positioned.fill(
// child: Container(
// color: Colors.black26,
// child: Center(
// child: CircularProgressIndicator(
// valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
// ),
// ),
// ),
// ),
// ],
// );
// }
// Widget _buildVideoView() {
// if (_betterPlayerController == null) {
// return Container(
// height: 180.h,
// width: double.infinity,
// color: Colors.black,
// child: Center(
// child: CircularProgressIndicator(
// valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
// ),
// ),
// );
// }
// return SizedBox(
// height: 180.h,
// width: double.infinity,
// child: BetterPlayer(controller: _betterPlayerController!),
// );
// }
// Widget _buildShimmerPlaceholder() {
// return Shimmer.fromColors(
// baseColor: Colors.grey.shade300,
// highlightColor: Colors.grey.shade100,
// child: Container(
// height: 180.h,
// width: double.infinity,
// color: Colors.grey.shade300,
// ),
// );
// }
// Widget _buildErrorPlaceholder() {
// return Container(
// height: 180.h,
// width: double.infinity,
// color: Colors.grey.shade200,
// child: Column(
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// Icon(
// Icons.video_library_outlined,
// size: 40.sp,
// color: Colors.grey.shade500,
// ),
// SizedBox(height: 8.h),
// Text(
// 'Video Thumbnail',
// style: TextStyle(color: Colors.grey.shade500, fontSize: 12.sp),
// ),
// ],
// ),
// );
// }
// @override
// Widget build(BuildContext context) {
// log("rebuild video card");
// return Container(
// decoration: BoxDecoration(
// color: Colors.white,
// borderRadius: BorderRadius.circular(12.r),
// boxShadow: [
// BoxShadow(color: Colors.black12, blurRadius: 4, spreadRadius: 2),
// ],
// ),
// child: Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// ClipRRect(
// borderRadius: BorderRadius.vertical(top: Radius.circular(12.r)),
// child:
// _isVideoInitialized &&
// _isVideoPlaying &&
// _betterPlayerController != null &&
// !_isDisposed
// ? _buildVideoView()
// : _buildThumbnailView(),
// ),
// Padding(
// padding: EdgeInsets.all(8.0.w),
// child: Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// Text(
// widget.category,
// style: TextStyle(fontSize: 14.sp, color: greyTextColor1),
// ),
// SizedBox(height: 4.h),
// Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// Expanded(
// child: Text(
// widget.title,
// maxLines: 2,
// overflow: TextOverflow.ellipsis,
// style: TextStyle(
// fontSize: 15.sp,
// color: Colors.black,
// fontWeight: FontWeight.w600,
// ),
// ),
// ),
// if (widget.isMyVideoScreen)
// IconButton(
// onPressed: widget.onPressed,
// icon: Icon(Icons.more_horiz),
// ),
// ],
// ),
// ],
// ),
// ),
// ],
// ),
// );
// }
// }