import 'dart:async'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:onufitness/constants/asset_constants.dart'; import 'package:onufitness/constants/color_constant.dart'; import 'package:onufitness/constants/text_constant.dart'; import 'package:onufitness/routes/route_constant.dart'; import 'package:onufitness/screens/u_vault/controllers/uvault_video_controller.dart'; import 'package:onufitness/screens/u_vault/widgets/more_pressed_bottom_sheet.dart'; import 'package:onufitness/screens/u_vault/widgets/video_card.dart'; import 'package:onufitness/services/local_storage_services/shared_services.dart'; import 'package:onufitness/utils/helper_function.dart'; import 'package:onufitness/widgets/others/button_action_bottom_sheet.dart'; import 'package:onufitness/widgets/appbars/custom_appbar.dart'; class UvaultMyVideosScreen extends StatefulWidget { const UvaultMyVideosScreen({super.key}); @override State createState() => _UvaultMyVideosScreenState(); } class _UvaultMyVideosScreenState extends State { final controller = Get.find(); final ScrollController _scrollController = ScrollController(); final TextEditingController _searchController = TextEditingController(); final FocusNode _searchFocusNode = FocusNode(); @override void initState() { super.initState(); _scrollController.addListener(_scrollListener); } void _scrollListener() { if (!controller.isLastPageMyVideos.value && _scrollController.position.pixels >= _scrollController.position.maxScrollExtent - 200) { controller.fetchVideos( userId: SharedServices.getUserDetails()?.data?.userId?.toString(), isMyVideoScreen: true, searchQuery: _searchController.text.trim(), ); } } void _performSearch({required String query}) { controller.resetPagination(isMyVideoScreen: true); controller.fetchVideos( userId: SharedServices.getUserDetails()!.data!.userId!.toString(), isMyVideoScreen: true, searchQuery: query.trim(), isRefresh: true, ); } Future _handleRefresh() async { controller.resetPagination(isMyVideoScreen: true); await controller.fetchVideos( userId: SharedServices.getUserDetails()!.data!.userId!.toString(), isMyVideoScreen: true, searchQuery: _searchController.text.trim(), isRefresh: true, ); } @override void dispose() { _scrollController.removeListener(_scrollListener); _scrollController.dispose(); _searchController.dispose(); _searchFocusNode.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, appBar: CustomAppBar( leading: IconButton( onPressed: () => Navigator.pop(context), icon: const Icon(Icons.arrow_back_ios), ), actions: [ TextButton( onPressed: () { Get.toNamed(RouteConstant.uploadUvaultVideos); }, child: Image.asset( AssetConstants.uploadTextWithIcon, height: isTablet ? 35.h : 25.h, ), ), ], backgroundColor: Colors.white, title: "My Videos", textColor: appbarTextColor, titleFontSize: appBarHeardingText, ), body: RefreshIndicator( onRefresh: _handleRefresh, color: Colors.black, child: Column( children: [ // Search Bar Padding( padding: EdgeInsets.symmetric(horizontal: 15.w, vertical: 10.h), child: TextField( controller: _searchController, focusNode: _searchFocusNode, onChanged: (value) { controller.searchQueryText.value = value; // Debounce search - wait 500ms after user stops typing if (controller.searchDebounceTimer?.isActive ?? false) { controller.searchDebounceTimer?.cancel(); } controller.searchDebounceTimer = Timer( Duration(milliseconds: 500), () => _performSearch(query: value), ); }, decoration: InputDecoration( hintText: 'Search my videos...', suffixIcon: Obx( () => controller.searchQueryText.value.isNotEmpty ? IconButton( icon: Icon(Icons.clear, color: Colors.grey), onPressed: () { controller.searchQueryText.value = ""; _searchController.clear(); _searchFocusNode.unfocus(); _performSearch(query: ''); }, ) : SizedBox.shrink(), ), hintStyle: TextStyle( fontWeight: FontWeight.w600, color: greyBorderColor, ), prefixIcon: const Padding( padding: EdgeInsets.only(left: 20), child: Icon(Icons.search), ), border: OutlineInputBorder( borderRadius: BorderRadius.circular(30.r), borderSide: BorderSide.none, ), filled: true, fillColor: textFieldFillColor, ), ), ), // Video List Expanded( child: Obx(() { if (controller.myVideos.isEmpty && !controller.isLoading.value && controller.isLastPageMyVideos.value) { // Wrap empty state in CustomScrollView for pull-to-refresh return CustomScrollView( physics: const AlwaysScrollableScrollPhysics(), slivers: [ SliverFillRemaining( child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.video_library_outlined, size: 60.sp, color: Colors.grey, ), SizedBox(height: 10.h), Text( _searchController.text.isEmpty ? "No Videos available" : "No videos found", style: TextStyle( fontSize: 16.sp, color: Colors.grey, ), ), if (_searchController.text.isNotEmpty) ...[ SizedBox(height: 5.h), Text( 'Try a different search term', style: TextStyle( fontSize: 14.sp, color: Colors.grey[600], ), ), ], ], ), ), ), ], ); } if (controller.myVideos.isEmpty && controller.isLoading.value) { // Wrap loading state in CustomScrollView for pull-to-refresh return CustomScrollView( physics: const AlwaysScrollableScrollPhysics(), slivers: [ SliverFillRemaining( child: const Center( child: CircularProgressIndicator(color: Colors.black), ), ), ], ); } return ListView.builder( physics: const AlwaysScrollableScrollPhysics(), controller: _scrollController, itemCount: controller.myVideos.length + (controller.isLastPageMyVideos.value ? 0 : 1), itemBuilder: (context, index) { if (index < controller.myVideos.length) { final video = controller.myVideos[index]; return Padding( padding: EdgeInsets.all(15.0.w), child: VideoCard( title: video.title ?? '', category: video.fitnessGoalName?.toString() ?? '', videoUrl: video.uVaultFile ?? '', videoId: video.uVaultId.toString(), onPressed: () { VideoMoreOptionsBottomSheet.show( context: context, onEdit: () { video.title; controller.selectedFitnessGoalId.value = video.fitnessGoalId!; controller.videoTitleController.text = video.title!; controller.selectedFitnessGoal.value = video.fitnessGoalName!; controller.updateUvaultId.value = video.uVaultId.toString(); Get.toNamed(RouteConstant.updateUvaultVideos); }, onDelete: () { actionButtonBottomSheet( context: context, title: "Delete Video?", subtitle: "Are you sure you want to delete this video? This action cannot be undone.", submitButtonText: "Delete", onPressed: () async { await controller.deleteUVault( uVaultId: video.uVaultId.toString(), ); Get.back(); }, cancelButtonText: "Cancel", onCancelPressed: () { Get.back(); }, assetPath: AssetConstants.delete, primaryColor: Color(primaryColor), cancelBorderColor: greyTextColor1, isLoading: controller.isDeleteLoading, ); }, ); }, isMyVideoScreen: true, thumbnailUrl: video.thumbnailFileName, ), ); } else { return const Padding( padding: EdgeInsets.all(16.0), child: Center( child: CircularProgressIndicator(color: Colors.black), ), ); } }, ); }), ), ], ), ), ); } }