feat: Implement support chat functionality, add 2FA verification screen and routing, and update dependencies.
This commit is contained in:
@@ -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;
|
||||
}
|
||||
98
lib/features/support_chat/data/support_chat_repository.dart
Normal file
98
lib/features/support_chat/data/support_chat_repository.dart
Normal 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();
|
||||
});
|
||||
Reference in New Issue
Block a user