feat(plans): fixed plans price field in CreatePlanScreen, feat(menu): removed dev tools, feat(chats,notifications): added ws reconection

This commit is contained in:
Username1548
2025-06-03 23:19:15 +07:00
parent c95b610c35
commit da8d21bb08
9 changed files with 119 additions and 43 deletions

View File

@@ -101,7 +101,6 @@ class AppRouter {
path: '/my-profile-screen',
name: 'myProfileScreen',
redirect: (context, state) async {
await sl<WsManager>().initialize();
return null;
},
pageBuilder: (context, state) {
@@ -113,6 +112,10 @@ class AppRouter {
name: 'feedScreen',
pageBuilder: (context, state) => const NoTransitionPage(child: FeedScreen()),
redirect: (context, state) async {
final data = state.extra as Map<String, dynamic>?;
if (data?['needToInitializeWebSocket'] == false) {
return null;
}
await sl<WsManager>().initialize();
return null;
},

View File

@@ -1,5 +1,5 @@
String baseUrl = 'https://eatvio.ru';
// String baseUrl = 'https://dev2.eatvio.com';
// String baseUrl = 'https://eatvio.ru';
String baseUrl = 'https://dev2.eatvio.com';
const List<int> durationValues = [7, 14, 21];

View File

@@ -18,7 +18,7 @@ class WsManager {
PrivateChannel? _privateDirectChatChannel;
Stream<ChannelReadEvent>? get newMessagesStream => _privateMessagesChannel?.bind('App\\Events\\ChatMessageUpdated');
Stream<ChannelReadEvent>? get newNotificationsStream => _privateNotificationChannel?.bind('Illuminate\\Notifications\\Events\\BroadcastNotificationCreated');
Stream<ChannelReadEvent>? get directChatStream => _privateDirectChatChannel?.bind('App\\Events\\ChatMessageUpdated');
@@ -135,4 +135,16 @@ class WsManager {
void unsubscribeDirectChatChannel() {
_privateDirectChatChannel?.unsubscribe();
}
Future<void> clear() async{
await client?.disconnect();
client = null;
userId = null;
_privateDirectChatChannel?.unsubscribe();
_privateMessagesChannel?.unsubscribe();
_privateNotificationChannel?.unsubscribe();
_privateDirectChatChannel = null;
_privateMessagesChannel = null;
_privateNotificationChannel = null;
}
}

View File

@@ -1,12 +1,11 @@
import 'dart:developer';
import 'package:eatvio_app/core/app_router.dart';
import 'package:eatvio_app/core/token_storage.dart';
import 'package:eatvio_app/core/utils/toasts.dart';
import 'package:eatvio_app/features/auth/presentation/bloc/auth_state.dart';
import 'package:eatvio_app/features/auth/repositories/auth_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
class AuthCubit extends Cubit<AuthState> {
final AuthRepository authRepository;
@@ -28,9 +27,7 @@ class AuthCubit extends Cubit<AuthState> {
token: token,
),
);
showSuccessToast('Успешный вход');
await TokenStorage.saveTokenInStorage(token);
AppRouter.navigatorKey.currentContext?.go('/feed-screen');
await TokenStorage.saveTokenInStorage(token);
return true;
} catch (e) {
log(e.toString());

View File

@@ -5,10 +5,13 @@ import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/core/utils/image_constant.dart';
import 'package:eatvio_app/core/utils/toasts.dart';
import 'package:eatvio_app/core/websockets/ws_manager.dart';
import 'package:eatvio_app/core/widgets/custom_text_form_field.dart';
import 'package:eatvio_app/features/auth/presentation/bloc/auth_cubit.dart';
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart';
import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart';
import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_cubit.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/gestures.dart';
@@ -49,6 +52,20 @@ class _AuthScreenState extends State<AuthScreen> {
});
}
Future<void> _reconnectWebSocket(BuildContext context) async {
await sl<WsManager>().clear();
await sl<WsManager>().initialize();
if (context.mounted) {
context.read<ProfileAvatarCubit>().getMyProfile();
context.read<ChatsCubit>().getChatRoomsWithLoading();
context.read<ChatsCubit>().connectToWebSocket();
context.read<NotificationsCubit>().getNotifications();
context.read<NotificationsCubit>().connectToWebSocket();
showSuccessToast('Успешный вход');
GoRouter.of(context).go('/feed-screen', extra: {'needToInitializeWebSocket': false});
}
}
@override
Widget build(BuildContext context) {
final s = S.of(context);
@@ -179,8 +196,7 @@ class _AuthScreenState extends State<AuthScreen> {
);
if (isAuthenticated) {
if (context.mounted) {
context.read<ProfileAvatarCubit>().getMyProfile();
context.read<ChatsCubit>().getChatRoomsWithLoading();
_reconnectWebSocket(context);
}
}
},

View File

@@ -14,18 +14,14 @@ class ChatsCubit extends Cubit<ChatsState> {
final ChatRepository chatRepository;
final ProfileRepository profileRepository;
final WsManager wsManager;
late StreamSubscription? newIncomingMessagesSubscription;
late StreamSubscription? newLastMessageSubscription;
StreamSubscription? newIncomingMessagesSubscription;
StreamSubscription? newLastMessageSubscription;
ChatsCubit({
required this.chatRepository,
required this.wsManager,
required this.profileRepository,
}) : super(ChatsState()) {
newIncomingMessagesSubscription = wsManager.newMessagesStream?.listen((event) {
_handleNewMessageViaSocket(event.data);
});
}) : super(ChatsState()) {
newLastMessageSubscription = chatRepository.newUserMessagesStream.listen((message) {
_setNewLastMessage(message);
});
@@ -33,6 +29,13 @@ class ChatsCubit extends Cubit<ChatsState> {
String? _query;
int page = 1;
late int lastPage;
Future<void> connectToWebSocket() async {
await newIncomingMessagesSubscription?.cancel();
newIncomingMessagesSubscription = wsManager.newMessagesStream?.listen((event) {
_handleNewMessageViaSocket(event.data);
});
}
Future<void> getChatRoomsWithLoading({String? query}) async {
page = 1;
_query = query;

View File

@@ -21,21 +21,45 @@ class OptionWidget extends StatefulWidget {
}
class _OptionWidgetState extends State<OptionWidget> {
final customerPriceController = TextEditingController()..text = '1200₽';
final firstOptionCustomerPriceController = TextEditingController()..text = '1200₽';
final secondOptionCustomerPriceController = TextEditingController()..text = '1200₽';
final thirdOptionCustomerPriceController = TextEditingController()..text = '1200₽';
final creatorPriceController = TextEditingController()..text = '1000₽';
final optionTitleController = TextEditingController();
final optionDescriptionController = TextEditingController();
final firstOptionTitleController = TextEditingController();
final secondOptionTitleController = TextEditingController();
final thirdOptionTitleController = TextEditingController();
final firstOptionDescriptionController = TextEditingController();
final secondOptionDescriptionController = TextEditingController();
final thirdOptionDescriptionController = TextEditingController();
bool firstExplanationVisible = false;
bool isChecked = false;
bool secondExplanationVisible = false;
@override
void initState() {
super.initState();
customerPriceController.addListener(() {
if (!customerPriceController.text.endsWith('₽')) {
final text = customerPriceController.text.replaceAll('₽', '');
customerPriceController.text = '$text₽';
customerPriceController.selection = TextSelection.collapsed(offset: text.length);
firstOptionCustomerPriceController.addListener(() {
if (!firstOptionCustomerPriceController.text.endsWith('₽')) {
final text = firstOptionCustomerPriceController.text.replaceAll('₽', '');
firstOptionCustomerPriceController.text = '$text₽';
firstOptionCustomerPriceController.selection = TextSelection.collapsed(offset: text.length);
}
});
secondOptionCustomerPriceController.addListener(() {
if (!secondOptionCustomerPriceController.text.endsWith('₽')) {
final text = secondOptionCustomerPriceController.text.replaceAll('₽', '');
secondOptionCustomerPriceController.text = '$text₽';
secondOptionCustomerPriceController.selection = TextSelection.collapsed(offset: text.length);
}
});
thirdOptionCustomerPriceController.addListener(() {
if (!thirdOptionCustomerPriceController.text.endsWith('₽')) {
final text = thirdOptionCustomerPriceController.text.replaceAll('₽', '');
thirdOptionCustomerPriceController.text = '$text₽';
thirdOptionCustomerPriceController.selection = TextSelection.collapsed(offset: text.length);
}
});
}
@@ -45,15 +69,32 @@ class _OptionWidgetState extends State<OptionWidget> {
final s = S.of(context);
return BlocBuilder<CreatePlanCubit, CreatePlanState>(
builder: (context, state) {
final optionNameHintText = switch (state.selectedOptionIndex ?? 1) {
final selectedOptionIndex = state.selectedOptionIndex ?? 0;
final optionNameHintText = switch (selectedOptionIndex) {
0 => s.economy,
2 => s.comfort,
_ => s.standard,
};
final selectedOptionIndex = state.selectedOptionIndex ?? 0;
optionTitleController.text = state.planData.options[selectedOptionIndex].short;
optionDescriptionController.text = state.planData.options[selectedOptionIndex].description;
final optionTitleController = switch (selectedOptionIndex) {
0 => firstOptionTitleController,
2 => thirdOptionTitleController,
_ => secondOptionTitleController,
};
final optionDescriptionController = switch (selectedOptionIndex) {
0 => firstOptionDescriptionController,
2 => thirdOptionDescriptionController,
_ => secondOptionDescriptionController,
};
final customerPriceController = switch (selectedOptionIndex) {
0 => firstOptionCustomerPriceController,
2 => thirdOptionCustomerPriceController,
_ => secondOptionCustomerPriceController,
};
final price = double.tryParse(customerPriceController.text.replaceAll('₽', ''));
creatorPriceController.text = '${((price ?? 0) * 0.8).toStringAsFixed(2)}₽';
firstOptionTitleController.text = state.planData.options[selectedOptionIndex].short;
firstOptionDescriptionController.text = state.planData.options[selectedOptionIndex].description;
return MainBackgroundBouble(
padding: const EdgeInsets.all(16),
child: Column(
@@ -151,7 +192,6 @@ class _OptionWidgetState extends State<OptionWidget> {
hasError: state.validationState.optionsValidationState[state.selectedOptionIndex ?? 0].priceHasError,
onChanged: (value) {
final price = double.tryParse(value.replaceAll('₽', ''));
creatorPriceController.text = '${((price ?? 0) * 0.8).toStringAsFixed(2)}₽';
if (price != null) {
context.read<CreatePlanCubit>().setPrice(price);
}

View File

@@ -4,7 +4,6 @@ import 'package:eatvio_app/core/token_storage.dart';
import 'package:eatvio_app/core/utils/image_constant.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/dev_tools/presentation/dev_tools_screen.dart';
import 'package:eatvio_app/features/my_diets/presentation/my_diets_screen.dart';
import 'package:eatvio_app/features/my_plans/presentation/my_plans_screen.dart';
import 'package:eatvio_app/features/profile/models/profile_model.dart';
@@ -48,14 +47,14 @@ class MenuScreen extends StatelessWidget {
_tile(leading: ImageConstant.icnGlossary, title: 'Глоссарий', onTap: () => GoRouter.of(context).push('/glossary-screen')),
_divider(context),
_tile(leading: ImageConstant.icnSupport, title: 'Поддержка', onTap: () => GoRouter.of(context).push('/support-screen')),
_divider(context),
_tile(
leading: ImageConstant.icnDevTools,
title: 'Для разработчиков',
onTap: () {
GoRouter.of(context).pushNamed(DevToolsScreen.name);
},
),
// _divider(context),
// _tile(
// leading: ImageConstant.icnDevTools,
// title: 'Для разработчиков',
// onTap: () {
// GoRouter.of(context).pushNamed(DevToolsScreen.name);
// },
// ),
_divider(context),
_tile(leading: ImageConstant.icnExit, title: 'Выход', onTap: _onTapLogout(context)),
],

View File

@@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:convert';
import 'dart:developer';
@@ -11,14 +12,19 @@ class NotificationsCubit extends Cubit<NotificationsState> {
final List<BaseNotificationModel> _notifications = [];
final WsManager wsManager;
final NotificationsRepository notificationsRepository;
StreamSubscription? _newNotificationsSubscription;
NotificationsCubit({
required this.wsManager,
required this.notificationsRepository,
}) : super(const NotificationsState.empty()) {
wsManager.newNotificationsStream?.listen((event) {
}) : super(const NotificationsState.empty());
Future<void> connectToWebSocket() async {
await _newNotificationsSubscription?.cancel();
_newNotificationsSubscription = wsManager.newNotificationsStream?.listen((event) {
_handleNotification(event.data);
});
}
BaseNotificationModel _parseNotification(Map<String, dynamic> notificationJson, NotificationType type) {
try {
return switch (type) {