2026-06-29 13:43:40 +05:30

1399 lines
47 KiB
Dart

// import 'dart:async';
// import 'dart:convert';
// import 'package:flutter/material.dart';
// import 'package:onufitness/services/logger_service/logger_service.dart';
// import 'package:get/get.dart';
// import 'package:onufitness/constants/api_endpoints.dart';
// import 'package:onufitness/screens/rise/models/get_challenges_response_model.dart';
// import 'package:onufitness/screens/rise/models/participient_leaderboard_response_model.dart';
// import 'package:onufitness/services/api_services/base_api_services.dart';
// import 'package:onufitness/utils/custom_sneakbar.dart';
// class RiseController extends GetxController {
// final logger = LoggerService();
// @override
// void onInit() {
// upcomingSearchController = TextEditingController();
// createdSearchController = TextEditingController();
// joinedSearchController = TextEditingController();
// inputValueController = TextEditingController();
// ongoingSearchController = TextEditingController();
// super.onInit();
// fetchOngoingChallenges();
// fetchUpcomingChallenges();
// fetchCreatedChallenges();
// fetchJoinedChallenges();
// Future.delayed(Duration(milliseconds: 900), () {
// _ongoingInitialized = true;
// _upcomingInitialized = true;
// _createdInitialized = true;
// _joinedInitialized = true;
// ongoingSearchController.addListener(_onOngoingSearchChanged);
// upcomingSearchController.addListener(_onUpcomingSearchChanged);
// createdSearchController.addListener(_onCreatedSearchChanged);
// joinedSearchController.addListener(_onJoinedSearchChanged);
// });
// }
// @override
// void onClose() {
// _ongoingSearchTimer?.cancel();
// _upcomingSearchTimer?.cancel();
// _createdSearchTimer?.cancel();
// _joinedSearchTimer?.cancel();
// ongoingSearchController.removeListener(_onOngoingSearchChanged);
// upcomingSearchController.removeListener(_onUpcomingSearchChanged);
// createdSearchController.removeListener(_onCreatedSearchChanged);
// joinedSearchController.removeListener(_onJoinedSearchChanged);
// ongoingSearchController.dispose();
// upcomingSearchController.dispose();
// createdSearchController.dispose();
// joinedSearchController.dispose();
// super.onClose();
// }
// //----------------------------------------------------------------------------------------------------
// //......Ongoing Challenges............................................................................
// //----------------------------------------------------------------------------------------------------
// var ongoingChallenges = <ChallengeItem>[].obs;
// var isOngoingLoading = false.obs;
// var ongoingCurrentPage = 1;
// var ongoingHasMoreData = true.obs;
// TextEditingController ongoingSearchController = TextEditingController();
// final ongoingSearchText = "".obs;
// Timer? _ongoingSearchTimer;
// bool _ongoingInitialized = false;
// void _onOngoingSearchChanged() {
// if (!_ongoingInitialized) return;
// _ongoingSearchTimer?.cancel();
// _ongoingSearchTimer = Timer(Duration(milliseconds: 800), () {
// ongoingSearchText.value = ongoingSearchController.text;
// fetchOngoingChallenges(isRefresh: true);
// });
// }
// Future<void> fetchOngoingChallenges({bool isRefresh = false}) async {
// if (isRefresh) {
// ongoingCurrentPage = 1;
// ongoingChallenges.clear();
// ongoingHasMoreData.value = true;
// }
// if (isOngoingLoading.value || !ongoingHasMoreData.value) return;
// isOngoingLoading.value = true;
// try {
// var url =
// "${ApiUrl.fetchOngoingChallenges}?PageNumber=$ongoingCurrentPage&pageSize=20";
// if (ongoingSearchController.text.isNotEmpty ||
// ongoingSearchText.value.isNotEmpty) {
// url += "&SearchType=Name&SearchValue=${ongoingSearchText.value}";
// }
// final response = await ApiBase.getRequest(extendedURL: url);
// if (response.statusCode == 200) {
// final challengesResponse = challengesResponseModelFromJson(
// response.body,
// );
// if (challengesResponse.isSuccess == true &&
// challengesResponse.data != null) {
// List<ChallengeItem> newChallenges =
// challengesResponse.data!.items ?? [];
// if (newChallenges.isNotEmpty) {
// if (isRefresh) {
// ongoingChallenges.value = newChallenges;
// } else {
// ongoingChallenges.addAll(newChallenges);
// }
// ongoingCurrentPage++;
// } else {
// ongoingHasMoreData.value = false;
// }
// } else {
// if (isRefresh) ongoingChallenges.clear();
// }
// } else {
// customSnackbar(
// title: "Error",
// message: "Failed to fetch ongoing challenges",
// );
// }
// } catch (e) {
// logger.error('Error fetching ongoing challenges: $e');
// customSnackbar(
// title: "Error",
// message: "Failed to fetch ongoing challenges",
// );
// } finally {
// isOngoingLoading.value = false;
// }
// }
// Future<void> refreshOngoingChallenges() async {
// await fetchOngoingChallenges(isRefresh: true);
// }
// void loadMoreOngoingChallenges() {
// if (!isOngoingLoading.value && ongoingHasMoreData.value) {
// fetchOngoingChallenges();
// }
// }
// void clearOngoingSearch() {
// ongoingSearchController.clear();
// ongoingSearchText.value = "";
// }
// //----------------------------------------------------------------------------------------------------
// //......Upcoming Challenges...........................................................................
// //----------------------------------------------------------------------------------------------------
// var upcomingChallenges = <ChallengeItem>[].obs;
// var isUpcomingLoading = false.obs;
// var upcomingCurrentPage = 1;
// var upcomingHasMoreData = true.obs;
// TextEditingController upcomingSearchController = TextEditingController();
// final upcomingSearchText = "".obs;
// Timer? _upcomingSearchTimer;
// bool _upcomingInitialized = false;
// void _onUpcomingSearchChanged() {
// if (!_upcomingInitialized) return;
// _upcomingSearchTimer?.cancel();
// _upcomingSearchTimer = Timer(Duration(milliseconds: 800), () {
// upcomingSearchText.value = upcomingSearchController.text;
// fetchUpcomingChallenges(isRefresh: true);
// });
// }
// Future<void> fetchUpcomingChallenges({bool isRefresh = false}) async {
// if (isRefresh) {
// upcomingCurrentPage = 1;
// upcomingChallenges.clear();
// upcomingHasMoreData.value = true;
// }
// if (isUpcomingLoading.value || !upcomingHasMoreData.value) return;
// isUpcomingLoading.value = true;
// try {
// var url =
// "${ApiUrl.fetchUpcomingChallenges}?PageNumber=$upcomingCurrentPage&pageSize=20";
// if (upcomingSearchController.text.isNotEmpty ||
// upcomingSearchText.value.isNotEmpty) {
// url += "&SearchType=Name&SearchValue=${upcomingSearchText.value}";
// }
// final response = await ApiBase.getRequest(extendedURL: url);
// if (response.statusCode == 200) {
// final challengesResponse = challengesResponseModelFromJson(
// response.body,
// );
// if (challengesResponse.isSuccess == true &&
// challengesResponse.data != null) {
// List<ChallengeItem> newChallenges =
// challengesResponse.data!.items ?? [];
// if (newChallenges.isNotEmpty) {
// if (isRefresh) {
// upcomingChallenges.value = newChallenges;
// } else {
// upcomingChallenges.addAll(newChallenges);
// }
// upcomingCurrentPage++;
// } else {
// upcomingHasMoreData.value = false;
// }
// } else {
// if (isRefresh) upcomingChallenges.clear();
// }
// } else {
// customSnackbar(
// title: "Error",
// message: "Failed to fetch upcoming challenges",
// );
// }
// } catch (e) {
// logger.error('Error fetching upcoming challenges: $e');
// customSnackbar(
// title: "Error",
// message: "Failed to fetch upcoming challenges",
// );
// } finally {
// isUpcomingLoading.value = false;
// }
// }
// void refreshUpcomingChallenges() async {
// await fetchUpcomingChallenges(isRefresh: true);
// }
// void loadMoreUpcomingChallenges() {
// if (!isUpcomingLoading.value && upcomingHasMoreData.value) {
// fetchUpcomingChallenges();
// }
// }
// void clearUpcomingSearch() {
// upcomingSearchController.clear();
// upcomingSearchText.value = "";
// }
// //----------------------------------------------------------------------------------------------------
// //......Created Challenges............................................................................
// //----------------------------------------------------------------------------------------------------
// var createdChallenges = <ChallengeItem>[].obs;
// var isCreatedLoading = false.obs;
// var createdCurrentPage = 1;
// var createdHasMoreData = true.obs;
// TextEditingController createdSearchController = TextEditingController();
// final createdSearchText = "".obs;
// Timer? _createdSearchTimer;
// bool _createdInitialized = false;
// void _onCreatedSearchChanged() {
// if (!_createdInitialized) return;
// _createdSearchTimer?.cancel();
// _createdSearchTimer = Timer(Duration(milliseconds: 800), () {
// createdSearchText.value = createdSearchController.text;
// fetchCreatedChallenges(isRefresh: true);
// });
// }
// Future<void> fetchCreatedChallenges({bool isRefresh = false}) async {
// if (isRefresh) {
// createdCurrentPage = 1;
// createdChallenges.clear();
// createdHasMoreData.value = true;
// }
// if (isCreatedLoading.value || !createdHasMoreData.value) return;
// isCreatedLoading.value = true;
// try {
// var url =
// "${ApiUrl.fetchCreatedbyMeChallenges}?PageNumber=$createdCurrentPage&PageSize=10";
// if (createdSearchController.text.isNotEmpty ||
// createdSearchText.value.isNotEmpty) {
// url += "&SearchType=Name&SearchValue=${createdSearchText.value}";
// }
// final response = await ApiBase.getRequest(extendedURL: url);
// if (response.statusCode == 200) {
// final challengesResponse = challengesResponseModelFromJson(
// response.body,
// );
// if (challengesResponse.isSuccess == true &&
// challengesResponse.data != null) {
// List<ChallengeItem> newChallenges =
// challengesResponse.data!.items ?? [];
// if (newChallenges.isNotEmpty) {
// if (isRefresh) {
// createdChallenges.value = newChallenges;
// } else {
// createdChallenges.addAll(newChallenges);
// }
// createdCurrentPage++;
// } else {
// createdHasMoreData.value = false;
// }
// } else {
// if (isRefresh) createdChallenges.clear();
// }
// } else {
// customSnackbar(title: "Error", message: "Failed to fetch challenges");
// }
// } catch (e) {
// logger.error('Error fetching created challenges: $e');
// customSnackbar(title: "Error", message: "Failed to fetch challenges");
// } finally {
// isCreatedLoading.value = false;
// }
// }
// void refreshCreatedChallenges() async {
// await fetchCreatedChallenges(isRefresh: true);
// }
// void loadMoreCreatedChallenges() {
// if (!isCreatedLoading.value && createdHasMoreData.value) {
// fetchCreatedChallenges();
// }
// }
// void clearCreatedSearch() {
// createdSearchController.clear();
// createdSearchText.value = "";
// }
// //----------------------------------------------------------------------------------------------------
// //......Joined Challenges list.............................................................................
// //----------------------------------------------------------------------------------------------------
// var joinedChallenges = <ChallengeItem>[].obs;
// var isJoinedLoading = false.obs;
// var joinedCurrentPage = 1;
// var joinedHasMoreData = true.obs;
// TextEditingController joinedSearchController = TextEditingController();
// final joinedSearchText = "".obs;
// Timer? _joinedSearchTimer;
// bool _joinedInitialized = false;
// void _onJoinedSearchChanged() {
// if (!_joinedInitialized) return;
// _joinedSearchTimer?.cancel();
// _joinedSearchTimer = Timer(Duration(milliseconds: 800), () {
// joinedSearchText.value = joinedSearchController.text;
// fetchJoinedChallenges(isRefresh: true);
// });
// }
// Future<void> fetchJoinedChallenges({bool isRefresh = false}) async {
// if (isRefresh) {
// joinedCurrentPage = 1;
// joinedChallenges.clear();
// joinedHasMoreData.value = true;
// }
// if (isJoinedLoading.value || !joinedHasMoreData.value) return;
// isJoinedLoading.value = true;
// try {
// var url =
// "${ApiUrl.fetchJoinedChallenges}?PageNumber=$joinedCurrentPage&PageSize=10";
// if (joinedSearchController.text.isNotEmpty ||
// joinedSearchText.value.isNotEmpty) {
// url += "&SearchType=Name&SearchValue=${joinedSearchText.value}";
// }
// final response = await ApiBase.getRequest(extendedURL: url);
// if (response.statusCode == 200) {
// final challengesResponse = challengesResponseModelFromJson(
// response.body,
// );
// if (challengesResponse.isSuccess == true &&
// challengesResponse.data != null) {
// List<ChallengeItem> newChallenges =
// challengesResponse.data!.items ?? [];
// if (newChallenges.isNotEmpty) {
// if (isRefresh) {
// joinedChallenges.value = newChallenges;
// } else {
// joinedChallenges.addAll(newChallenges);
// }
// joinedCurrentPage++;
// } else {
// joinedHasMoreData.value = false;
// }
// } else {
// if (isRefresh) joinedChallenges.clear();
// }
// }
// } catch (e) {
// logger.error('Error fetching joined challenges: $e');
// } finally {
// isJoinedLoading.value = false;
// }
// }
// void refreshJoinedChallenges() async {
// await fetchJoinedChallenges(isRefresh: true);
// }
// void loadMoreJoinedChallenges() {
// if (!isJoinedLoading.value && joinedHasMoreData.value) {
// fetchJoinedChallenges();
// }
// }
// void clearJoinedSearch() {
// joinedSearchController.clear();
// joinedSearchText.value = "";
// }
// // Join a Challenge......................................................................
// RxList<int> isJoiningChallengeLoading = <int>[].obs;
// var joinChallengeError = ''.obs;
// Future<bool> joinChallenge({required int challengeID}) async {
// bool ret = false;
// try {
// isJoiningChallengeLoading.add(challengeID);
// joinChallengeError.value = '';
// final response = await ApiBase.postRequest(
// extendedURL: ApiUrl.joinChallenge,
// body: {"challengeID": challengeID},
// );
// if (response.statusCode == 200 || response.statusCode == 201) {
// ret = true;
// customSnackbar(
// title: 'Success',
// message: 'Successfully joined the challenge!',
// duration: 1,
// );
// } else {
// ret = false;
// Map<String, dynamic> responseData = json.decode(response.body);
// String errorMessage =
// responseData['message'] ??
// 'An error occurred while joining the challenge';
// joinChallengeError.value = errorMessage;
// customSnackbar(title: 'Error', message: errorMessage, duration: 1);
// }
// } catch (e) {
// logger.error('Error joining challenge: $e');
// ret = false;
// String errorMessage = 'An error occurred while joining the challenge';
// joinChallengeError.value = errorMessage;
// customSnackbar(title: 'Error', message: errorMessage, duration: 1);
// } finally {
// isJoiningChallengeLoading.remove(challengeID);
// }
// return ret;
// }
// //......Input Performance............................................................................
// TextEditingController inputValueController = TextEditingController();
// RxBool isChallengeInputLoading = false.obs;
// Future<bool> inputChallengeTaskTracking({
// required int challengeID,
// required int challengeTaskID,
// required String metricValue,
// }) async {
// bool ret = false;
// isChallengeInputLoading(true);
// try {
// final requestBody = {
// "challengeID": challengeID,
// "challengeTaskID": challengeTaskID,
// "metricValue": double.tryParse(metricValue) ?? 0,
// "trackingDate": selectedDate.value?.toUtc().toIso8601String(),
// };
// final response = await ApiBase.postRequest(
// extendedURL: ApiUrl.inputChallengeTask,
// body: requestBody,
// );
// final responseData = jsonDecode(response.body);
// if (response.statusCode == 200 || response.statusCode == 201) {
// ret = true;
// inputValueController.clear();
// customSnackbar(
// title: "Successful!",
// message:
// responseData['message'] ?? "Performance submitted successfully!",
// duration: 1,
// );
// } else {
// ret = false;
// customSnackbar(
// title: "Failed!",
// message: responseData['message'] ?? "Performance submission failed.",
// );
// }
// } catch (e) {
// logger.error('Error submitting challenge task: $e');
// ret = false;
// customSnackbar(
// title: "Failed!",
// message: "An error occurred. Please try again.",
// );
// } finally {
// isChallengeInputLoading(false);
// }
// return ret;
// }
// //----------------------------------------------------------------------------------------------------
// //......Challenge Participants (Stride Participants)................................................
// //----------------------------------------------------------------------------------------------------
// var challengeParticipants = <SingleUserLeaderboard>[].obs;
// var isParticipantsLoading = false.obs;
// var participantsCurrentPage = 1;
// var participantsHasMoreData = true.obs;
// var currentChallengeId = 0;
// Future<void> fetchChallengeParticipants({
// required int challengeId,
// bool isRefresh = false,
// }) async {
// if (isRefresh) {
// participantsCurrentPage = 1;
// challengeParticipants.clear();
// participantsHasMoreData.value = true;
// currentChallengeId = challengeId;
// }
// // If it's a different challenge, reset everything
// if (currentChallengeId != challengeId) {
// participantsCurrentPage = 1;
// challengeParticipants.clear();
// participantsHasMoreData.value = true;
// currentChallengeId = challengeId;
// }
// if (isParticipantsLoading.value || !participantsHasMoreData.value) return;
// isParticipantsLoading.value = true;
// try {
// var url =
// "${ApiUrl.fetchLeaderBoard}/$challengeId?PageNumber=$participantsCurrentPage&PageSize=10";
// final response = await ApiBase.getRequest(extendedURL: url);
// if (response.statusCode == 200) {
// final participantsResponse =
// leaderboardParticipentListResponseModelFromJson(response.body);
// if (participantsResponse.isSuccess == true &&
// participantsResponse.data != null) {
// List<SingleUserLeaderboard> newParticipants =
// participantsResponse.data!.items ?? [];
// if (newParticipants.isNotEmpty) {
// if (isRefresh) {
// challengeParticipants.value = newParticipants;
// } else {
// challengeParticipants.addAll(newParticipants);
// }
// participantsCurrentPage++;
// } else {
// participantsHasMoreData.value = false;
// }
// } else {
// if (isRefresh) challengeParticipants.clear();
// }
// } else {
// customSnackbar(
// title: 'Error',
// message: 'Failed to fetch challenge participants',
// duration: 2,
// );
// }
// } catch (e) {
// logger.error('Error fetching challenge participants: $e');
// customSnackbar(
// title: 'Error',
// message: 'Failed to fetch challenge participants',
// duration: 2,
// );
// } finally {
// isParticipantsLoading.value = false;
// }
// }
// void refreshChallengeParticipants(int challengeId) async {
// await fetchChallengeParticipants(challengeId: challengeId, isRefresh: true);
// }
// void loadMoreChallengeParticipants() {
// if (!isParticipantsLoading.value &&
// participantsHasMoreData.value &&
// currentChallengeId != 0) {
// fetchChallengeParticipants(challengeId: currentChallengeId);
// }
// }
// //----------------------------------------------------------------------------------------------------
// //......Common Logic..................................................................................
// //----------------------------------------------------------------------------------------------------
// RxString showSelectedDateToUI = "".obs;
// final Rx<DateTime?> selectedDate = Rx<DateTime?>(null);
// 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);
// }
// }
//................ Above one is working code till 11th June 2026 ................................
import 'dart:async';
import 'dart:convert';
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:onufitness/services/logger_service/logger_service.dart';
import 'package:get/get.dart';
import 'package:onufitness/constants/api_endpoints.dart';
import 'package:onufitness/screens/rise/models/get_challenges_response_model.dart';
import 'package:onufitness/screens/rise/models/participient_leaderboard_response_model.dart';
import 'package:onufitness/services/api_services/base_api_services.dart';
import 'package:onufitness/utils/custom_sneakbar.dart';
class RiseController extends GetxController {
final logger = LoggerService();
/// When set, all challenge fetches use the tribe-wise endpoints for this
/// tribe. Set this to a tribe id before fetching tribe-scoped data, and
/// set it back to null before fetching the normal (non-tribe) lists.
int? activeTribeId;
/// [baseUrl] MUST be the bare endpoint (no query string) — e.g.
/// ApiUrl.fetchJoinedChallenges. Append your query params to the
/// RETURNED string, never before calling this — appending first breaks
/// the map lookup below and you'll silently always get the normal
/// (non-tribe) URL back, no matter what activeTribeId is.
///
String isTribeChallenge(String baseUrl) {
if (activeTribeId == null) return baseUrl;
const tribeWiseMap = {
ApiUrl.fetchOngoingChallenges:
'/api/Challenges/get-ongoing-challenges-tribe-wise',
ApiUrl.fetchUpcomingChallenges:
'/api/Challenges/get-upcoming-challenges-tribe-wise',
ApiUrl.fetchJoinedChallenges:
'/api/Challenges/get-joined-challenges-tribe-wise',
ApiUrl.fetchCreatedbyMeChallenges:
'/api/Challenges/get-created-challenges-tribe-wise',
};
final tribeUrl = tribeWiseMap[baseUrl];
if (tribeUrl == null) {
log("No tribe-wise mapping for: $baseUrl");
return baseUrl;
}
final result = '$tribeUrl/$activeTribeId';
log("Tribe-specific URL: $result");
return result;
}
@override
void onInit() {
upcomingSearchController = TextEditingController();
createdSearchController = TextEditingController();
joinedSearchController = TextEditingController();
inputValueController = TextEditingController();
ongoingSearchController = TextEditingController();
super.onInit();
fetchOngoingChallenges();
fetchUpcomingChallenges();
fetchCreatedChallenges();
fetchJoinedChallenges();
Future.delayed(const Duration(milliseconds: 900), () {
_ongoingInitialized = true;
_upcomingInitialized = true;
_createdInitialized = true;
_joinedInitialized = true;
ongoingSearchController.addListener(_onOngoingSearchChanged);
upcomingSearchController.addListener(_onUpcomingSearchChanged);
createdSearchController.addListener(_onCreatedSearchChanged);
joinedSearchController.addListener(_onJoinedSearchChanged);
});
}
@override
void onClose() {
_ongoingSearchTimer?.cancel();
_upcomingSearchTimer?.cancel();
_createdSearchTimer?.cancel();
_joinedSearchTimer?.cancel();
ongoingSearchController.removeListener(_onOngoingSearchChanged);
upcomingSearchController.removeListener(_onUpcomingSearchChanged);
createdSearchController.removeListener(_onCreatedSearchChanged);
joinedSearchController.removeListener(_onJoinedSearchChanged);
ongoingSearchController.dispose();
upcomingSearchController.dispose();
createdSearchController.dispose();
joinedSearchController.dispose();
super.onClose();
}
//----------------------------------------------------------------------------------------------------
//......Ongoing Challenges............................................................................
//----------------------------------------------------------------------------------------------------
var ongoingChallenges = <ChallengeItem>[].obs;
var isOngoingLoading = false.obs;
var ongoingCurrentPage = 1;
var ongoingHasMoreData = true.obs;
TextEditingController ongoingSearchController = TextEditingController();
final ongoingSearchText = "".obs;
Timer? _ongoingSearchTimer;
bool _ongoingInitialized = false;
int _ongoingRequestId = 0;
void _onOngoingSearchChanged() {
if (!_ongoingInitialized) return;
_ongoingSearchTimer?.cancel();
_ongoingSearchTimer = Timer(const Duration(milliseconds: 800), () {
ongoingSearchText.value = ongoingSearchController.text;
fetchOngoingChallenges(isRefresh: true);
});
}
Future<void> fetchOngoingChallenges({bool isRefresh = false}) async {
if (isRefresh) {
ongoingCurrentPage = 1;
ongoingChallenges.clear();
ongoingHasMoreData.value = true;
} else {
if (isOngoingLoading.value || !ongoingHasMoreData.value) return;
}
final requestId = ++_ongoingRequestId; // invalidates any in-flight request
isOngoingLoading.value = true;
try {
final baseUrl = isTribeChallenge(ApiUrl.fetchOngoingChallenges);
var url = "$baseUrl?PageNumber=$ongoingCurrentPage&pageSize=20";
if (ongoingSearchController.text.isNotEmpty ||
ongoingSearchText.value.isNotEmpty) {
url += "&SearchType=Name&SearchValue=${ongoingSearchText.value}";
}
log("url----->: $url");
final response = await ApiBase.getRequest(extendedURL: url);
log("statusCode----->: ${response.statusCode}");
if (requestId != _ongoingRequestId) return; // stale response, drop it
if (response.statusCode == 200) {
final challengesResponse = challengesResponseModelFromJson(
response.body,
);
if (challengesResponse.isSuccess == true &&
challengesResponse.data != null) {
final newChallenges = challengesResponse.data!.items ?? [];
if (newChallenges.isNotEmpty) {
if (isRefresh) {
ongoingChallenges.value = newChallenges;
} else {
ongoingChallenges.addAll(newChallenges);
}
ongoingCurrentPage++;
} else {
ongoingHasMoreData.value = false;
}
} else {
if (isRefresh) ongoingChallenges.clear();
}
} else {
customSnackbar(
title: "Error",
message: "Failed to fetch ongoing challenges",
);
}
} catch (e) {
if (requestId != _ongoingRequestId) return;
logger.error('Error fetching ongoing challenges: $e');
customSnackbar(
title: "Error",
message: "Failed to fetch ongoing challenges",
);
} finally {
if (requestId == _ongoingRequestId) {
isOngoingLoading.value = false;
}
}
}
Future<void> refreshOngoingChallenges() async {
await fetchOngoingChallenges(isRefresh: true);
}
void loadMoreOngoingChallenges() {
if (!isOngoingLoading.value && ongoingHasMoreData.value) {
fetchOngoingChallenges();
}
}
void clearOngoingSearch() {
ongoingSearchController.clear();
ongoingSearchText.value = "";
}
//----------------------------------------------------------------------------------------------------
//......Upcoming Challenges...........................................................................
//----------------------------------------------------------------------------------------------------
var upcomingChallenges = <ChallengeItem>[].obs;
var isUpcomingLoading = false.obs;
var upcomingCurrentPage = 1;
var upcomingHasMoreData = true.obs;
TextEditingController upcomingSearchController = TextEditingController();
final upcomingSearchText = "".obs;
Timer? _upcomingSearchTimer;
bool _upcomingInitialized = false;
int _upcomingRequestId = 0;
void _onUpcomingSearchChanged() {
if (!_upcomingInitialized) return;
_upcomingSearchTimer?.cancel();
_upcomingSearchTimer = Timer(const Duration(milliseconds: 800), () {
upcomingSearchText.value = upcomingSearchController.text;
fetchUpcomingChallenges(isRefresh: true);
});
}
Future<void> fetchUpcomingChallenges({bool isRefresh = false}) async {
if (isRefresh) {
upcomingCurrentPage = 1;
upcomingChallenges.clear();
upcomingHasMoreData.value = true;
} else {
if (isUpcomingLoading.value || !upcomingHasMoreData.value) return;
}
final requestId = ++_upcomingRequestId;
isUpcomingLoading.value = true;
try {
final baseUrl = isTribeChallenge(ApiUrl.fetchUpcomingChallenges);
var url = "$baseUrl?PageNumber=$upcomingCurrentPage&pageSize=20";
if (upcomingSearchController.text.isNotEmpty ||
upcomingSearchText.value.isNotEmpty) {
url += "&SearchType=Name&SearchValue=${upcomingSearchText.value}";
}
log("url----->: $url");
final response = await ApiBase.getRequest(extendedURL: url);
if (requestId != _upcomingRequestId) return;
if (response.statusCode == 200) {
final challengesResponse = challengesResponseModelFromJson(
response.body,
);
if (challengesResponse.isSuccess == true &&
challengesResponse.data != null) {
final newChallenges = challengesResponse.data!.items ?? [];
if (newChallenges.isNotEmpty) {
if (isRefresh) {
upcomingChallenges.value = newChallenges;
} else {
upcomingChallenges.addAll(newChallenges);
}
upcomingCurrentPage++;
} else {
upcomingHasMoreData.value = false;
}
} else {
if (isRefresh) upcomingChallenges.clear();
}
} else {
customSnackbar(
title: "Error",
message: "Failed to fetch upcoming challenges",
);
}
} catch (e) {
if (requestId != _upcomingRequestId) return;
logger.error('Error fetching upcoming challenges: $e');
customSnackbar(
title: "Error",
message: "Failed to fetch upcoming challenges",
);
} finally {
if (requestId == _upcomingRequestId) {
isUpcomingLoading.value = false;
}
}
}
void refreshUpcomingChallenges() async {
await fetchUpcomingChallenges(isRefresh: true);
}
void loadMoreUpcomingChallenges() {
if (!isUpcomingLoading.value && upcomingHasMoreData.value) {
fetchUpcomingChallenges();
}
}
void clearUpcomingSearch() {
upcomingSearchController.clear();
upcomingSearchText.value = "";
}
//----------------------------------------------------------------------------------------------------
//......Created Challenges............................................................................
//----------------------------------------------------------------------------------------------------
var createdChallenges = <ChallengeItem>[].obs;
var isCreatedLoading = false.obs;
var createdCurrentPage = 1;
var createdHasMoreData = true.obs;
TextEditingController createdSearchController = TextEditingController();
final createdSearchText = "".obs;
Timer? _createdSearchTimer;
bool _createdInitialized = false;
int _createdRequestId = 0;
void _onCreatedSearchChanged() {
if (!_createdInitialized) return;
_createdSearchTimer?.cancel();
_createdSearchTimer = Timer(const Duration(milliseconds: 800), () {
createdSearchText.value = createdSearchController.text;
fetchCreatedChallenges(isRefresh: true);
});
}
Future<void> fetchCreatedChallenges({bool isRefresh = false}) async {
if (isRefresh) {
createdCurrentPage = 1;
createdChallenges.clear();
createdHasMoreData.value = true;
} else {
if (isCreatedLoading.value || !createdHasMoreData.value) return;
}
final requestId = ++_createdRequestId;
isCreatedLoading.value = true;
try {
final baseUrl = isTribeChallenge(ApiUrl.fetchCreatedbyMeChallenges);
var url = "$baseUrl?PageNumber=$createdCurrentPage&PageSize=10";
if (createdSearchController.text.isNotEmpty ||
createdSearchText.value.isNotEmpty) {
url += "&SearchType=Name&SearchValue=${createdSearchText.value}";
}
log("url----->: $url");
final response = await ApiBase.getRequest(extendedURL: url);
if (requestId != _createdRequestId) return;
if (response.statusCode == 200) {
final challengesResponse = challengesResponseModelFromJson(
response.body,
);
if (challengesResponse.isSuccess == true &&
challengesResponse.data != null) {
final newChallenges = challengesResponse.data!.items ?? [];
if (newChallenges.isNotEmpty) {
if (isRefresh) {
createdChallenges.value = newChallenges;
} else {
createdChallenges.addAll(newChallenges);
}
createdCurrentPage++;
} else {
createdHasMoreData.value = false;
}
} else {
if (isRefresh) createdChallenges.clear();
}
} else {
customSnackbar(title: "Error", message: "Failed to fetch challenges");
}
} catch (e) {
if (requestId != _createdRequestId) return;
logger.error('Error fetching created challenges: $e');
customSnackbar(title: "Error", message: "Failed to fetch challenges");
} finally {
if (requestId == _createdRequestId) {
isCreatedLoading.value = false;
}
}
}
void refreshCreatedChallenges() async {
await fetchCreatedChallenges(isRefresh: true);
}
void loadMoreCreatedChallenges() {
if (!isCreatedLoading.value && createdHasMoreData.value) {
fetchCreatedChallenges();
}
}
void clearCreatedSearch() {
createdSearchController.clear();
createdSearchText.value = "";
}
//----------------------------------------------------------------------------------------------------
//......Joined Challenges.............................................................................
//----------------------------------------------------------------------------------------------------
var joinedChallenges = <ChallengeItem>[].obs;
var isJoinedLoading = false.obs;
var joinedCurrentPage = 1;
var joinedHasMoreData = true.obs;
TextEditingController joinedSearchController = TextEditingController();
final joinedSearchText = "".obs;
Timer? _joinedSearchTimer;
bool _joinedInitialized = false;
int _joinedRequestId = 0;
void _onJoinedSearchChanged() {
if (!_joinedInitialized) return;
_joinedSearchTimer?.cancel();
_joinedSearchTimer = Timer(const Duration(milliseconds: 800), () {
joinedSearchText.value = joinedSearchController.text;
fetchJoinedChallenges(isRefresh: true);
});
}
Future<void> fetchJoinedChallenges({bool isRefresh = false}) async {
if (isRefresh) {
joinedCurrentPage = 1;
joinedChallenges.clear();
joinedHasMoreData.value = true;
} else {
if (isJoinedLoading.value || !joinedHasMoreData.value) return;
}
final requestId = ++_joinedRequestId;
isJoinedLoading.value = true;
try {
final baseUrl = isTribeChallenge(ApiUrl.fetchJoinedChallenges);
var url = "$baseUrl?PageNumber=$joinedCurrentPage&PageSize=10";
if (joinedSearchController.text.isNotEmpty ||
joinedSearchText.value.isNotEmpty) {
url += "&SearchType=Name&SearchValue=${joinedSearchText.value}";
}
log("url----->: $url");
final response = await ApiBase.getRequest(extendedURL: url);
if (requestId != _joinedRequestId) return;
if (response.statusCode == 200) {
final challengesResponse = challengesResponseModelFromJson(
response.body,
);
if (challengesResponse.isSuccess == true &&
challengesResponse.data != null) {
final newChallenges = challengesResponse.data!.items ?? [];
if (newChallenges.isNotEmpty) {
if (isRefresh) {
joinedChallenges.value = newChallenges;
} else {
joinedChallenges.addAll(newChallenges);
}
joinedCurrentPage++;
} else {
joinedHasMoreData.value = false;
}
} else {
if (isRefresh) joinedChallenges.clear();
}
}
} catch (e) {
if (requestId != _joinedRequestId) return;
logger.error('Error fetching joined challenges: $e');
} finally {
if (requestId == _joinedRequestId) {
isJoinedLoading.value = false;
}
}
}
void refreshJoinedChallenges() async {
await fetchJoinedChallenges(isRefresh: true);
}
void loadMoreJoinedChallenges() {
if (!isJoinedLoading.value && joinedHasMoreData.value) {
fetchJoinedChallenges();
}
}
void clearJoinedSearch() {
joinedSearchController.clear();
joinedSearchText.value = "";
}
// Join a Challenge......................................................................
RxList<int> isJoiningChallengeLoading = <int>[].obs;
var joinChallengeError = ''.obs;
Future<bool> joinChallenge({required int challengeID}) async {
bool ret = false;
try {
isJoiningChallengeLoading.add(challengeID);
joinChallengeError.value = '';
final response = await ApiBase.postRequest(
extendedURL: ApiUrl.joinChallenge,
body: {"challengeID": challengeID},
);
if (response.statusCode == 200 || response.statusCode == 201) {
ret = true;
customSnackbar(
title: 'Success',
message: 'Successfully joined the challenge!',
duration: 1,
);
} else {
ret = false;
Map<String, dynamic> responseData = json.decode(response.body);
String errorMessage =
responseData['message'] ??
'An error occurred while joining the challenge';
joinChallengeError.value = errorMessage;
customSnackbar(title: 'Error', message: errorMessage, duration: 1);
}
} catch (e) {
logger.error('Error joining challenge: $e');
ret = false;
const errorMessage = 'An error occurred while joining the challenge';
joinChallengeError.value = errorMessage;
customSnackbar(title: 'Error', message: errorMessage, duration: 1);
} finally {
isJoiningChallengeLoading.remove(challengeID);
}
return ret;
}
//......Input Performance............................................................................
TextEditingController inputValueController = TextEditingController();
RxBool isChallengeInputLoading = false.obs;
Future<bool> inputChallengeTaskTracking({
required int challengeID,
required int challengeTaskID,
required String metricValue,
}) async {
bool ret = false;
isChallengeInputLoading(true);
try {
final requestBody = {
"challengeID": challengeID,
"challengeTaskID": challengeTaskID,
"metricValue": double.tryParse(metricValue) ?? 0,
"trackingDate": selectedDate.value?.toUtc().toIso8601String(),
};
final response = await ApiBase.postRequest(
extendedURL: ApiUrl.inputChallengeTask,
body: requestBody,
);
final responseData = jsonDecode(response.body);
if (response.statusCode == 200 || response.statusCode == 201) {
ret = true;
inputValueController.clear();
customSnackbar(
title: "Successful!",
message:
responseData['message'] ?? "Performance submitted successfully!",
duration: 1,
);
} else {
ret = false;
customSnackbar(
title: "Failed!",
message: responseData['message'] ?? "Performance submission failed.",
);
}
} catch (e) {
logger.error('Error submitting challenge task: $e');
ret = false;
customSnackbar(
title: "Failed!",
message: "An error occurred. Please try again.",
);
} finally {
isChallengeInputLoading(false);
}
return ret;
}
//----------------------------------------------------------------------------------------------------
//......Challenge Participants........................................................................
//----------------------------------------------------------------------------------------------------
var challengeParticipants = <SingleUserLeaderboard>[].obs;
var isParticipantsLoading = false.obs;
var participantsCurrentPage = 1;
var participantsHasMoreData = true.obs;
var currentChallengeId = 0;
Future<void> fetchChallengeParticipants({
required int challengeId,
bool isRefresh = false,
}) async {
if (isRefresh) {
participantsCurrentPage = 1;
challengeParticipants.clear();
participantsHasMoreData.value = true;
currentChallengeId = challengeId;
}
if (currentChallengeId != challengeId) {
participantsCurrentPage = 1;
challengeParticipants.clear();
participantsHasMoreData.value = true;
currentChallengeId = challengeId;
}
if (isParticipantsLoading.value || !participantsHasMoreData.value) return;
isParticipantsLoading.value = true;
try {
var url =
"${ApiUrl.fetchLeaderBoard}/$challengeId?PageNumber=$participantsCurrentPage&PageSize=10";
final response = await ApiBase.getRequest(extendedURL: url);
if (response.statusCode == 200) {
final participantsResponse =
leaderboardParticipentListResponseModelFromJson(response.body);
if (participantsResponse.isSuccess == true &&
participantsResponse.data != null) {
final newParticipants = participantsResponse.data!.items ?? [];
if (newParticipants.isNotEmpty) {
if (isRefresh) {
challengeParticipants.value = newParticipants;
} else {
challengeParticipants.addAll(newParticipants);
}
participantsCurrentPage++;
} else {
participantsHasMoreData.value = false;
}
} else {
if (isRefresh) challengeParticipants.clear();
}
} else {
customSnackbar(
title: 'Error',
message: 'Failed to fetch challenge participants',
duration: 2,
);
}
} catch (e) {
logger.error('Error fetching challenge participants: $e');
customSnackbar(
title: 'Error',
message: 'Failed to fetch challenge participants',
duration: 2,
);
} finally {
isParticipantsLoading.value = false;
}
}
void refreshChallengeParticipants(int challengeId) async {
await fetchChallengeParticipants(challengeId: challengeId, isRefresh: true);
}
void loadMoreChallengeParticipants() {
if (!isParticipantsLoading.value &&
participantsHasMoreData.value &&
currentChallengeId != 0) {
fetchChallengeParticipants(challengeId: currentChallengeId);
}
}
//----------------------------------------------------------------------------------------------------
//......Common Logic..................................................................................
//----------------------------------------------------------------------------------------------------
RxString showSelectedDateToUI = "".obs;
final Rx<DateTime?> selectedDate = Rx<DateTime?>(null);
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);
}
}