506 lines
16 KiB
Dart
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();
|
|
}
|
|
}
|