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/screens/echoboard/models/tribe/get_all_connections_with_tribe_member_checked_response_model.dart'; import 'package:onufitness/services/api_services/base_api_services.dart'; import 'package:onufitness/utils/custom_sneakbar.dart'; class TribeMemberController extends GetxController { RxBool isDeleteTribeLoading = false.obs; RxBool isLoadingMembers = false.obs; RxBool isLoadingMoreMembers = false.obs; RxBool isAddingMembers = false.obs; Rx connectionsData = Rx(null); // Keep track of selected members RxSet selectedMemberIds = {}.obs; // Search controller TextEditingController searchController = TextEditingController(); RxString searchValue = ''.obs; // Pagination RxInt currentPage = 1.obs; RxInt pageSize = 20.obs; RxBool hasMoreData = true.obs; // Scroll controller ScrollController scrollController = ScrollController(); // Current tribe id int? currentTribeId; @override void onInit() { super.onInit(); _setupScrollListener(); } @override void onClose() { searchController.dispose(); scrollController.dispose(); super.onClose(); } void _setupScrollListener() { scrollController.addListener(() { if (scrollController.position.pixels >= scrollController.position.maxScrollExtent - 200) { if (!isLoadingMoreMembers.value && hasMoreData.value) { loadMoreMembers(); } } }); } void onSearchSubmitted(String value) { searchValue.value = value; currentPage.value = 1; hasMoreData.value = true; fetchConnectedUsersWithMemberStatus( tribeId: currentTribeId, isRefresh: true, ); } void clearSearch() { searchController.clear(); searchValue.value = ''; currentPage.value = 1; hasMoreData.value = true; fetchConnectedUsersWithMemberStatus( tribeId: currentTribeId, isRefresh: true, ); } void loadMoreMembers() { if (!hasMoreData.value || isLoadingMoreMembers.value) return; currentPage.value++; fetchConnectedUsersWithMemberStatus( tribeId: currentTribeId, isRefresh: false, ); } // Fetch connected users with member status for a tribe......................................................................... Future fetchConnectedUsersWithMemberStatus({ required int? tribeId, bool isRefresh = true, }) async { if (tribeId == null) return; currentTribeId = tribeId; if (isRefresh) { isLoadingMembers(true); currentPage.value = 1; hasMoreData.value = true; } else { isLoadingMoreMembers(true); } try { String url = "${ApiUrl.getConnectedUsersWithMemberStatus}/$tribeId?PageNumber=${currentPage.value}&PageSize=${pageSize.value}"; // Add search query if exists if (searchValue.value.isNotEmpty) { url += "&SearchType=name&SearchValue=${searchValue.value}"; } final response = await ApiBase.getRequest(extendedURL: url); log(response.body.toString()); if (response.statusCode == 200) { final model = getConnectionsWithTribeMemberCheckedResponseModelFromJson( response.body, ); if (isRefresh) { // Replace data on refresh connectionsData.value = model; // Pre-select users who are already members selectedMemberIds.clear(); if (model.data?.items != null) { for (var user in model.data!.items!) { if (user.isMember == true && user.userId != null) { selectedMemberIds.add(user.userId!); } } } } else { // Append data for pagination if (model.data?.items != null && model.data!.items!.isNotEmpty) { final existingItems = connectionsData.value?.data?.items ?? []; final newItems = model.data!.items!; // Merge items final allItems = [...existingItems, ...newItems]; // Update connectionsData with merged items connectionsData .value = GetConnectionsWithTribeMemberCheckedResponseModel( isSuccess: model.isSuccess, statusCode: model.statusCode, message: model.message, data: Data(totalCount: model.data?.totalCount, items: allItems), errors: model.errors, ); // Pre-select new members for (var user in newItems) { if (user.isMember == true && user.userId != null) { selectedMemberIds.add(user.userId!); } } } else { // No more data hasMoreData.value = false; } } // Check if there's more data to load final totalCount = model.data?.totalCount ?? 0; final loadedCount = connectionsData.value?.data?.items?.length ?? 0; hasMoreData.value = loadedCount < totalCount; } else { customSnackbar(title: "Error", message: "Failed to load users"); } } catch (e) { customSnackbar( title: "Error", message: "An error occurred: ${e.toString()}", ); } finally { if (isRefresh) { isLoadingMembers(false); } else { isLoadingMoreMembers(false); } } } void toggleMemberSelection(String userId) { if (selectedMemberIds.contains(userId)) { selectedMemberIds.remove(userId); } else { selectedMemberIds.add(userId); } } bool isMemberSelected(String userId) { return selectedMemberIds.contains(userId); } // Add selected members to tribe.................................................................................. Future addTribeMembers({required int tribeId}) async { if (selectedMemberIds.isEmpty) { customSnackbar( title: "Warning", message: "Please select at least one member", ); return false; } isAddingMembers(true); try { final body = { "tribeID": tribeId, "tribeMemberIDs": selectedMemberIds.toList(), }; final response = await ApiBase.postRequest( extendedURL: ApiUrl.addTribeMembers, body: body, sendHeaders: true, ); String message = "Something went wrong"; try { final decoded = jsonDecode(response.body); message = decoded["message"]?.toString() ?? message; } catch (_) { // if invalid JSON, keep default message } if (response.statusCode == 200) { Future.delayed(const Duration(milliseconds: 300), () { customSnackbar(title: "Success", message: message, duration: 2); }); // Refresh the list after adding members await fetchConnectedUsersWithMemberStatus( tribeId: tribeId, isRefresh: true, ); return true; } else { Future.delayed(const Duration(milliseconds: 300), () { customSnackbar(title: "Error", message: message, duration: 2); }); return false; } } catch (e) { Future.delayed(const Duration(milliseconds: 300), () { customSnackbar( title: "Failed", message: "Failed to add members", duration: 2, ); }); return false; } finally { isAddingMembers(false); } } // Delete a member from tribe (Admin only)............................................................... RxBool deleteTribeMemberLoading = false.obs; Future deleteTribeMember({ required int tribeId, required String memberId, }) async { deleteTribeMemberLoading(true); try { final body = {"tribeID": tribeId, "tribeMemberID": memberId}; final response = await ApiBase.deleteRequest( extendedURL: ApiUrl.removeTribeMember, body: body, ); String message = "Something went wrong"; try { final decoded = jsonDecode(response.body); message = decoded["message"]?.toString() ?? message; } catch (_) { // if invalid JSON, keep default message } if (response.statusCode == 200) { Future.delayed(const Duration(milliseconds: 300), () { customSnackbar( title: "Success", message: message.isNotEmpty ? message : "Member removed successfully", duration: 2, ); }); return true; } else { Future.delayed(const Duration(milliseconds: 300), () { customSnackbar( title: "Error", message: message.isNotEmpty ? message : "Failed to remove member", duration: 2, ); }); return false; } } catch (e) { Future.delayed(const Duration(milliseconds: 300), () { customSnackbar( title: "Failed", message: "Failed to remove member", duration: 2, ); }); return false; } finally { deleteTribeMemberLoading(false); } } // Delete tribe....................................................................................... Future deleteTribe({required int tribeID}) async { isDeleteTribeLoading(true); try { final response = await ApiBase.deleteRequest( extendedURL: "${ApiUrl.deleteTribe}/$tribeID", body: {}, ); String message = "Something went wrong"; try { final decoded = jsonDecode(response.body); message = decoded["message"]?.toString() ?? message; } catch (_) { // if invalid JSON, keep default message } if (response.statusCode == 200) { Future.delayed(const Duration(milliseconds: 300), () { customSnackbar(title: "Success", message: message, duration: 2); }); return true; } else { Future.delayed(const Duration(milliseconds: 300), () { customSnackbar(title: "Error", message: message, duration: 2); }); return false; } } catch (e) { Future.delayed(const Duration(milliseconds: 300), () { customSnackbar( title: "Failed", message: "Failed to delete tribe", duration: 2, ); }); return false; } finally { isDeleteTribeLoading(false); } } }