2026-01-13 11:36:24 +05:30

521 lines
17 KiB
Dart

import 'dart:convert';
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:onufitness/constants/api_endpoints.dart';
import 'package:onufitness/models/master_dropdowns/fitness_goals_dropdown_response_model.dart';
import 'package:onufitness/screens/goals/models/get_all_goals_response_model.dart';
import 'package:onufitness/services/api_services/base_api_services.dart';
import 'package:onufitness/utils/custom_sneakbar.dart';
class GoalController extends GetxController {
@override
void onInit() {
super.onInit();
fetchAllExploreGoals();
fetchFitnessGoalTypes();
fetchAllJoinedGoals();
selectedDate.value = DateTime.now();
showSelectedDateToUI.value = formatDate(selectedDate.value!);
valueController = TextEditingController();
}
@override
void onClose() {
valueController.dispose();
super.onClose();
}
//.............................. Tab bar....................................................................
var selectedTabIndex = 0.obs;
void switchTab(int index) {
selectedTabIndex.value = index;
// Fetch joined goals when switching to joined tab if not already loaded
if (index == 1 && joinedGoals.isEmpty) {
fetchAllJoinedGoals();
}
}
//.......................................................................................................................
//......Fetch Explore API Call..........................................................................................
//.......................................................................................................................
GetAllGoalsResponseModel exploreGoalModel = GetAllGoalsResponseModel();
var isLoading = false.obs;
var isLoadingMore = false.obs;
var exploreGoals = <SingleGoalData>[].obs;
var filteredGoals = <SingleGoalData>[].obs;
var totalCount = 0.obs;
var currentPage = 1.obs;
var pageSize = 10.obs;
var hasError = false.obs;
var errorMessage = ''.obs;
var hasMoreData = true.obs;
Future<void> fetchAllExploreGoals({
int page = 1,
bool isRefresh = false,
}) async {
try {
if (page == 1) {
isLoading.value = true;
hasError.value = false;
if (isRefresh) {
exploreGoals.clear();
filteredGoals.clear();
}
} else {
isLoadingMore.value = true;
}
currentPage.value = page;
var url =
"${ApiUrl.getAllExploreGoals}?PageNumber=$page&PageSize=${pageSize.value}";
if (selectedFitnessGoalId.value != 0) {
url +=
"&SearchType=FitnessGoal&SearchValue=${selectedFitnessGoalId.value}";
}
final response = await ApiBase.getRequest(
extendedURL: url,
sendHeaders: true,
);
log('Response status: ${response.statusCode}');
log('Response body: ${response.body}');
if (response.statusCode == 200) {
exploreGoalModel = getAllGoalsResponseModelFromJson(response.body);
if (exploreGoalModel.isSuccess == true) {
totalCount.value = exploreGoalModel.data!.totalCount ?? 0;
List<SingleGoalData> newGoals = exploreGoalModel.data!.items ?? [];
if (page == 1) {
exploreGoals.assignAll(newGoals);
filteredGoals.assignAll(newGoals);
} else {
exploreGoals.addAll(newGoals);
filteredGoals.addAll(newGoals);
}
hasMoreData.value = newGoals.length == pageSize.value;
hasError.value = false;
errorMessage.value = '';
} else {
hasError.value = true;
errorMessage.value =
exploreGoalModel.message ?? 'Failed to fetch goals';
}
} else {
hasError.value = true;
errorMessage.value = 'Server error: ${response.statusCode}';
}
} catch (e) {
log('Network error: $e');
hasError.value = true;
errorMessage.value = 'Network error: ${e.toString()}';
} finally {
isLoading.value = false;
isLoadingMore.value = false;
}
}
// Apply filter and reset pagination
Future<void> applyFilter() async {
currentPage.value = 1;
hasMoreData.value = true;
exploreGoals.clear();
filteredGoals.clear();
await fetchAllExploreGoals(page: 1);
}
Future<void> refreshFetchAllGoals() async {
currentPage.value = 1;
hasMoreData.value = true;
exploreGoals.clear();
filteredGoals.clear();
await fetchAllExploreGoals(page: 1, isRefresh: true);
}
// Load more goals (pagination)
Future<void> loadMore() async {
if (!isLoading.value && !isLoadingMore.value && hasMoreData.value) {
await fetchAllExploreGoals(page: currentPage.value + 1);
}
}
//.......................................................................................................................
// API call for joined goals.............................................................................................
//.......................................................................................................................
var currentJoinedPage = 1.obs;
var hasMoreJoinedData = true.obs;
var joinedGoals = <SingleGoalData>[].obs;
var joinedGoalsCount = 0.obs;
var joinedGoalsErrorMessage = ''.obs;
var isLoadingMoreJoinedGoals = false.obs;
var hasJoinedGoalsError = false.obs;
GetAllGoalsResponseModel joinedGoalModel = GetAllGoalsResponseModel();
RxBool isJoinedGoalsLoading = false.obs;
Future<void> fetchAllJoinedGoals({
int page = 1,
bool isRefresh = false,
}) async {
try {
if (page == 1) {
isJoinedGoalsLoading.value = true;
hasJoinedGoalsError.value = false;
if (isRefresh) {
joinedGoals.clear();
}
} else {
isLoadingMoreJoinedGoals.value = true;
}
currentJoinedPage.value = page;
var url =
"${ApiUrl.getAllJoinedGoals}?PageNumber=$page&PageSize=${pageSize.value}";
if (selectedJoinedFitnessGoalId.value != 0) {
url +=
"&SearchType=FitnessGoal&SearchValue=${selectedJoinedFitnessGoalId.value}";
}
final response = await ApiBase.getRequest(
extendedURL: url,
sendHeaders: true,
);
if (response.statusCode == 200) {
joinedGoalModel = getAllGoalsResponseModelFromJson(response.body);
if (joinedGoalModel.isSuccess == true) {
joinedGoalsCount.value = joinedGoalModel.data!.totalCount ?? 0;
List<SingleGoalData> newJoinedGoals =
joinedGoalModel.data!.items ?? [];
if (page == 1) {
joinedGoals.assignAll(newJoinedGoals);
} else {
joinedGoals.addAll(newJoinedGoals);
}
hasMoreJoinedData.value = newJoinedGoals.length == pageSize.value;
hasJoinedGoalsError.value = false;
joinedGoalsErrorMessage.value = '';
} else {
hasJoinedGoalsError.value = true;
joinedGoalsErrorMessage.value =
joinedGoalModel.message ?? 'Failed to fetch joined goals';
}
} else {
hasJoinedGoalsError.value = true;
joinedGoalsErrorMessage.value = 'Server error: ${response.statusCode}';
}
} catch (e) {
log('Network error fetching joined goals: $e');
hasJoinedGoalsError.value = true;
joinedGoalsErrorMessage.value = 'Network error: ${e.toString()}';
} finally {
isJoinedGoalsLoading.value = false;
isLoadingMoreJoinedGoals.value = false;
}
}
// Apply filter for joined goals and reset pagination
Future<void> applyJoinedGoalsFilter() async {
currentJoinedPage.value = 1;
hasMoreJoinedData.value = true;
joinedGoals.clear();
await fetchAllJoinedGoals(page: 1);
}
Future<void> refreshFetchJoinedGoals() async {
currentJoinedPage.value = 1;
hasMoreJoinedData.value = true;
joinedGoals.clear();
await fetchAllJoinedGoals(page: 1, isRefresh: true);
}
// Load more joined goals (pagination)
Future<void> loadMoreJoinedGoals() async {
if (!isJoinedGoalsLoading.value &&
!isLoadingMoreJoinedGoals.value &&
hasMoreJoinedData.value) {
await fetchAllJoinedGoals(page: currentJoinedPage.value + 1);
}
}
//.......................................................................................................................
// Join to a Goal.........................................................................................................
//.......................................................................................................................
RxInt selectedGoalIDforJoinGoal = 0.obs;
RxBool isJoinGoalLoading = false.obs;
Future<bool> joinGoal() async {
bool ret = false;
isJoinGoalLoading(true);
try {
final response = await ApiBase.postRequest(
extendedURL: ApiUrl.joinToTheGoal,
body: {"goalID": selectedGoalIDforJoinGoal.value},
);
log('Join Goal Response: ${response.statusCode} | ${response.body}');
if (response.statusCode == 200 || response.statusCode == 201) {
ret = true;
// Refresh joined goals after successful join
await refreshFetchJoinedGoals();
} else {
String errorMsg = 'Failed to participate in goal';
try {
final body = jsonDecode(response.body);
if (body['message'] != null) {
errorMsg = body['message'];
}
} catch (_) {
errorMsg = 'Failed to participate in goal';
}
customSnackbar(title: "Failed!", message: errorMsg);
}
} catch (e) {
log("Join goal error: $e");
customSnackbar(
title: "Error",
message: "Something went wrong. Please try again later.",
);
} finally {
isJoinGoalLoading(false);
}
return ret;
}
//.......................................................................................................................
//............... Fetch Fitness Goals List For dropdown................................................................
//.......................................................................................................................
var isFitnessGoalsLoading = false.obs;
var apiFitnessGoalsList = <SingleFitnessGoal>[].obs;
RxList<String> localFitnessGoalsList = <String>[].obs;
var selectedFitnessGoal = "".obs;
RxInt selectedFitnessGoalId = 0.obs;
var selectedJoinedFitnessGoal = "".obs;
RxInt selectedJoinedFitnessGoalId = 0.obs;
//...
Future<void> fetchFitnessGoalTypes() 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();
localFitnessGoalsList.add("All");
for (var goal in responseData.data!) {
localFitnessGoalsList.add(goal.fitnessGoalTitle.toString());
}
// Set default selection for explore goals
selectedFitnessGoal.value = "All";
selectedFitnessGoalId.value = 0;
// Set default selection for joined goals
selectedJoinedFitnessGoal.value = "All";
selectedJoinedFitnessGoalId.value = 0;
} 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) {
customSnackbar(title: "Error", message: "Failed to load Fitness Goals");
} finally {
isFitnessGoalsLoading(false);
}
}
//.......................................................................................................................
// Submit goal Input......................................................................................................
//.......................................................................................................................
TextEditingController valueController = TextEditingController();
RxString showSelectedDateToUI = "".obs;
final Rx<Task?> selectedTask = Rx<Task?>(null);
final Rx<DateTime?> selectedDate = Rx<DateTime?>(null);
RxInt selectedTaskId = 0.obs;
RxInt selectedGoalIDforTaskInput = 0.obs;
Future<void> selectDate(BuildContext context) async {
final DateTime? pickedDate = await showDatePicker(
context: context,
initialDate: selectedDate.value ?? DateTime.now(),
firstDate: DateTime(2000),
lastDate: DateTime.now(),
);
if (!context.mounted) return;
if (pickedDate != null) {
final now = DateTime.now();
final DateTime selectedDateTime = DateTime(
pickedDate.year,
pickedDate.month,
pickedDate.day,
now.hour,
now.minute,
);
if (selectedDateTime != selectedDate.value) {
updateSelectedDate(selectedDateTime);
}
}
}
String formatDate(DateTime date) {
return "${date.month.toString().padLeft(2, '0')}/${date.day.toString().padLeft(2, '0')}/${date.year}";
}
void updateSelectedDate(DateTime date) {
selectedDate.value = date;
showSelectedDateToUI.value = formatDate(date);
log("selected date : $selectedDate");
}
void updateSelectedTask(Task task) {
selectedTask.value = task;
selectedTaskId.value = task.goalTaskId!;
log("Selecetd Task :${selectedTaskId.value}");
}
void clearForm() {
selectedTask.value = null;
selectedTaskId.value = 0;
selectedDate.value = DateTime.now();
showSelectedDateToUI.value = formatDate(selectedDate.value!);
valueController.clear();
}
//............................
RxBool isInputLoading = false.obs;
Future<bool> inputGoalTask() async {
isInputLoading(true);
bool ret = false;
try {
final metricValue = double.tryParse(valueController.text.trim());
final trackingDate = selectedDate.value?.toUtc().toIso8601String();
log("Date : $trackingDate");
final requestBody = {
'goalID': selectedGoalIDforTaskInput.value,
'goalTaskID': selectedTaskId.value,
'metricValue': metricValue!,
'trackingDate': trackingDate,
};
log(requestBody.toString());
final response = await ApiBase.postRequest(
extendedURL: ApiUrl.inputGoalTask,
body: requestBody,
sendHeaders: true,
);
log(response.body.toString());
if (response.statusCode == 200 || response.statusCode == 201) {
ret = true;
final Map<String, dynamic> responseBody = jsonDecode(response.body);
if (responseBody['isSuccess'] == true && responseBody['data'] != null) {
final updatedGoal = SingleGoalData.fromJson(responseBody['data']);
// 🔁 Call clean update method
updateGoalFromResponse(updatedGoal);
final message =
responseBody['message'] ?? 'Performance submitted successfully!';
customSnackbar(title: "Success", message: message, duration: 1);
clearForm();
} else {
ret = false;
customSnackbar(
title: "Failed",
message: responseBody['message'] ?? 'Submission failed',
duration: 2,
);
}
} else {
ret = false;
final Map<String, dynamic> responseBody = jsonDecode(response.body);
customSnackbar(
title: "Failed",
message: responseBody['message'] ?? 'Submission failed',
duration: 2,
);
}
} catch (e) {
log('Input goal task error: $e');
customSnackbar(
title: "Error",
message: "Something went wrong. Please try again.",
duration: 2,
);
ret = false;
} finally {
isInputLoading(false);
}
return ret;
}
void updateGoalFromResponse(SingleGoalData updatedGoal) {
final goalIndex = joinedGoals.indexWhere(
(goal) => goal.goalId == updatedGoal.goalId,
);
if (goalIndex != -1) {
// Update goal progress
joinedGoals[goalIndex].goalProgressPercentage =
updatedGoal.goalProgressPercentage;
// Update tasks
if (updatedGoal.tasks != null) {
for (var updatedTask in updatedGoal.tasks!) {
final taskIndex = joinedGoals[goalIndex].tasks?.indexWhere(
(task) => task.goalTaskId == updatedTask.goalTaskId,
);
if (taskIndex != null && taskIndex != -1) {
joinedGoals[goalIndex].tasks![taskIndex] = updatedTask;
}
}
}
joinedGoals.refresh();
}
}
}