368 lines
10 KiB
Dart
368 lines
10 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/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<GetConnectionsWithTribeMemberCheckedResponseModel?> connectionsData =
|
|
Rx<GetConnectionsWithTribeMemberCheckedResponseModel?>(null);
|
|
|
|
// Keep track of selected members
|
|
RxSet<String> selectedMemberIds = <String>{}.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<void> 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<bool> 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<bool> 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<bool> 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);
|
|
}
|
|
}
|
|
}
|