onufitness_mobile/lib/screens/u_vault/views/my_videos_screen.dart
2026-01-13 11:36:24 +05:30

297 lines
12 KiB
Dart

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<UvaultMyVideosScreen> createState() => _UvaultMyVideosScreenState();
}
class _UvaultMyVideosScreenState extends State<UvaultMyVideosScreen> {
final controller = Get.find<UvaultController>();
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<void> _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),
),
);
}
},
);
}),
),
],
),
),
);
}
}