taxgilde/lib/controller/api_contoller.dart
2026-04-11 10:21:31 +05:30

537 lines
17 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_riverpod/legacy.dart';
import 'package:taxglide/controller/api_consts.dart';
import 'package:taxglide/controller/api_repository.dart';
import 'package:taxglide/model/chat_model.dart';
import 'package:taxglide/model/chat_profile_model.dart';
import 'package:taxglide/model/city_model.dart';
import 'package:taxglide/model/count_model.dart';
import 'package:taxglide/model/country_model.dart';
import 'package:taxglide/model/dash_board_model.dart';
import 'package:taxglide/model/detail_model.dart';
import 'package:taxglide/model/employeeprofile_model.dart';
import 'package:taxglide/model/login_model.dart';
import 'package:taxglide/model/notification_model.dart';
import 'package:taxglide/model/notificationcount_model.dart';
import 'package:taxglide/model/profile_get_model.dart';
import 'package:taxglide/model/serivce_list_model.dart';
import 'package:taxglide/model/service_list_history_model.dart';
import 'package:taxglide/model/signup_model.dart';
import 'package:taxglide/model/staff_model.dart';
import 'package:taxglide/model/terms_model.dart';
final apiRepositoryProvider = Provider((ref) => ApiRepository());
//login controller
final loginProvider =
StateNotifierProvider<LoginNotifier, AsyncValue<Map<String, dynamic>>>(
(ref) => LoginNotifier(ref.read(apiRepositoryProvider)),
);
class LoginNotifier extends StateNotifier<AsyncValue<Map<String, dynamic>>> {
final ApiRepository _apiRepository;
LoginNotifier(this._apiRepository) : super(const AsyncValue.data({}));
Future<void> login(String mobile) async {
if (!mounted) return;
state = const AsyncValue.loading();
try {
final model = LoginModel(mobile: mobile);
final result = await _apiRepository.loginUser(model);
if (mounted) state = AsyncValue.data(result);
} catch (e, st) {
if (mounted) state = AsyncValue.error(e, st);
}
}
Future<void> verifyOtp(String mobile, String otp) async {
if (!mounted) return;
state = const AsyncValue.loading();
try {
final model = LoginModel(mobile: mobile, otp: otp);
final result = await _apiRepository.verifyOtp(model);
if (mounted) state = AsyncValue.data(result);
} catch (e, st) {
if (mounted) state = AsyncValue.error(e, st);
}
}
}
//signup controller
final signupProvider =
StateNotifierProvider<SignupNotifier, AsyncValue<Map<String, dynamic>>>(
(ref) => SignupNotifier(ref.read(apiRepositoryProvider)),
);
class SignupNotifier extends StateNotifier<AsyncValue<Map<String, dynamic>>> {
final ApiRepository _apiRepository;
SignupNotifier(this._apiRepository) : super(const AsyncValue.data({}));
Future<void> signup(String name, String contactNumber, String email) async {
if (!mounted) return;
state = const AsyncValue.loading();
try {
final model = SignupModel(
name: name,
contactNumber: contactNumber,
email: email,
);
final result = await _apiRepository.signupUser(model);
if (mounted) state = AsyncValue.data(result);
} catch (e, st) {
if (mounted) state = AsyncValue.error(e, st);
}
}
// Verify OTP for signup
Future<void> verifySignupOtp(String mobile, String otp) async {
if (!mounted) return;
state = const AsyncValue.loading();
try {
final result = await _apiRepository.verifySignupOtp(mobile, otp);
if (mounted) state = AsyncValue.data(result);
} catch (e, st) {
if (mounted) state = AsyncValue.error(e, st);
}
}
// Reset state
void reset() {
state = const AsyncValue.data({});
}
}
final employeeloginProvider =
StateNotifierProvider<
EmployeeLoginNotifier,
AsyncValue<Map<String, dynamic>>
>((ref) => EmployeeLoginNotifier(ref.read(apiRepositoryProvider)));
class EmployeeLoginNotifier
extends StateNotifier<AsyncValue<Map<String, dynamic>>> {
final ApiRepository _apiRepository;
EmployeeLoginNotifier(this._apiRepository) : super(const AsyncValue.data({}));
Future<void> login(String mobile) async {
if (!mounted) return;
state = const AsyncValue.loading();
try {
final result = await _apiRepository.employeeLogin(mobile);
if (mounted) state = AsyncValue.data(result);
} catch (e, st) {
if (mounted) state = AsyncValue.error(e, st);
}
}
Future<void> verifyOtp(String mobile, String otp) async {
if (!mounted) return;
state = const AsyncValue.loading();
try {
final model = LoginModel(mobile: mobile, otp: otp);
final result = await _apiRepository.verifyOtp(model);
if (mounted) state = AsyncValue.data(result);
} catch (e, st) {
if (mounted) state = AsyncValue.error(e, st);
}
}
}
final termsProvider = FutureProvider<TermsModel>((ref) async {
final repo = ref.read(apiRepositoryProvider);
return await repo.fetchTermsAndConditions();
});
final dashboardProvider = FutureProvider<DashBoardModel>((ref) async {
final repo = ref.read(apiRepositoryProvider);
return await repo.fetchDashboard();
});
final policyProvider = FutureProvider<TermsModel>((ref) async {
final repo = ref.read(apiRepositoryProvider);
return await repo.fetchpolicy();
});
final profileProvider = FutureProvider<ProfileGetModel>((ref) async {
final repo = ref.read(apiRepositoryProvider);
return await repo.fetchProfile();
});
final employeeProfileProvider = FutureProvider<EmployeeProfileModel>((
ref,
) async {
final repo = ref.read(apiRepositoryProvider);
return await repo.fetchEmployeeProfile();
});
final serviceHistoryNotifierProvider =
StateNotifierProvider.family<
ServiceHistoryNotifier,
AsyncValue<ServiceListHistoryModel>,
String
>((ref, type) {
return ServiceHistoryNotifier(ref: ref, type: type);
});
class ServiceHistoryNotifier
extends StateNotifier<AsyncValue<ServiceListHistoryModel>> {
final Ref ref;
final String type;
String? fromDate;
String? toDate;
ServiceHistoryNotifier({required this.ref, required this.type})
: super(const AsyncValue.loading()) {
fetchServiceHistory();
}
Future<void> fetchServiceHistory() async {
try {
if (!mounted) return;
state = const AsyncValue.loading();
final repo = ref.read(apiRepositoryProvider);
final result = await repo.fetchServiceHistory(
type,
fromDate: fromDate,
toDate: toDate,
);
if (!mounted) return;
state = AsyncValue.data(result);
} catch (e, st) {
if (!mounted) return;
state = AsyncValue.error(e, st);
}
}
// ✅ APPLY FILTER
void applyDateFilter({String? from, String? to}) {
fromDate = from;
toDate = to;
fetchServiceHistory();
}
// ✅ CLEAR FILTER
void clearFilter() {
fromDate = null;
toDate = null;
fetchServiceHistory();
}
// ✅ MANUAL REFRESH
Future<void> refresh() async {
await fetchServiceHistory();
}
}
final countProvider = FutureProvider.family<CountModel, String>((
ref,
type,
) async {
final repo = ref.read(apiRepositoryProvider);
return await repo.fetchCount(type);
});
final notificationTriggerProvider = StateProvider<int>((ref) => 0);
// 🔥 fetch notification count API
final notificationCountProvider =
FutureProvider.autoDispose<NotificationCountModel>((ref) async {
ref.watch(notificationTriggerProvider); // listen for refresh trigger
final repo = ref.read(apiRepositoryProvider);
return await repo.fetchNotificationCount();
});
final serviceDetailProvider = FutureProvider.family<DetailModel, int>((
ref,
id,
) async {
final repo = ref.read(apiRepositoryProvider);
return await repo.fetchServiceDetail(id);
});
final chatDocumentProvider = FutureProvider.family<ChatProfileModel, int>((
ref,
id,
) async {
final repo = ref.read(apiRepositoryProvider);
return await repo.fetchChatDocuments(id);
});
final serviceListProvider = FutureProvider<List<ServiceListModel>>((ref) async {
final repo = ref.read(apiRepositoryProvider);
return await repo.fetchServiceList();
});
final staffListProvider = FutureProvider<List<StaffModel>>((ref) async {
final repo = ref.read(apiRepositoryProvider);
return await repo.fetchStaffList();
});
final notificationProvider = StateNotifierProvider.family
.autoDispose<
NotificationNotifier,
AsyncValue<List<NotificationModel>>,
int
>((ref, page) {
return NotificationNotifier(ref: ref, page: page);
});
// ---------------- NOTIFIER --------------------
class NotificationNotifier
extends StateNotifier<AsyncValue<List<NotificationModel>>> {
final Ref ref;
final int page;
NotificationNotifier({required this.ref, required this.page})
: super(const AsyncValue.loading()) {
fetchNotifications();
}
Future<void> fetchNotifications() async {
if (!mounted) return;
try {
state = const AsyncValue.loading();
final List<NotificationModel> notifications = await ApiRepository()
.fetchNotificationList(page: page);
if (mounted) state = AsyncValue.data(notifications);
} catch (e, st) {
if (mounted) state = AsyncValue.error(e, st);
}
}
}
final chatMessagesProvider =
StateNotifierProvider.family<
ChatMessagesNotifier,
AsyncValue<List<MessageModel>>,
int
>((ref, chatId) {
return ChatMessagesNotifier(ref: ref, chatId: chatId);
});
class ChatMessagesNotifier
extends StateNotifier<AsyncValue<List<MessageModel>>> {
final Ref ref;
final int chatId;
int _currentPage = 1;
bool _hasNextPage = true;
bool _isFetching = false;
bool _isInitialLoad = true;
final List<MessageModel> _messages = [];
ChatMessagesNotifier({required this.ref, required this.chatId})
: super(const AsyncLoading()) {
loadMessages();
}
/// ⭐ Getter to expose hasNextPage
bool get hasNextPage => _hasNextPage;
/// Load paginated messages (used for scroll pagination)
Future<void> loadMessages() async {
if (_isFetching || !_hasNextPage || !mounted) return;
_isFetching = true;
try {
print("📥 Loading page $_currentPage for chat $chatId");
final result = await ref
.read(apiRepositoryProvider)
.fetchChatMessages(chatId: chatId, page: _currentPage);
if (result.isEmpty) {
print("✅ No more messages - pagination complete");
_hasNextPage = false;
} else {
print("✅ Loaded ${result.length} messages from page $_currentPage");
if (_isInitialLoad) {
// ⭐ FIX: On initial load, sort messages in ascending order (oldest first, newest last)
result.sort((a, b) {
final dateA =
DateTime.tryParse(a.createdAt ?? '') ?? DateTime(1970);
final dateB =
DateTime.tryParse(b.createdAt ?? '') ?? DateTime(1970);
return dateA.compareTo(dateB);
});
_messages.clear();
_messages.addAll(result);
} else {
// ⭐ FIX: On pagination load, insert older messages at beginning
// Make sure pagination result is sorted in ascending order
result.sort((a, b) {
final dateA =
DateTime.tryParse(a.createdAt ?? '') ?? DateTime(1970);
final dateB =
DateTime.tryParse(b.createdAt ?? '') ?? DateTime(1970);
return dateA.compareTo(dateB);
});
_messages.insertAll(0, result);
}
_currentPage++;
if (mounted) state = AsyncData(List.from(_messages));
}
if (_isInitialLoad && result.isNotEmpty) {
_isInitialLoad = false;
}
} catch (e, st) {
print("❌ Error loading messages: $e");
if (mounted) state = AsyncError(e, st);
} finally {
_isFetching = false;
}
}
/// ⭐ Refresh chat (pull to refresh or after sending message)
/// This is a SMOOTH refresh that doesn't show loading screen
Future<void> refresh() async {
print("🔄 Refreshing chat...");
try {
// ⭐ Get the latest messages from page 1
final result = await ref
.read(apiRepositoryProvider)
.fetchChatMessages(chatId: chatId, page: 1);
if (result.isNotEmpty) {
// Sort in ascending order
result.sort((a, b) {
final dateA = DateTime.tryParse(a.createdAt ?? '') ?? DateTime(1970);
final dateB = DateTime.tryParse(b.createdAt ?? '') ?? DateTime(1970);
return dateA.compareTo(dateB);
});
// ⭐ Check if we have any new messages
final lastMessageId = _messages.isNotEmpty ? _messages.last.id : -1;
final latestMessageId = result.last.id;
if (latestMessageId != lastMessageId) {
// ⭐ New messages found - merge them smoothly
print("✅ Found new messages, updating list");
// Add only new messages that don't exist in current list
for (var newMsg in result) {
final exists = _messages.any((m) => m.id == newMsg.id);
if (!exists) {
_messages.add(newMsg);
}
}
// Sort the entire list
_messages.sort((a, b) {
final dateA =
DateTime.tryParse(a.createdAt ?? '') ?? DateTime(1970);
final dateB =
DateTime.tryParse(b.createdAt ?? '') ?? DateTime(1970);
return dateA.compareTo(dateB);
});
if (mounted) state = AsyncData(List.from(_messages));
} else {
print("✅ No new messages");
}
}
} catch (e) {
print("❌ Error refreshing: $e");
// Don't throw error, just continue with existing messages
}
}
/// ⭐ Hard refresh (for pull to refresh gesture)
Future<void> hardRefresh() async {
print("🔄 Hard refreshing chat...");
_currentPage = 1;
_hasNextPage = true;
_messages.clear();
_isInitialLoad = true;
if (mounted) state = const AsyncLoading();
await loadMessages();
}
/// Add new message to list (socket or send API)
void addNewMessage(MessageModel msg) {
print(" Adding new message: ${msg.message} (ID: ${msg.id})");
// ⭐ DEDUPLICATION: Avoid adding the same message twice
// 1. Check if ID already exists
final existingIndex = _messages.indexWhere((m) => m.id == msg.id);
if (existingIndex != -1) {
print("♻️ Message ID ${msg.id} already exists, updating instead");
_messages[existingIndex] = msg;
if (mounted) state = AsyncData(List.from(_messages));
return;
}
// 2. Check if this is a server confirmation of an optimistic message
// Optimistic messages have large positive IDs from DateTime.now().millisecondsSinceEpoch
// OR content match for very recent messages
final optimisticIndex = _messages.indexWhere(
(m) =>
// Match by content and sender if it's a very recent "local" message
(m.id > 1000000000000 &&
m.message == msg.message &&
m.chatBy == msg.chatBy),
);
if (optimisticIndex != -1) {
print("🔄 Replacing optimistic message with server version");
_messages[optimisticIndex] = msg;
} else {
// ⭐ Add at the END (newest messages at bottom)
_messages.add(msg);
}
if (mounted) state = AsyncData(List.from(_messages));
}
/// Update existing message (e.g., delivery status, read status)
void updateMessage(MessageModel updatedMsg) {
final index = _messages.indexWhere((m) => m.id == updatedMsg.id);
if (index != -1) {
_messages[index] = updatedMsg;
if (mounted) state = AsyncData(List.from(_messages));
}
}
/// Delete a message
void deleteMessage(int messageId) {
_messages.removeWhere((m) => m.id == messageId);
if (mounted) state = AsyncData(List.from(_messages));
}
}
// ✅ Provider for fetching both country and state data
final countryAndStatesProvider = FutureProvider<CountryModel>((ref) async {
final repo = ref.read(apiRepositoryProvider);
try {
final model = await repo.fetchCountryAndStates(ConstsApi.conturystate);
ref.keepAlive(); // Keep cached until manually disposed
return model;
} catch (e) {
print('❌ Error in countryAndStatesProvider: $e');
throw e;
}
});
final fetchCityProvider = FutureProvider.family<CityModel, int>((
ref,
stateId,
) async {
final repo = ref.read(apiRepositoryProvider);
try {
final cityModel = await repo.fetchCityByState(ConstsApi.city, stateId);
ref.keepAlive();
return cityModel;
} catch (e) {
print('❌ Error in fetchCityProvider: $e');
throw e;
}
});