feat: Implement support chat functionality, add 2FA verification screen and routing, and update dependencies.

This commit is contained in:
pradeepkumar
2026-03-11 13:26:16 +05:30
parent 4780eb9a63
commit 6928697c5c
17 changed files with 1660 additions and 20 deletions

View File

@@ -0,0 +1,93 @@
class SupportChat {
final String id;
final String userId;
final String status; // OPEN, CLOSED
final String? lastMessageAt;
final String? lastMessageText;
final int userUnreadCount;
final int adminUnreadCount;
final String createdAt;
final String updatedAt;
const SupportChat({
required this.id,
required this.userId,
required this.status,
this.lastMessageAt,
this.lastMessageText,
required this.userUnreadCount,
required this.adminUnreadCount,
required this.createdAt,
required this.updatedAt,
});
factory SupportChat.fromJson(Map<String, dynamic> json) {
return SupportChat(
id: json['id'] as String,
userId: json['userId'] as String,
status: json['status'] as String? ?? 'OPEN',
lastMessageAt: json['lastMessageAt'] as String?,
lastMessageText: json['lastMessageText'] as String?,
userUnreadCount: json['userUnreadCount'] as int? ?? 0,
adminUnreadCount: json['adminUnreadCount'] as int? ?? 0,
createdAt: json['createdAt'] as String,
updatedAt: json['updatedAt'] as String,
);
}
bool get isClosed => status == 'CLOSED';
}
class SupportMessage {
final String id;
final String chatId;
final String senderId;
final String senderRole;
final String content;
final String createdAt;
const SupportMessage({
required this.id,
required this.chatId,
required this.senderId,
required this.senderRole,
required this.content,
required this.createdAt,
});
factory SupportMessage.fromJson(Map<String, dynamic> json) {
return SupportMessage(
id: json['id'] as String,
chatId: json['chatId'] as String,
senderId: json['senderId'] as String,
senderRole: json['senderRole'] as String? ?? 'USER',
content: json['content'] as String,
createdAt: json['createdAt'] as String,
);
}
}
class SupportPagination {
final int page;
final int limit;
final int total;
final int pages;
const SupportPagination({
required this.page,
required this.limit,
required this.total,
required this.pages,
});
factory SupportPagination.fromJson(Map<String, dynamic> json) {
return SupportPagination(
page: json['page'] as int? ?? 1,
limit: json['limit'] as int? ?? 50,
total: json['total'] as int? ?? 0,
pages: json['pages'] as int? ?? 1,
);
}
bool get hasMore => page < pages;
}

View File

@@ -0,0 +1,98 @@
import 'package:dio/dio.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:logger/logger.dart';
import 'package:real_estate_mobile/core/constants/api_constants.dart';
import 'package:real_estate_mobile/core/network/api_client.dart';
import 'package:real_estate_mobile/core/network/api_exceptions.dart';
import 'package:real_estate_mobile/features/support_chat/data/models/support_chat_models.dart';
final _log = Logger(printer: PrettyPrinter(methodCount: 0));
class SupportChatRepository {
final Dio _dio = ApiClient.instance.dio;
/// POST /support-chat — get or create a support chat
Future<SupportChat> getOrCreateChat() async {
try {
final response = await _dio.post(ApiConstants.supportChat);
return SupportChat.fromJson(
response.data['data'] as Map<String, dynamic>);
} on DioException catch (e) {
if (e.error is ApiException) throw e.error as ApiException;
throw ApiException(
message: e.message ?? 'Failed to create support chat',
statusCode: e.response?.statusCode,
);
}
}
/// GET /support-chat/my — get user's existing chat
Future<SupportChat> getMyChat() async {
try {
final response = await _dio.get('${ApiConstants.supportChat}/my');
return SupportChat.fromJson(
response.data['data'] as Map<String, dynamic>);
} on DioException catch (e) {
if (e.error is ApiException) throw e.error as ApiException;
throw ApiException(
message: e.message ?? 'Failed to fetch support chat',
statusCode: e.response?.statusCode,
);
}
}
/// GET /support-chat/:chatId/messages
Future<({List<SupportMessage> messages, SupportPagination pagination})>
getMessages(String chatId, {int page = 1, int limit = 50}) async {
try {
final response = await _dio.get(
'${ApiConstants.supportChat}/$chatId/messages',
queryParameters: {'page': page, 'limit': limit},
);
final data = response.data['data'] as Map<String, dynamic>;
final messages = (data['messages'] as List<dynamic>)
.map((e) => SupportMessage.fromJson(e as Map<String, dynamic>))
.toList();
final pagination = SupportPagination.fromJson(
data['pagination'] as Map<String, dynamic>);
return (messages: messages, pagination: pagination);
} on DioException catch (e) {
if (e.error is ApiException) throw e.error as ApiException;
throw ApiException(
message: e.message ?? 'Failed to fetch messages',
statusCode: e.response?.statusCode,
);
}
}
/// POST /support-chat/:chatId/messages
Future<SupportMessage> sendMessage(String chatId, String content) async {
try {
final response = await _dio.post(
'${ApiConstants.supportChat}/$chatId/messages',
data: {'content': content},
);
return SupportMessage.fromJson(
response.data['data'] as Map<String, dynamic>);
} on DioException catch (e) {
if (e.error is ApiException) throw e.error as ApiException;
throw ApiException(
message: e.message ?? 'Failed to send message',
statusCode: e.response?.statusCode,
);
}
}
/// PATCH /support-chat/:chatId/read
Future<void> markAsRead(String chatId) async {
try {
await _dio.patch('${ApiConstants.supportChat}/$chatId/read');
} on DioException catch (e) {
_log.w('Failed to mark support chat as read: ${e.message}');
}
}
}
final supportChatRepositoryProvider = Provider<SupportChatRepository>((ref) {
return SupportChatRepository();
});