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>>( (ref) => LoginNotifier(ref.read(apiRepositoryProvider)), ); class LoginNotifier extends StateNotifier>> { final ApiRepository _apiRepository; LoginNotifier(this._apiRepository) : super(const AsyncValue.data({})); Future 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 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>>( (ref) => SignupNotifier(ref.read(apiRepositoryProvider)), ); class SignupNotifier extends StateNotifier>> { final ApiRepository _apiRepository; SignupNotifier(this._apiRepository) : super(const AsyncValue.data({})); Future 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 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> >((ref) => EmployeeLoginNotifier(ref.read(apiRepositoryProvider))); class EmployeeLoginNotifier extends StateNotifier>> { final ApiRepository _apiRepository; EmployeeLoginNotifier(this._apiRepository) : super(const AsyncValue.data({})); Future 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 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((ref) async { final repo = ref.read(apiRepositoryProvider); return await repo.fetchTermsAndConditions(); }); final dashboardProvider = FutureProvider((ref) async { final repo = ref.read(apiRepositoryProvider); return await repo.fetchDashboard(); }); final policyProvider = FutureProvider((ref) async { final repo = ref.read(apiRepositoryProvider); return await repo.fetchpolicy(); }); final profileProvider = FutureProvider((ref) async { final repo = ref.read(apiRepositoryProvider); return await repo.fetchProfile(); }); final employeeProfileProvider = FutureProvider(( ref, ) async { final repo = ref.read(apiRepositoryProvider); return await repo.fetchEmployeeProfile(); }); final serviceHistoryNotifierProvider = StateNotifierProvider.family< ServiceHistoryNotifier, AsyncValue, String >((ref, type) { return ServiceHistoryNotifier(ref: ref, type: type); }); class ServiceHistoryNotifier extends StateNotifier> { final Ref ref; final String type; String? fromDate; String? toDate; ServiceHistoryNotifier({required this.ref, required this.type}) : super(const AsyncValue.loading()) { fetchServiceHistory(); } Future fetchServiceHistory({bool isSilent = false}) async { try { if (!mounted) return; // 🔄 Skip loading state for silent refresh if (!isSilent) { 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 refresh() async { await fetchServiceHistory(); } } final countProvider = FutureProvider.family(( ref, type, ) async { final repo = ref.read(apiRepositoryProvider); return await repo.fetchCount(type); }); final notificationTriggerProvider = StateProvider((ref) => 0); // 🔥 fetch notification count API final notificationCountProvider = FutureProvider.autoDispose((ref) async { final repo = ref.read(apiRepositoryProvider); return await repo.fetchNotificationCount(); }); final serviceDetailProvider = FutureProvider.family(( ref, id, ) async { final repo = ref.read(apiRepositoryProvider); return await repo.fetchServiceDetail(id); }); final chatDocumentProvider = FutureProvider.family(( ref, id, ) async { final repo = ref.read(apiRepositoryProvider); return await repo.fetchChatDocuments(id); }); final serviceListProvider = FutureProvider>((ref) async { final repo = ref.read(apiRepositoryProvider); return await repo.fetchServiceList(); }); final staffListProvider = FutureProvider>((ref) async { final repo = ref.read(apiRepositoryProvider); return await repo.fetchStaffList(); }); final notificationProvider = StateNotifierProvider.family .autoDispose< NotificationNotifier, AsyncValue>, int >((ref, page) { return NotificationNotifier(ref: ref, page: page); }); // ---------------- NOTIFIER -------------------- class NotificationNotifier extends StateNotifier>> { final Ref ref; final int page; NotificationNotifier({required this.ref, required this.page}) : super(const AsyncValue.loading()) { fetchNotifications(); } Future fetchNotifications() async { if (!mounted) return; try { state = const AsyncValue.loading(); final List 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>, int >((ref, chatId) { return ChatMessagesNotifier(ref: ref, chatId: chatId); }); class ChatMessagesNotifier extends StateNotifier>> { final Ref ref; final int chatId; int _currentPage = 1; bool _hasNextPage = true; bool _isFetching = false; bool _isInitialLoad = true; final List _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 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 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); }); // ⭐ Merge strategy: // 1. Update existing messages (read/delivered status) // 2. Add new messages bool hasChanges = false; for (var incomingMsg in result) { final existingIndex = _messages.indexWhere((m) => m.id == incomingMsg.id); if (existingIndex != -1) { // Check if status changed final existingMsg = _messages[existingIndex]; if (existingMsg.isRead != incomingMsg.isRead || existingMsg.isDelivered != incomingMsg.isDelivered) { print("♻️ Updating status for message ID ${incomingMsg.id}"); _messages[existingIndex] = incomingMsg; hasChanges = true; } } else { // ⭐ NEW: Check if this server message replaces an optimistic one final optimisticIndex = _messages.indexWhere( (m) => (m.id > 1000000000000 && m.message == incomingMsg.message && m.chatBy == incomingMsg.chatBy) ); if (optimisticIndex != -1) { print("🔄 refresh: Replacing optimistic message with ID ${incomingMsg.id}"); _messages[optimisticIndex] = incomingMsg; hasChanges = true; } else { // Truly new message (e.g. incoming from someone else) print("➕ Adding new message ID ${incomingMsg.id}"); _messages.add(incomingMsg); hasChanges = true; } } } if (hasChanges) { // Sort the entire list to be sure _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 or status changes"); } } } catch (e) { print("❌ Error refreshing: $e"); // Don't throw error, just continue with existing messages } } /// ⭐ Hard refresh (for pull to refresh gesture) Future 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 final optimisticIndex = _messages.indexWhere( (m) => (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); } // Sort to ensure order (important if multiple local messages are sent) _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)); } /// 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((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(( 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; } });