669 lines
21 KiB
Dart
669 lines
21 KiB
Dart
import 'dart:async';
|
|
import 'dart:convert';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:onufitness/services/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);
|
|
}
|
|
}
|