2026-04-11

This commit is contained in:
Sarankumar committed 2026-04-11 10:21:31 +05:30
commit 27e27fb70b
270 files changed
+30296

No files matched your search

+30
View File
@@ -0,0 +1,30 @@
class ConstsApi {
static const String baseUrl = "https://www.taxglide.amrithaa.net";
static const String login = "$baseUrl/api/otp/sms/request";
static const String verifyOtp = "$baseUrl/api/otp/sms/verify";
static const String signup = "$baseUrl/api/register";
static const String terms = "$baseUrl/api/get_terms_conditions";
static const String policy = "$baseUrl/api/get_privacy_policy";
static const String getprofile = "$baseUrl/api/get_profile";
static const String employeelogin = "$baseUrl/api/otp/employee/sms/request";
static const String serivcelist = "$baseUrl/api/get_services_list";
static const String conturystate = "$baseUrl/api/kyc_master";
static const String city = "$baseUrl/api/state_based_districts";
static const String kycupdate = "$baseUrl/api/profile_update";
static const String serivcerequest = "$baseUrl/api/service_register";
static const String serivcehistory = "$baseUrl/api/get_service_requests_list";
static const String serivcedetails =
"$baseUrl/api/get_service_request_detail";
static const String logout = "$baseUrl/api/logout";
static const String chatList = "$baseUrl/api/chat";
static const String sendChatMessage = "$baseUrl/api/chat/message/send";
static const String stafflist = "$baseUrl/api/get_employee_list";
static const String count = "$baseUrl/api/chat/un_read_message_count";
static const String chatdocument = "$baseUrl/api/chat/document_lists";
static const String notificationList = "$baseUrl/api/notification/lists";
static const String notificationcount =
"$baseUrl/api/notification/un_read_count";
static const String dashboard = "$baseUrl/api/get_dashboard_data";
static const String proformaaccept = "$baseUrl/api/proforma/accept";
}
+536
View File
@@ -0,0 +1,536 @@
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;
}
});
File diff suppressed because it is too large. Load diff