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

321 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/api_enum_constant.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/filter_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/appbars/custom_appbar.dart';
import 'package:onufitness/widgets/Buttons/custom_submit_button.dart';
class UvaultViewScreen extends StatefulWidget {
const UvaultViewScreen({super.key});
@override
State<UvaultViewScreen> createState() => _UvaultViewScreenState();
}
class _UvaultViewScreenState extends State<UvaultViewScreen> {
final UvaultController controller = Get.put(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.isLastPageUvault.value &&
_scrollController.position.pixels >=
_scrollController.position.maxScrollExtent - 200) {
controller.fetchVideos(
isMyVideoScreen: false,
searchQuery: _searchController.text.trim(),
);
}
}
void _performSearch({required String query}) {
controller.resetPagination(isMyVideoScreen: false);
controller.fetchVideos(
isMyVideoScreen: false,
searchQuery: query.trim(),
isRefresh: true,
);
}
Future<void> _handleRefresh() async {
controller.resetPagination(isMyVideoScreen: false);
await controller.fetchVideos(
isMyVideoScreen: false,
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(
backgroundColor: Colors.white,
leading: IconButton(
onPressed: () => Navigator.pop(context),
icon: const Icon(Icons.arrow_back_ios),
),
actions: [
if (SharedServices.getLoginDetails()?.data?.userRole ==
ApiEnum.coachUserRole)
TextButton(
onPressed: () {
Get.toNamed(RouteConstant.uploadUvaultVideos);
},
child: Image.asset(
AssetConstants.uploadTextWithIcon,
height: isTablet ? 35.h : 25.h,
),
),
IconButton(
icon: Image.asset(
AssetConstants.filterMenuIcon,
height: isTablet ? 35.h : 25.h,
),
onPressed: () {
showFitnessGoalsFilterBottomSheet(
context: context,
isMyVideoScreen: false,
);
},
),
],
title: "U-Vault",
textColor: appbarTextColor,
titleFontSize: appBarHeardingText,
),
body: RefreshIndicator(
onRefresh: _handleRefresh,
color: Colors.black,
child: Column(
children: [
// Coach buttons
SharedServices.getLoginDetails()?.data?.userRole ==
ApiEnum.coachUserRole
? Padding(
padding: EdgeInsets.symmetric(horizontal: 15.w),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
CustomSubmitButton(
width: 0.4.sw,
height: 45.h,
backgroundColor: Colors.white,
textColor: Colors.black,
textStyle: TextStyle(
fontSize: 16.sp,
color: Colors.black,
),
borderSide: BorderSide(color: greyBorderColor),
text: "My Videos",
onPressed: () async {
controller.resetPagination(isMyVideoScreen: true);
Get.toNamed(RouteConstant.myUploadedUvaultVideos);
await controller.fetchVideos(
userId:
SharedServices.getUserDetails()!.data!.userId!
.toString(),
isMyVideoScreen: true,
);
},
),
CustomSubmitButton(
width: 0.4.sw,
height: 45.h,
fontSize: 16.sp,
backgroundColor: Colors.white,
textStyle: TextStyle(
fontSize: 16.sp,
color: Colors.black,
),
borderSide: BorderSide(color: greyBorderColor),
text: "Draft Videos",
onPressed: () {
Get.toNamed(RouteConstant.draftUvaultVideos);
},
),
],
),
)
: Container(),
SizedBox(height: 10.h),
// 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 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,
),
),
),
SizedBox(height: 10.h),
// Video List
Expanded(
child: Obx(() {
if (controller.videos.isEmpty &&
!controller.isLoading.value &&
controller.isLastPageUvault.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.videos.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(
controller: _scrollController,
physics: const AlwaysScrollableScrollPhysics(),
itemCount:
controller.videos.length +
(controller.isLastPageUvault.value ? 0 : 1),
itemBuilder: (context, index) {
if (index < controller.videos.length) {
final video = controller.videos[index];
return Padding(
padding: EdgeInsets.all(15.0.w),
child: VideoCard(
title: video.title ?? 'Untitled',
category:
video.fitnessGoalName?.toString() ?? 'Unknown',
videoUrl: video.uVaultFile ?? '',
videoId: video.uVaultId.toString(),
isMyVideoScreen: false,
thumbnailUrl: video.thumbnailFileName,
),
);
} else {
return const Padding(
padding: EdgeInsets.all(16.0),
child: Center(
child: CircularProgressIndicator(color: Colors.black),
),
);
}
},
);
}),
),
],
),
),
);
}
}