forked from eatvio/eatvio_app
feat(chat): WIP: Issue-1594
This commit is contained in:
@@ -373,10 +373,10 @@ class AppRouter {
|
||||
name: 'chatScreen',
|
||||
builder: (context, state) {
|
||||
final data = state.extra as Map<String, dynamic>;
|
||||
final chatId = data['chatId'] as String;
|
||||
final uuid = data['uuid'] as String;
|
||||
final sendInitialMessage = data['sendInitialMessage'] as bool?;
|
||||
return ChatScreen(
|
||||
chatId: chatId,
|
||||
uuid: uuid,
|
||||
sendInitialMessage: sendInitialMessage,
|
||||
);
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
String baseUrl = 'https://eatvio.ru';
|
||||
String baseUrl = 'https://mobile-stage.eatvio.ru';
|
||||
|
||||
const List<int> durationValues = [7, 14, 21];
|
||||
|
||||
|
||||
@@ -21,3 +21,25 @@ abstract class TokenStorage {
|
||||
|
||||
static Future<void> removeTokenInStorage() async => (await const FlutterSecureStorage().delete(key: 'token'));
|
||||
}
|
||||
|
||||
abstract class SocketIdStorage {
|
||||
static Future<bool> haveSocketIdInStorage() async {
|
||||
try {
|
||||
if (await const FlutterSecureStorage().read(key: 'socketId') == null) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static Future<String?> getSocketIdFromStorage() async => await const FlutterSecureStorage().read(key: 'socketId');
|
||||
|
||||
static Future<void> saveSocketIdInStorage(String socketId) async {
|
||||
await const FlutterSecureStorage().write(key: 'socketId', value: socketId);
|
||||
}
|
||||
|
||||
static Future<void> removeSocketIdInStorage() async => (await const FlutterSecureStorage().delete(key: 'socketId'));
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:dart_pusher_channels/dart_pusher_channels.dart';
|
||||
@@ -59,6 +60,24 @@ class WsManager {
|
||||
),
|
||||
);
|
||||
|
||||
String? extractSocketId(String? data) {
|
||||
if (data == null) return null;
|
||||
try {
|
||||
final decoded = jsonDecode(data);
|
||||
return decoded['socket_id'] as String?;
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
client?.eventStream.listen((event) async {
|
||||
if (event.name == 'pusher:connection_established') {
|
||||
final data = event.data;
|
||||
final socketId = extractSocketId(data);
|
||||
await SocketIdStorage.saveSocketIdInStorage(socketId!);
|
||||
}
|
||||
});
|
||||
|
||||
await client?.connect();
|
||||
|
||||
client = client;
|
||||
@@ -77,7 +96,7 @@ class WsManager {
|
||||
EasyDebounce.debounce(
|
||||
'searchChatRoom',
|
||||
const Duration(milliseconds: 300),
|
||||
() => sl<ChatsCubit>().getChatRooms(),
|
||||
() => sl<ChatsCubit>().getChatRooms(),
|
||||
);
|
||||
log(event.rootObject.toString());
|
||||
});
|
||||
|
||||
@@ -9,7 +9,7 @@ part 'chat_room_model.g.dart';
|
||||
@freezed
|
||||
abstract class ChatRoomModel with _$ChatRoomModel {
|
||||
const factory ChatRoomModel({
|
||||
required String id,
|
||||
required String id,
|
||||
@JsonKey(name: 'chat_room_setting') required ChatRoomSettingsModel chatRoomSettings,
|
||||
required Users users,
|
||||
@JsonKey(name: 'chosen_plan_id') required int? chosenPlanId,
|
||||
|
||||
@@ -8,29 +8,17 @@ part 'chat_room_pagination_model.g.dart';
|
||||
@freezed
|
||||
abstract class ChatRoomPaginationModel with _$ChatRoomPaginationModel {
|
||||
const factory ChatRoomPaginationModel({
|
||||
@JsonKey(name: "chatRooms") required ChatRooms chatRooms,
|
||||
@JsonKey(name: "data") required List<ChatRoom> data,
|
||||
@JsonKey(name: "number_of_unread_chat_rooms") required int unreadChatsCount,
|
||||
}) = _ChatRoomPreviewModel;
|
||||
|
||||
factory ChatRoomPaginationModel.fromJson(Map<String, dynamic> json) => _$ChatRoomPaginationModelFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
abstract class ChatRooms with _$ChatRooms {
|
||||
const factory ChatRooms({
|
||||
@JsonKey(name: "data") required List<ChatRoom> data,
|
||||
@JsonKey(name: "last_page") required int lastPage,
|
||||
@JsonKey(name: "current_page") required int currentPage,
|
||||
}) = _ChatRooms;
|
||||
|
||||
factory ChatRooms.fromJson(Map<String, dynamic> json) => _$ChatRoomsFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
abstract class ChatRoom with _$ChatRoom {
|
||||
const factory ChatRoom({
|
||||
@JsonKey(name: "id") required String id,
|
||||
@JsonKey(name: "room_name") required String roomName,
|
||||
@JsonKey(name: "last_message", fromJson: parseLastMessage) required ChatMessageModel? lastMessage,
|
||||
@JsonKey(name: "users") required Users users,
|
||||
@JsonKey(name: "unread_messages_count") required int unreadMessagesCount,
|
||||
|
||||
@@ -6,11 +6,8 @@ part 'chat_room_settings_model.g.dart';
|
||||
@freezed
|
||||
abstract class ChatRoomSettingsModel with _$ChatRoomSettingsModel {
|
||||
const factory ChatRoomSettingsModel({
|
||||
required int id,
|
||||
@JsonKey(name: 'room_name') required String roomName,
|
||||
required String name,
|
||||
required String state,
|
||||
@JsonKey(name: 'access_level') required String accessLevel,
|
||||
@JsonKey(name: 'updated_at') required String updatedAt,
|
||||
}) = _ChatRoomSettingsModel;
|
||||
|
||||
factory ChatRoomSettingsModel.fromJson(Map<String, Object?> json) => _$ChatRoomSettingsModelFromJson(json);
|
||||
|
||||
@@ -47,14 +47,14 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
||||
_timer?.cancel();
|
||||
}
|
||||
|
||||
Future<void> loadNextPage(String chatId) async {
|
||||
Future<void> loadNextPage(String uuid) async {
|
||||
if (currentPage < lastPage && !isLoading) {
|
||||
isLoading = true;
|
||||
|
||||
emit((state as ChatRoomStateLoaded).copyWith(isLoading: isLoading));
|
||||
|
||||
try {
|
||||
final chatMessages = await chatRepository.getChatMessages(chatId, currentPage + 1);
|
||||
final chatMessages = await chatRepository.getChatMessages(uuid, currentPage + 1);
|
||||
|
||||
isLoading = false;
|
||||
emit(
|
||||
@@ -89,17 +89,17 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> initChat(String chatId, {bool sendInitialMessage = false}) async {
|
||||
Future<void> initChat(String uuid, {bool sendInitialMessage = false}) async {
|
||||
try {
|
||||
final chatRoom = await chatRepository.getChatRoom(chatId);
|
||||
final chatRoom = await chatRepository.getChatRoom(uuid);
|
||||
ChosenPlanModel? chosenPlan;
|
||||
if (chatRoom.chosenPlanId != null) {
|
||||
chosenPlan = await chatRepository.getChosenPlan(id: chatRoom.chosenPlanId!);
|
||||
}
|
||||
final chatMessages = await chatRepository.getChatMessages(chatId, currentPage);
|
||||
final chatMessages = await chatRepository.getChatMessages(uuid, currentPage);
|
||||
lastPage = chatMessages.meta.lastPage;
|
||||
|
||||
await _subscribeToChatChannel(chatId: chatId);
|
||||
await _subscribeToChatChannel(uuid: uuid);
|
||||
emit(
|
||||
ChatRoomState.loaded(
|
||||
chatRoom: chatRoom.copyWith(chosenPlan: chosenPlan),
|
||||
@@ -158,10 +158,10 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _subscribeToChatChannel({required String chatId}) async {
|
||||
Future<void> _subscribeToChatChannel({required String uuid}) async {
|
||||
final client = wsManager.client;
|
||||
if (client == null) return;
|
||||
await wsManager.initPrivateDirectChatChannel(chatId);
|
||||
await wsManager.initPrivateDirectChatChannel(uuid);
|
||||
wsManager.subscribeDirectChatChannel();
|
||||
messagesSubscription = wsManager.directChatStream?.listen((event) {
|
||||
AudioPlayer().play((AssetSource('audio/recive_message.wav')));
|
||||
|
||||
@@ -43,10 +43,10 @@ class ChatsCubit extends Cubit<ChatsState> {
|
||||
try {
|
||||
final chatRoomsPaginationModel = await chatRepository.getChatRooms(query: query);
|
||||
final unreadChats = chatRoomsPaginationModel.unreadChatsCount;
|
||||
lastPage = chatRoomsPaginationModel.chatRooms.lastPage + 1;
|
||||
// lastPage = chatRoomsPaginationModel.chatRooms.lastPage + 1;
|
||||
emit(
|
||||
state.copyWith(
|
||||
chatRooms: chatRoomsPaginationModel.chatRooms.data,
|
||||
chatRooms: chatRoomsPaginationModel.data,
|
||||
chatsWithUnreadMessagesCount: unreadChats,
|
||||
state: BaseState.success,
|
||||
),
|
||||
@@ -65,10 +65,10 @@ class ChatsCubit extends Cubit<ChatsState> {
|
||||
try {
|
||||
final chatRoomsPaginationModel = await chatRepository.getChatRooms(query: query);
|
||||
final unreadChats = chatRoomsPaginationModel.unreadChatsCount;
|
||||
lastPage = chatRoomsPaginationModel.chatRooms.lastPage + 1;
|
||||
// lastPage = chatRoomsPaginationModel.chatRooms.lastPage + 1;
|
||||
emit(
|
||||
state.copyWith(
|
||||
chatRooms: chatRoomsPaginationModel.chatRooms.data,
|
||||
chatRooms: chatRoomsPaginationModel.data,
|
||||
chatsWithUnreadMessagesCount: unreadChats,
|
||||
state: BaseState.success,
|
||||
),
|
||||
@@ -90,7 +90,7 @@ class ChatsCubit extends Cubit<ChatsState> {
|
||||
final unreadChats = chatRoomsPaginationModel.unreadChatsCount;
|
||||
emit(
|
||||
state.copyWith(
|
||||
chatRooms: [...state.chatRooms, ...chatRoomsPaginationModel.chatRooms.data],
|
||||
chatRooms: [...state.chatRooms, ...chatRoomsPaginationModel.data],
|
||||
chatsWithUnreadMessagesCount: unreadChats,
|
||||
state: BaseState.success,
|
||||
isLoadingMoreChats: false,
|
||||
|
||||
@@ -32,10 +32,10 @@ import 'package:intl/intl.dart';
|
||||
import 'package:visibility_detector/visibility_detector.dart';
|
||||
|
||||
class ChatScreen extends StatefulWidget {
|
||||
final String chatId;
|
||||
final String uuid;
|
||||
final bool? sendInitialMessage;
|
||||
|
||||
const ChatScreen({required this.chatId, super.key, required this.sendInitialMessage});
|
||||
const ChatScreen({required this.uuid, super.key, required this.sendInitialMessage});
|
||||
|
||||
@override
|
||||
State<ChatScreen> createState() => _ChatScreenState();
|
||||
@@ -64,14 +64,14 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
scrollController.addListener(_onScroll);
|
||||
chatRoomCubit.initChat(widget.chatId, sendInitialMessage: widget.sendInitialMessage ?? false);
|
||||
chatRoomCubit.initChat(widget.uuid, sendInitialMessage: widget.sendInitialMessage ?? false);
|
||||
chatRoomCubit.setTimer();
|
||||
_listener = AppLifecycleListener(onHide: _onHide, onResume: _onResume);
|
||||
}
|
||||
|
||||
void _onScroll() {
|
||||
if (scrollController.offset >= scrollController.position.maxScrollExtent && !scrollController.position.outOfRange) {
|
||||
chatRoomCubit.loadNextPage(widget.chatId);
|
||||
chatRoomCubit.loadNextPage(widget.uuid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
if (state is ChatRoomStateLoaded) {
|
||||
return _buildChat(context, state);
|
||||
} else if (state is ChatRoomStateError) {
|
||||
return Scaffold(appBar: AppBar(), body: DataErrorWidget(onReload: () => chatRoomCubit.initChat(widget.chatId)));
|
||||
return Scaffold(appBar: AppBar(), body: DataErrorWidget(onReload: () => chatRoomCubit.initChat(widget.uuid)));
|
||||
} else {
|
||||
return const Scaffold(body: Loader());
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ class _EmptyChatScreenState extends State<EmptyChatScreen> {
|
||||
AppRouter.navigatorKey.currentContext?.pushReplacementNamed(
|
||||
'chatScreen',
|
||||
extra: {
|
||||
'chatId': chatIdOrError,
|
||||
'uuid': chatIdOrError,
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -186,7 +186,7 @@ class _EmptyChatScreenState extends State<EmptyChatScreen> {
|
||||
AppRouter.navigatorKey.currentContext?.pushReplacementNamed(
|
||||
'chatScreen',
|
||||
extra: {
|
||||
'chatId': chatIdOrError,
|
||||
'uuid': chatIdOrError,
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -214,7 +214,7 @@ class _EmptyChatScreenState extends State<EmptyChatScreen> {
|
||||
AppRouter.navigatorKey.currentContext?.pushReplacementNamed(
|
||||
'chatScreen',
|
||||
extra: {
|
||||
'chatId': chatIdOrError,
|
||||
'uuid': chatIdOrError,
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -264,7 +264,7 @@ class _EmptyChatScreenState extends State<EmptyChatScreen> {
|
||||
AppRouter.navigatorKey.currentContext?.pushReplacementNamed(
|
||||
'chatScreen',
|
||||
extra: {
|
||||
'chatId': chatIdOrError,
|
||||
'uuid': chatIdOrError,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ class _ChatEntryWidgetState extends State<ChatEntryWidget> {
|
||||
onTap: () => GoRouter.of(context).pushNamed(
|
||||
'chatScreen',
|
||||
extra: {
|
||||
'chatId': widget.chatRoom.id.toString(),
|
||||
'uuid': widget.chatRoom.id.toString(),
|
||||
},
|
||||
),
|
||||
child: Container(
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'dart:async';
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:eatvio_app/core/token_storage.dart';
|
||||
import 'package:eatvio_app/features/chat/models/chat_message_model.dart';
|
||||
import 'package:eatvio_app/features/chat/models/chat_messages_model.dart';
|
||||
import 'package:eatvio_app/features/chat/models/chat_room_model.dart';
|
||||
@@ -47,10 +48,11 @@ class ChatRepository {
|
||||
}
|
||||
}
|
||||
|
||||
await chatService.sendMessageByUserSlug(
|
||||
userSlug: slug,
|
||||
await chatService.sendMessage(
|
||||
slug: slug,
|
||||
data: data,
|
||||
mediaData: multipartFiles.isEmpty ? null : multipartFiles,
|
||||
socketId: (await SocketIdStorage.getSocketIdFromStorage())!,
|
||||
);
|
||||
} catch (e, st) {
|
||||
log(e.toString(), stackTrace: st);
|
||||
@@ -58,9 +60,9 @@ class ChatRepository {
|
||||
}
|
||||
}
|
||||
|
||||
Future<ChatRoomModel> getChatRoom(String chatId) async {
|
||||
Future<ChatRoomModel> getChatRoom(String uuid) async {
|
||||
try {
|
||||
return (await chatService.getChatRoom(id: chatId))['chatRoom']!;
|
||||
return (await chatService.getChatRoom(uuid: uuid));
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
@@ -74,9 +76,9 @@ class ChatRepository {
|
||||
}
|
||||
}
|
||||
|
||||
Future<ChatMessagesModel> getChatMessages(String chatId, int page) async {
|
||||
Future<ChatMessagesModel> getChatMessages(String id, int page) async {
|
||||
try {
|
||||
return (await chatService.getChatMessages(id: chatId, page: page));
|
||||
return (await chatService.getChatMessages(id: id, page: page));
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
@@ -90,9 +92,9 @@ class ChatRepository {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> markMessagesAsRead(String chatId, List<int> messageIds) async {
|
||||
Future<void> markMessagesAsRead(String id, List<int> messageIds) async {
|
||||
try {
|
||||
await chatService.markMessagesAsRead(id: chatId, body: {'messages_ids': messageIds});
|
||||
await chatService.markMessagesAsRead(id: id, body: {'messages_ids': messageIds});
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
|
||||
@@ -19,15 +19,16 @@ abstract class ChatService {
|
||||
@Query('find') String? query,
|
||||
});
|
||||
|
||||
@GET('/api/chat/show/{id}')
|
||||
Future<Map<String, ChatRoomModel>> getChatRoom({
|
||||
@Path() required String id,
|
||||
@GET('/api/chat/show/{uuid}')
|
||||
Future<ChatRoomModel> getChatRoom({
|
||||
@Path() required String uuid,
|
||||
});
|
||||
|
||||
@POST('/api/v2/chat/message/send/{id}')
|
||||
@POST('/api/chat/message/send/{slug}')
|
||||
@MultiPart()
|
||||
Future<void> sendMessage({
|
||||
@Path() required String id,
|
||||
@Header('X-Socket-ID') required String socketId,
|
||||
@Path() required String slug,
|
||||
@Part() String? data,
|
||||
@Part(name: 'media_data[]', contentType: 'multipart/form-data') List<MultipartFile>? mediaData,
|
||||
});
|
||||
@@ -85,12 +86,4 @@ abstract class ChatService {
|
||||
Future<HttpResponse> getChatByUserSlug({
|
||||
@Path() required String slug,
|
||||
});
|
||||
|
||||
@POST('/api/v2/chat/message/send/{userSlug}')
|
||||
@MultiPart()
|
||||
Future<void> sendMessageByUserSlug({
|
||||
@Path() required String userSlug,
|
||||
@Part() String? data,
|
||||
@Part(name: 'media_data[]', contentType: 'multipart/form-data') List<MultipartFile>? mediaData,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -208,7 +208,7 @@ class _AboutPlanScreenState extends State<AboutPlanScreen> {
|
||||
GoRouter.of(context).pushReplacementNamed(
|
||||
'chatScreen',
|
||||
extra: {
|
||||
'chatId': chosenPlan.chatId,
|
||||
'uuid': chosenPlan.chatId,
|
||||
'sendInitialMessage': true,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -123,7 +123,7 @@ class ProfileCubit extends Cubit<ProfileState> {
|
||||
AppRouter.navigatorKey.currentContext?.pushNamed(
|
||||
'chatScreen',
|
||||
extra: {
|
||||
'chatId': chatIdOrError,
|
||||
'uuid': chatIdOrError,
|
||||
},
|
||||
);
|
||||
return chatIdOrError;
|
||||
|
||||
@@ -69,7 +69,7 @@ class _SubActionButtonWidgetState extends State<SubActionButtonWidget> {
|
||||
GoRouter.of(context).pushNamed(
|
||||
'chatScreen',
|
||||
extra: {
|
||||
'chatId': await sl<ProfileCubit>().getChatRoomIdBySlug(
|
||||
'uuid': await sl<ProfileCubit>().getChatRoomIdBySlug(
|
||||
author: Author(
|
||||
authorSlug: widget.slug,
|
||||
authorName: widget.name,
|
||||
|
||||
Reference in New Issue
Block a user