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

506 lines
16 KiB
Dart

import 'dart:async';
import 'package:file_picker/file_picker.dart';
import 'package:onufitness/services/logger_service.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:get_thumbnail_video/video_thumbnail.dart';
import 'package:intl/intl.dart';
import 'package:onufitness/constants/api_endpoints.dart';
import 'package:onufitness/models/master_dropdowns/fitness_goals_dropdown_response_model.dart';
import 'package:onufitness/screens/u_vault/models/failed_videos_draft_model.dart';
import 'package:onufitness/screens/u_vault/models/uvault_videos_response_model.dart';
import 'package:onufitness/services/api_services/base_api_services.dart';
import 'package:onufitness/services/local_storage_services/shared_services.dart';
import 'package:onufitness/utils/custom_sneakbar.dart';
class UvaultController extends GetxController {
final logger = LoggerService();
@override
void onInit() {
super.onInit();
callOnInitMethods();
}
callOnInitMethods() async {
loadDraftVideos();
await fetchFitnessGoals();
await fetchVideos(isMyVideoScreen: false);
}
// Search functionality
Timer? searchDebounceTimer;
//...........................................................................................................
final RxnString currentPlayingVideoUrl = RxnString();
// Video state management
final RxMap<String, bool> videoInitializingStates = <String, bool>{}.obs;
final RxMap<String, bool> videoInitializedStates = <String, bool>{}.obs;
final RxMap<String, bool> videoPlayingStates = <String, bool>{}.obs;
void setCurrentPlayingVideo(String videoUrl) {
// Clear states for all other videos
for (String url in videoInitializedStates.keys.toList()) {
if (url != videoUrl) {
videoInitializingStates[url] = false;
videoInitializedStates[url] = false;
videoPlayingStates[url] = false;
}
}
currentPlayingVideoUrl.value = videoUrl;
videoInitializingStates[videoUrl] = true;
}
//......................................................................................................
//........... Upload videos......................................................................
Rx<PlatformFile?> selectedFile = Rx<PlatformFile?>(null);
var selectedCategory = ''.obs;
var files = <PlatformFile>[].obs;
TextEditingController videoTitleController = TextEditingController();
void setCategory(String? value) {
if (value != null) selectedCategory.value = value;
}
RxDouble uploadProgress = 0.0.obs;
RxBool isUploading = false.obs;
RxString uploadStatus = "".obs;
//............................................................................................................
//.......... SetUp IsLoading for Draft Videos Retry Button......................................................
var uploadingIndexes = <int>{}.obs;
int draftVideoIndex = 0;
void setUploading(bool value) {
if (value) {
uploadingIndexes.add(draftVideoIndex);
} else {
uploadingIndexes.remove(draftVideoIndex);
}
}
// Upload API call.................................................................................................
Future<bool> uploadFileToAPI() async {
var ret = false;
final filePath = selectedFile.value?.path;
if (filePath == null) {
customSnackbar(title: "Error", message: "No file selected");
return false;
}
isUploading(true);
setUploading(true);
uploadStatus("Uploading...");
uploadProgress(0.0);
Timer? timer;
timer = Timer.periodic(Duration(milliseconds: 100), (t) {
if (uploadProgress.value < 0.9) {
uploadProgress.value += 0.05;
} else {
t.cancel();
}
});
try {
final xfile = XFile(filePath);
var response = await ApiBase.postAnyFileWithMimeType(
extendedURL: ApiUrl.uploadUvaultVideos,
file: xfile,
fileNameKey: 'UVaultFile',
body: {
"UserID": SharedServices.getUserDetails()?.data?.userId.toString(),
"FitnessGoalId": selectedFitnessGoalId.value,
"Title": videoTitleController.text,
},
);
timer.cancel();
if (response.statusCode == 200 || response.statusCode == 201) {
uploadProgress(1.0);
uploadStatus("Completed");
customSnackbar(title: "Success", message: "File uploaded successfully");
ret = true;
// selectedFitnessGoal.value = "";
// selectedFitnessGoalId.value = 0;
// videoTitleController.clear();
} else {
uploadProgress(0.0);
uploadStatus("Failed");
customSnackbar(title: "Error", message: "Failed to upload file");
final failedUpload = FailedUploadModel(
userId:
SharedServices.getUserDetails()?.data?.userId.toString() ?? '',
fitnessGoalId: selectedFitnessGoalId.value,
title: videoTitleController.text,
videoFilePath: selectedFile.value?.path ?? '',
failedDate: DateFormat('dd-MM-yyyy').format(DateTime.now()),
);
await SharedServices.addFailedUpload(failedUpload);
selectedFile.value = null;
ret = false;
}
} catch (e) {
uploadProgress(0.0);
uploadStatus("Failed");
customSnackbar(title: "Error", message: "Failed to upload file");
final failedUpload = FailedUploadModel(
userId: SharedServices.getUserDetails()?.data?.userId.toString() ?? '',
fitnessGoalId: selectedFitnessGoalId.value,
title: videoTitleController.text,
videoFilePath: selectedFile.value?.path ?? '',
failedDate: DateFormat('dd-MM-yyyy').format(DateTime.now()),
);
await SharedServices.addFailedUpload(failedUpload);
selectedFile.value = null;
ret = false;
} finally {
setUploading(false);
isUploading(false);
}
return ret;
}
void cancelUpload() {
selectedFile.value = null;
uploadProgress(0);
isUploading(false);
}
//............. Load Draft Videos From Shared Preferences to This GetX Controller...........................
RxList<FailedUploadModel> draftVideos = <FailedUploadModel>[].obs;
void loadDraftVideos() {
draftVideos.value = SharedServices.getFailedUploads();
}
void deleteDraftVideo(int index) {
SharedServices.removeFailedUploadAt(index);
loadDraftVideos();
}
//............... See More Feature in the Draft Video list...........................................
RxList<bool> seeMoreList = RxList<bool>();
void initializeSeeMoreList(int itemCount) {
seeMoreList.value = List.filled(itemCount, false);
}
void toggleSeeMore(int index) {
seeMoreList[index] = !seeMoreList[index];
update();
}
//.....................................................................................................................
//............... Fetch Fitness Goals List................................................................
var isFitnessGoalsLoading = false.obs;
var apiFitnessGoalsList = <SingleFitnessGoal>[].obs;
var selectedFitnessGoal = "".obs;
RxList<String> localFitnessGoalsList = <String>[].obs;
RxInt selectedFitnessGoalId = 0.obs;
Future<void> fetchFitnessGoals() async {
isFitnessGoalsLoading(true);
try {
var response = await ApiBase.getRequest(
extendedURL: ApiUrl.fetchFitnessGoals,
sendHeaders: false,
);
if (response.statusCode == 200 || response.statusCode == 201) {
var responseData = fitnessGoalsResponseModelFromJson(response.body);
if (responseData.isSuccess == true) {
apiFitnessGoalsList.assignAll(responseData.data ?? []);
localFitnessGoalsList.clear();
for (var goal in responseData.data!) {
localFitnessGoalsList.add(goal.fitnessGoalTitle.toString());
}
} else {
var responseData = fitnessGoalsResponseModelFromJson(response.body);
customSnackbar(
title: "Error",
message: responseData.message ?? "Failed to load Fitness Goals",
);
}
} else {
var responseData = fitnessGoalsResponseModelFromJson(response.body);
customSnackbar(
title: "Error",
message: responseData.message ?? "Failed to load Fitness Goals",
);
}
} catch (e) {
logger.error("Exception in fetchFitnessGoals: $e");
customSnackbar(title: "Error", message: "Failed to load Fitness Goals");
} finally {
isFitnessGoalsLoading(false);
}
}
//... View Videos......................................................................................................
//fetchVideos API Call................................................................................................
RxList<Item> videos = <Item>[].obs;
RxList<Item> myVideos = <Item>[].obs;
RxBool isLoading = false.obs;
final int pageSize = 5;
//.....
int currentPageUvault = 1;
RxBool isLastPageUvault = false.obs;
int currentPageMyVideos = 1;
RxBool isLastPageMyVideos = false.obs;
//.....
void resetPagination({required bool isMyVideoScreen, int? newFitnessGoalId}) {
if (isMyVideoScreen) {
currentPageMyVideos = 1;
isLastPageMyVideos.value = false;
myVideos.clear();
} else {
currentPageUvault = 1;
isLastPageUvault.value = false;
videos.clear();
}
if (newFitnessGoalId != null) {
selectedFitnessGoalId.value = newFitnessGoalId;
}
}
RxString searchQueryText = "".obs;
Future<void> fetchVideos({
bool isRefresh = false,
String? userId,
String? fitnessGoalId,
required bool isMyVideoScreen,
String? searchQuery,
}) async {
if (isLoading.value) {
return;
}
if (isMyVideoScreen) {
if (isRefresh) {
currentPageMyVideos = 1;
isLastPageMyVideos.value = false;
myVideos.clear();
}
}
if (!isMyVideoScreen) {
if (isRefresh) {
currentPageUvault = 1;
isLastPageUvault.value = false;
videos.clear();
}
}
isLoading.value = true;
try {
// Always required
Map<String, String> queryParams = {
'PageNumber':
isMyVideoScreen
? currentPageMyVideos.toString()
: currentPageUvault.toString(),
'PageSize': pageSize.toString(),
};
// Add optional params only if they are not null or empty
if (userId?.isNotEmpty == true) queryParams['UserId'] = userId!;
if (fitnessGoalId?.isNotEmpty == true) {
queryParams['FitnessGoalId'] = fitnessGoalId!;
}
if (searchQuery?.isNotEmpty == true) {
queryParams['Title'] = searchQuery!;
}
// Build query string
final queryString = queryParams.entries
.map((e) => "${e.key}=${e.value}")
.join("&");
final response = await ApiBase.getRequest(
extendedURL: "${ApiUrl.fetchUvaultVideos}?$queryString",
);
if (response.body.trim().isEmpty) {
isMyVideoScreen
? isLastPageMyVideos.value = true
: isLastPageUvault.value = true;
return;
}
final parsed = uvaultVideosResponseModelFromJson(response.body);
if (parsed.isSuccess == true) {
final newVideos = parsed.data?.items ?? [];
if (isMyVideoScreen) {
if (currentPageMyVideos == 1 && newVideos.isEmpty) {
isLastPageMyVideos.value = true;
}
}
if (!isMyVideoScreen) {
if (currentPageUvault == 1 && newVideos.isEmpty) {
isLastPageUvault.value = true;
}
}
if (newVideos.isNotEmpty) {
if (userId == null) {
videos.addAll(newVideos);
} else {
myVideos.addAll(newVideos);
}
}
if (newVideos.length < pageSize) {
isMyVideoScreen
? isLastPageMyVideos.value = true
: isLastPageUvault.value = true;
} else {
isMyVideoScreen ? currentPageMyVideos++ : currentPageUvault++;
}
} else {
isMyVideoScreen
? isLastPageMyVideos.value = true
: isLastPageUvault.value = true;
}
} catch (e) {
logger.error("Exception during video fetch: $e");
isMyVideoScreen
? isLastPageMyVideos.value = true
: isLastPageUvault.value = true;
} finally {
isLoading.value = false;
}
}
//............... Delete U-Vault............................................................................
RxBool isDeleteLoading = false.obs;
Future<void> deleteUVault({required String uVaultId}) async {
isDeleteLoading(true);
try {
final response = await ApiBase.deleteRequest(
extendedURL: "${ApiUrl.deleteUvault}/$uVaultId",
body: {},
);
if (response.statusCode == 200 || response.statusCode == 201) {
customSnackbar(
title: "Success",
message: "UVault deleted successfully.",
);
resetPagination(isMyVideoScreen: true);
await fetchVideos(
userId: SharedServices.getUserDetails()!.data!.userId!.toString(),
isMyVideoScreen: true,
isRefresh: true,
);
} else {
customSnackbar(
title: "Error",
message: "Failed to delete UVault. Please try again.",
);
}
} catch (e) {
logger.error("Exception while deleting UVault: $e");
customSnackbar(title: "Error", message: "Something went wrong.");
}
isDeleteLoading(false);
}
//............... Edit U-Vault............................................................................
RxBool isUpdating = false.obs;
RxString updateUvaultId = "".obs;
Future<bool> updateUVault() async {
var ret = false;
isUpdating(true);
// Add these lines to reset and show upload progress
uploadStatus("Uploading...");
uploadProgress(0.0);
Timer? timer;
timer = Timer.periodic(Duration(milliseconds: 100), (t) {
if (uploadProgress.value < 0.9) {
uploadProgress.value += 0.05;
} else {
t.cancel();
}
});
//.........................................................
try {
final Map<String, dynamic> body = {
"UserID": SharedServices.getUserDetails()?.data?.userId.toString(),
};
if (selectedFitnessGoalId.value != 0) {
body["FitnessGoalId"] = selectedFitnessGoalId.value;
}
if (videoTitleController.text.isNotEmpty) {
body["Title"] = videoTitleController.text;
}
final filePath = selectedFile.value?.path;
XFile? xfile;
if (filePath != null) {
xfile = XFile(filePath);
}
final response = await ApiBase.patchAnyFileWithMimeType(
extendedURL: "${ApiUrl.updateUvault}/$updateUvaultId",
file: xfile,
fileNameKey: 'UVaultFile',
body: body,
);
timer.cancel();
if (response.statusCode == 200 || response.statusCode == 201) {
uploadProgress(1.0);
uploadStatus("Completed");
Future.delayed(Duration(milliseconds: 300), () {
customSnackbar(
title: "Success",
message: "Video updated successfully",
duration: 2,
);
});
selectedFitnessGoalId.value = 0;
selectedFitnessGoal.value = "";
updateUvaultId.value = "";
videoTitleController.clear();
selectedFile.value = null;
ret = true;
} else {
uploadProgress(0.0);
uploadStatus("Failed");
customSnackbar(title: "Error", message: "Failed to update video");
}
} catch (e) {
logger.error("Exception updateUVault: $e");
uploadProgress(0.0);
uploadStatus("Failed");
customSnackbar(title: "Error", message: "Something went wrong");
} finally {
isUpdating(false);
}
return ret;
}
@override
void dispose() {
searchDebounceTimer?.cancel();
super.dispose();
}
}