forked from eatvio/eatvio_app
feat(session): centralize session teardown and refresh
Add SessionLifecycleService to reset user-scoped cubits, analytics, socket id and account-scoped preferences on logout, 401 or account deletion. Run the same refresh after every successful auth path, including registration v1/v2, and ignore 401 responses from stale tokens so an old request cannot terminate the new user's session. Refs: #1113
This commit is contained in:
@@ -97,6 +97,7 @@ class CubitRegistrations {
|
||||
systemPipCoordinator: sl(),
|
||||
),
|
||||
);
|
||||
sl.registerLazySingleton<FeedCubit>(() => FeedCubit(feedRepository: sl()));
|
||||
}
|
||||
|
||||
/// Регистрация Cubit'ов как фабрик
|
||||
@@ -108,10 +109,15 @@ class CubitRegistrations {
|
||||
firebaseApi: sl(),
|
||||
prefs: sl(),
|
||||
profileRepository: sl(),
|
||||
sessionLifecycleService: sl(),
|
||||
),
|
||||
);
|
||||
sl.registerFactory<RegistrationCubit>(
|
||||
() => RegistrationCubit(registrationRepository: sl(), prefs: sl()),
|
||||
() => RegistrationCubit(
|
||||
registrationRepository: sl(),
|
||||
prefs: sl(),
|
||||
sessionLifecycleService: sl(),
|
||||
),
|
||||
);
|
||||
sl.registerFactory<ResetPasswordCubit>(
|
||||
() => ResetPasswordCubit(resetPasswordRepository: sl()),
|
||||
@@ -120,14 +126,21 @@ class CubitRegistrations {
|
||||
() => VerifyEmailCubit(profileRepository: sl(), prefs: sl()),
|
||||
);
|
||||
sl.registerFactory<OAuthCubit>(
|
||||
() => OAuthCubit(authRepository: sl(), firebaseApi: sl(), prefs: sl()),
|
||||
() => OAuthCubit(
|
||||
authRepository: sl(),
|
||||
firebaseApi: sl(),
|
||||
prefs: sl(),
|
||||
sessionLifecycleService: sl(),
|
||||
),
|
||||
);
|
||||
sl.registerFactory<RegistrationCubitV2>(
|
||||
() =>
|
||||
RegistrationCubitV2(registrationRepository: sl(), firebaseApi: sl()),
|
||||
() => RegistrationCubitV2(
|
||||
registrationRepository: sl(),
|
||||
firebaseApi: sl(),
|
||||
sessionLifecycleService: sl(),
|
||||
),
|
||||
);
|
||||
// Feed related cubits
|
||||
sl.registerFactory<FeedCubit>(() => FeedCubit(feedRepository: sl()));
|
||||
sl.registerFactory<RecipesCubit>(
|
||||
() => RecipesCubit(
|
||||
feedRepository: sl(),
|
||||
@@ -242,6 +255,7 @@ class CubitRegistrations {
|
||||
settingsRepository: sl(),
|
||||
consultantRepository: sl(),
|
||||
consultationsRepository: sl(),
|
||||
sessionLifecycleService: sl(),
|
||||
),
|
||||
);
|
||||
sl.registerFactory<RegisterConsultantCubit>(
|
||||
@@ -297,7 +311,7 @@ class CubitRegistrations {
|
||||
sl.registerFactory<UserDetailsCubit>(
|
||||
() => UserDetailsCubit(profileRepository: sl()),
|
||||
);
|
||||
sl.registerFactory<MenuCubit>(() => MenuCubit(sl()));
|
||||
sl.registerFactory<MenuCubit>(() => MenuCubit(sl(), sl()));
|
||||
sl.registerFactory<MarketingCubit>(() => MarketingCubit(sl()));
|
||||
sl.registerFactory<PersonalDietSuggestionCubit>(
|
||||
() => PersonalDietSuggestionCubit(
|
||||
|
||||
15
lib/core/di/session_registrations.dart
Normal file
15
lib/core/di/session_registrations.dart
Normal file
@@ -0,0 +1,15 @@
|
||||
import 'package:eatvio_app/core/error_reporting/error_reporter.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/session/session_lifecycle_service.dart';
|
||||
import 'package:eatvio_app/core/websockets/ws_manager.dart';
|
||||
|
||||
class SessionRegistrations {
|
||||
static void registerAll() {
|
||||
sl.registerLazySingleton<SessionLifecycleService>(
|
||||
() => SessionLifecycleService(
|
||||
wsManager: sl<WsManager>(),
|
||||
errorReporter: sl<ErrorReporter>(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,7 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:eatvio_app/core/app_router.dart';
|
||||
import 'package:eatvio_app/core/session/session_lifecycle_service.dart';
|
||||
import 'package:eatvio_app/core/token_storage.dart';
|
||||
import 'package:eatvio_app/core/websockets/ws_manager.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class TokenInterceptor extends Interceptor {
|
||||
@override
|
||||
@@ -14,6 +12,7 @@ class TokenInterceptor extends Interceptor {
|
||||
final token = await TokenStorage.getTokenFromStorage();
|
||||
if (token != null && token.isNotEmpty) {
|
||||
options.headers['Authorization'] = 'Bearer $token';
|
||||
options.extra['authToken'] = token;
|
||||
}
|
||||
|
||||
super.onRequest(options, handler);
|
||||
@@ -25,9 +24,18 @@ class TokenInterceptor extends Interceptor {
|
||||
ErrorInterceptorHandler handler,
|
||||
) async {
|
||||
if (err.response?.statusCode == 401) {
|
||||
await sl<WsManager>().clear();
|
||||
await TokenStorage.removeTokenInStorage();
|
||||
AppRouter.navigatorKey.currentContext?.go('/auth-screen');
|
||||
final requestCarriedAuth = err.requestOptions.headers.containsKey(
|
||||
'Authorization',
|
||||
);
|
||||
final requestToken = err.requestOptions.extra['authToken'];
|
||||
final currentToken = await TokenStorage.getTokenFromStorage();
|
||||
if (requestCarriedAuth &&
|
||||
requestToken != null &&
|
||||
requestToken == currentToken) {
|
||||
await sl<SessionLifecycleService>().endSession(
|
||||
reason: SessionEndReason.unauthorized,
|
||||
);
|
||||
}
|
||||
}
|
||||
super.onError(err, handler);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:eatvio_app/core/di/cubit_registrations.dart';
|
||||
import 'package:eatvio_app/core/di/external_registrations.dart';
|
||||
import 'package:eatvio_app/core/di/repository_registrations.dart';
|
||||
import 'package:eatvio_app/core/di/service_registrations.dart';
|
||||
import 'package:eatvio_app/core/di/session_registrations.dart';
|
||||
import 'package:eatvio_app/core/prefs.dart';
|
||||
import 'package:eatvio_app/core/websockets/ws_manager.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
@@ -38,4 +39,6 @@ Future<void> init({required AppBuildType buildType}) async {
|
||||
sl.registerLazySingleton<WsManager>(
|
||||
() => WsManager(wsService: sl(), errorReporter: sl()),
|
||||
);
|
||||
|
||||
SessionRegistrations.registerAll();
|
||||
}
|
||||
|
||||
122
lib/core/session/session_lifecycle_service.dart
Normal file
122
lib/core/session/session_lifecycle_service.dart
Normal file
@@ -0,0 +1,122 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:appmetrica_plugin/appmetrica_plugin.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_router.dart';
|
||||
import 'package:eatvio_app/core/error_reporting/error_reporter.dart';
|
||||
import 'package:eatvio_app/core/firebase_api.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/prefs.dart';
|
||||
import 'package:eatvio_app/core/prefs_keys.dart';
|
||||
import 'package:eatvio_app/core/routing/route_constants.dart';
|
||||
import 'package:eatvio_app/core/token_storage.dart';
|
||||
import 'package:eatvio_app/core/websockets/ws_manager.dart';
|
||||
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/new_messages_cubit/new_messages_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/feed/presentation/bloc/feed_cubit/feed_cubit.dart';
|
||||
import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_cubit.dart';
|
||||
import 'package:eatvio_app/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart';
|
||||
import 'package:eatvio_app/features/video_calls/presentation/bloc/video_call_session/video_call_session_cubit.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
enum SessionEndReason { logout, unauthorized, accountDeleted }
|
||||
|
||||
class SessionLifecycleService {
|
||||
SessionLifecycleService({
|
||||
required WsManager wsManager,
|
||||
required ErrorReporter errorReporter,
|
||||
}) : _wsManager = wsManager,
|
||||
_errorReporter = errorReporter;
|
||||
|
||||
final WsManager _wsManager;
|
||||
final ErrorReporter _errorReporter;
|
||||
|
||||
int? _authenticatedFingerprint;
|
||||
bool _endingSession = false;
|
||||
|
||||
Future<void> endSession({required SessionEndReason reason}) async {
|
||||
if (_endingSession) return;
|
||||
_endingSession = true;
|
||||
try {
|
||||
await _run(() => _wsManager.clear());
|
||||
await _run(TokenStorage.removeTokenInStorage);
|
||||
await _run(SocketIdStorage.removeSocketIdInStorage);
|
||||
await _run(() => sl<ProfileAvatarCubit>().reset());
|
||||
await _run(() => sl<ChatsCubit>().reset());
|
||||
await _run(() => sl<NotificationsCubit>().reset());
|
||||
await _run(() => sl<NewMessagesCubit>().reset());
|
||||
await _run(() => sl<FeedCubit>().reset());
|
||||
await _run(() async => ProfileCubit.clearMyProfileReference());
|
||||
await _run(() => sl<VideoCallSessionCubit>().endCall());
|
||||
await _run(() async => AppMetrica.setUserProfileID(null));
|
||||
await _run(
|
||||
() => sl<Prefs>().remove(PrefsKeys.isEmailVerificationSkipped),
|
||||
);
|
||||
await _run(() => sl<Prefs>().remove(PrefsKeys.registrationDate));
|
||||
_authenticatedFingerprint = null;
|
||||
await _run(() async {
|
||||
final context = AppRouter.navigatorKey.currentContext;
|
||||
if (context == null) return;
|
||||
final path =
|
||||
AppRouter.appRouter.routeInformationProvider.value.uri.path;
|
||||
if (path != RouteConstants.authScreen) {
|
||||
context.go(RouteConstants.authScreen);
|
||||
}
|
||||
});
|
||||
} finally {
|
||||
_endingSession = false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> onAuthenticated() async {
|
||||
String? token;
|
||||
try {
|
||||
token = await TokenStorage.getTokenFromStorage();
|
||||
} catch (e, st) {
|
||||
await _report(e, st);
|
||||
return;
|
||||
}
|
||||
if (token == null || token.isEmpty) return;
|
||||
final fingerprint = token.hashCode;
|
||||
if (_authenticatedFingerprint == fingerprint) return;
|
||||
_authenticatedFingerprint = fingerprint;
|
||||
|
||||
await _run(() async {
|
||||
await _wsManager.clear();
|
||||
await _wsManager.initialize();
|
||||
});
|
||||
await _run(() => sl<ProfileAvatarCubit>().getMyProfile());
|
||||
await _run(() => sl<ChatsCubit>().getChatRoomsWithLoading());
|
||||
await _run(() => sl<NotificationsCubit>().getNotifications());
|
||||
await _run(() => sl<FirebaseApi>().registerPushToken());
|
||||
}
|
||||
|
||||
Future<void> logout() async {
|
||||
try {
|
||||
await _run(() => sl<FirebaseApi>().unregisterPushTokens());
|
||||
await _run(
|
||||
() => sl<Dio>().post('${sl<AppConfig>().baseUrl}/api/profile/logout'),
|
||||
);
|
||||
} finally {
|
||||
await endSession(reason: SessionEndReason.logout);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _run(FutureOr<void> Function() action) async {
|
||||
try {
|
||||
await action();
|
||||
} catch (e, st) {
|
||||
await _report(e, st);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _report(Object error, StackTrace stackTrace) {
|
||||
return _errorReporter.report(
|
||||
error,
|
||||
stackTrace,
|
||||
origin: 'SessionLifecycleService',
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import 'dart:developer';
|
||||
import 'package:eatvio_app/core/firebase_api.dart';
|
||||
import 'package:eatvio_app/core/prefs.dart';
|
||||
import 'package:eatvio_app/core/prefs_keys.dart';
|
||||
import 'package:eatvio_app/core/session/session_lifecycle_service.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_cubit/auth_state.dart';
|
||||
@@ -17,15 +18,26 @@ class AuthCubit extends Cubit<AuthState> {
|
||||
final ProfileRepository profileRepository;
|
||||
final FirebaseApi firebaseApi;
|
||||
final Prefs prefs;
|
||||
final SessionLifecycleService sessionLifecycleService;
|
||||
|
||||
AuthCubit({required this.authRepository, required this.prefs, required this.profileRepository, required this.firebaseApi}) : super(const AuthState());
|
||||
AuthCubit({
|
||||
required this.authRepository,
|
||||
required this.prefs,
|
||||
required this.profileRepository,
|
||||
required this.firebaseApi,
|
||||
required this.sessionLifecycleService,
|
||||
}) : super(const AuthState());
|
||||
|
||||
Future<bool> getTokenByCredentials(String email, String password) async {
|
||||
emit(const AuthState.loading());
|
||||
try {
|
||||
final token = (await authRepository.getTokenByCredentials(email, password)).token;
|
||||
final token = (await authRepository.getTokenByCredentials(
|
||||
email,
|
||||
password,
|
||||
)).token;
|
||||
emit(AuthState.loaded(token: token));
|
||||
await TokenStorage.saveTokenInStorage(token);
|
||||
await sessionLifecycleService.onAuthenticated();
|
||||
return true;
|
||||
} catch (e, st) {
|
||||
await reportFrom(e, st, this);
|
||||
@@ -48,7 +60,8 @@ class AuthCubit extends Cubit<AuthState> {
|
||||
|
||||
Future<void> registerAnonymousPushToken() async {
|
||||
try {
|
||||
final hasAnonymousTokenBeenSent = prefs.getBool(PrefsKeys.hasAnonymousTokenBeenSent) ?? false;
|
||||
final hasAnonymousTokenBeenSent =
|
||||
prefs.getBool(PrefsKeys.hasAnonymousTokenBeenSent) ?? false;
|
||||
if (!hasAnonymousTokenBeenSent) {
|
||||
await firebaseApi.registerAnonymousPushToken();
|
||||
prefs.setBool(PrefsKeys.hasAnonymousTokenBeenSent, true);
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:dio/dio.dart';
|
||||
import 'package:eatvio_app/core/firebase_api.dart';
|
||||
import 'package:eatvio_app/core/mixins/registration_date_cache_mixin.dart';
|
||||
import 'package:eatvio_app/core/prefs.dart';
|
||||
import 'package:eatvio_app/core/session/session_lifecycle_service.dart';
|
||||
import 'package:eatvio_app/core/token_storage.dart';
|
||||
import 'package:eatvio_app/features/auth/models/oauth_verification_request_response.dart';
|
||||
import 'package:eatvio_app/features/auth/presentation/bloc/oauth_cubit/oauth_state.dart';
|
||||
@@ -13,10 +14,12 @@ import 'package:eatvio_app/core/error_reporting/report_from.dart';
|
||||
class OAuthCubit extends Cubit<OAuthState> with RegistrationDateCacheMixin {
|
||||
final AuthRepository authRepository;
|
||||
final FirebaseApi firebaseApi;
|
||||
final SessionLifecycleService sessionLifecycleService;
|
||||
|
||||
OAuthCubit({
|
||||
required this.authRepository,
|
||||
required this.firebaseApi,
|
||||
required this.sessionLifecycleService,
|
||||
required Prefs prefs,
|
||||
}) : super(const OAuthState()) {
|
||||
this.prefs = prefs;
|
||||
@@ -40,6 +43,7 @@ class OAuthCubit extends Cubit<OAuthState> with RegistrationDateCacheMixin {
|
||||
|
||||
log('OAuthCubit: Got token model: ${tokenModel.token}');
|
||||
await TokenStorage.saveTokenInStorage(tokenModel.token);
|
||||
await sessionLifecycleService.onAuthenticated();
|
||||
emit(OAuthState.loaded(token: tokenModel.token));
|
||||
} on DioException catch (e, st) {
|
||||
await reportFrom(e, st, this);
|
||||
@@ -122,7 +126,7 @@ class OAuthCubit extends Cubit<OAuthState> with RegistrationDateCacheMixin {
|
||||
clientId: clientId,
|
||||
);
|
||||
await TokenStorage.saveTokenInStorage(token);
|
||||
await firebaseApi.registerPushToken();
|
||||
await sessionLifecycleService.onAuthenticated();
|
||||
cacheRegistrationDate(DateTime.now());
|
||||
emit(OAuthState.loaded(token: token));
|
||||
} on DioException catch (e, st) {
|
||||
@@ -241,7 +245,7 @@ class OAuthCubit extends Cubit<OAuthState> with RegistrationDateCacheMixin {
|
||||
'OAuthCubit: OAuth verification confirmed successfully, token received',
|
||||
);
|
||||
await TokenStorage.saveTokenInStorage(response.token);
|
||||
await firebaseApi.registerPushToken();
|
||||
await sessionLifecycleService.onAuthenticated();
|
||||
emit(OAuthState.loaded(token: response.token));
|
||||
} catch (e, st) {
|
||||
await reportFrom(e, st, this);
|
||||
|
||||
@@ -4,12 +4,10 @@ import 'package:eatvio_app/app_keys.dart';
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_router.dart';
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/firebase_api.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_divider.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_text_form_field.dart';
|
||||
import 'package:eatvio_app/features/auth/presentation/bloc/auth_cubit/auth_cubit.dart';
|
||||
@@ -18,8 +16,6 @@ import 'package:eatvio_app/features/auth/presentation/bloc/oauth_cubit/oauth_cub
|
||||
import 'package:eatvio_app/features/auth/presentation/bloc/oauth_cubit/oauth_state.dart';
|
||||
import 'package:eatvio_app/features/auth/entities/auth_result_entity.dart';
|
||||
import 'package:eatvio_app/core/di/cubit_registrations.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/dev_tools/presentation/dev_tools_screen.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
@@ -68,23 +64,8 @@ class _AuthScreenState extends State<AuthScreen> {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _reconnectWebSocket(BuildContext context) async {
|
||||
try {
|
||||
await sl<WsManager>().clear();
|
||||
await sl<WsManager>().initialize();
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
if (context.mounted) {
|
||||
try {
|
||||
await sl<ProfileAvatarCubit>().getMyProfile();
|
||||
await sl<ChatsCubit>().getChatRoomsWithLoading();
|
||||
await sl<FirebaseApi>().registerPushToken();
|
||||
showSuccessToast('Успешный вход');
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
Future<void> _reconnectWebSocket() async {
|
||||
showSuccessToast('Успешный вход');
|
||||
AppRouter.navigatorKey.currentContext?.go('/feed-screen', extra: {'needToInitializeWebSocket': false});
|
||||
}
|
||||
|
||||
@@ -202,18 +183,7 @@ class _AuthScreenState extends State<AuthScreen> {
|
||||
}
|
||||
|
||||
Future<void> _reconnectAfterAuth() async {
|
||||
final context = AppRouter.navigatorKey.currentContext;
|
||||
if (context == null) return;
|
||||
try {
|
||||
await sl<WsManager>().clear();
|
||||
await sl<WsManager>().initialize();
|
||||
await sl<ProfileAvatarCubit>().getMyProfile();
|
||||
await sl<ChatsCubit>().getChatRoomsWithLoading();
|
||||
await sl<FirebaseApi>().registerPushToken();
|
||||
showSuccessToast('Успешный вход');
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
}
|
||||
showSuccessToast('Успешный вход');
|
||||
AppRouter.navigatorKey.currentContext?.go('/feed-screen', extra: {'needToInitializeWebSocket': false});
|
||||
}
|
||||
|
||||
@@ -395,7 +365,7 @@ class _AuthScreenState extends State<AuthScreen> {
|
||||
final isAuthenticated = await authCubit.getTokenByCredentials(emailController.text, passwordController.text);
|
||||
if (isAuthenticated) {
|
||||
if (context.mounted) {
|
||||
await _reconnectWebSocket(context);
|
||||
await _reconnectWebSocket();
|
||||
}
|
||||
}
|
||||
setState(() {
|
||||
|
||||
@@ -9,11 +9,7 @@ import 'package:eatvio_app/features/auth/presentation/bloc/oauth_cubit/oauth_sta
|
||||
import 'package:eatvio_app/features/auth/entities/auth_result_entity.dart';
|
||||
import 'package:eatvio_app/core/app_router.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/websockets/ws_manager.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/create_diet/presentation/widgets/error_widget.dart';
|
||||
import 'package:eatvio_app/core/firebase_api.dart';
|
||||
import 'package:eatvio_app/core/utils/toasts.dart';
|
||||
import 'package:eatvio_app/core/utils/validation_utils.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
@@ -62,23 +58,8 @@ class _RegisterViaSocialScreenState extends State<RegisterViaSocialScreen> {
|
||||
_initializeFieldsFromAuthResult();
|
||||
}
|
||||
|
||||
Future<void> _reconnectWebSocket(BuildContext context) async {
|
||||
try {
|
||||
await sl<WsManager>().clear();
|
||||
await sl<WsManager>().initialize();
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
if (context.mounted) {
|
||||
try {
|
||||
await sl<ProfileAvatarCubit>().getMyProfile();
|
||||
await sl<ChatsCubit>().getChatRoomsWithLoading();
|
||||
await sl<FirebaseApi>().registerPushToken();
|
||||
showSuccessToast('Успешная регистрация');
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
Future<void> _reconnectWebSocket() async {
|
||||
showSuccessToast('Успешная регистрация');
|
||||
AppRouter.appRouter.go('/feed-screen', extra: {'needToInitializeWebSocket': false});
|
||||
}
|
||||
|
||||
@@ -451,7 +432,7 @@ class _RegisterViaSocialScreenState extends State<RegisterViaSocialScreen> {
|
||||
Builder(
|
||||
builder: (context) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_reconnectWebSocket(context);
|
||||
_reconnectWebSocket();
|
||||
});
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
@@ -795,7 +776,7 @@ class _RegisterViaSocialScreenState extends State<RegisterViaSocialScreen> {
|
||||
|
||||
// Перезагружаем веб-сокет и переходим на главный экран
|
||||
if (widgetContext.mounted) {
|
||||
_reconnectWebSocket(widgetContext);
|
||||
_reconnectWebSocket();
|
||||
}
|
||||
} catch (e, stackTrace) {
|
||||
log('OAuth Verification Confirm Error: $e', error: e, stackTrace: stackTrace);
|
||||
|
||||
@@ -3,13 +3,10 @@ import 'dart:io';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_router.dart';
|
||||
import 'package:eatvio_app/core/firebase_api.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/session/session_lifecycle_service.dart';
|
||||
import 'package:eatvio_app/core/token_storage.dart';
|
||||
import 'package:eatvio_app/core/utils/toasts.dart';
|
||||
import 'package:eatvio_app/core/websockets/ws_manager.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:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:qr_code_scanner_plus/qr_code_scanner_plus.dart';
|
||||
@@ -39,7 +36,10 @@ class _ScanQRCodeScreenState extends State<ScanQRCodeScreen> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Сканирование QR-кода'), centerTitle: true),
|
||||
appBar: AppBar(
|
||||
title: const Text('Сканирование QR-кода'),
|
||||
centerTitle: true,
|
||||
),
|
||||
body: Column(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
@@ -60,30 +60,33 @@ class _ScanQRCodeScreenState extends State<ScanQRCodeScreen> {
|
||||
try {
|
||||
final scannedUrl = scanData.code;
|
||||
final uri = Uri.parse(scannedUrl ?? '');
|
||||
final scannedBaseUrl = (uri.hasAuthority) ? '${uri.scheme}://${uri.host}' : '';
|
||||
final scannedBaseUrl = (uri.hasAuthority)
|
||||
? '${uri.scheme}://${uri.host}'
|
||||
: '';
|
||||
sl<AppConfig>().setBaseUrl(scannedBaseUrl);
|
||||
sl<Dio>().options.baseUrl = scannedBaseUrl;
|
||||
final response = await Dio().getUri(uri, options: Options(headers: {'accept': 'application/json', 'content-type': 'application/json'}));
|
||||
final response = await Dio().getUri(
|
||||
uri,
|
||||
options: Options(
|
||||
headers: {
|
||||
'accept': 'application/json',
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
),
|
||||
);
|
||||
final token = response.data['token'];
|
||||
await TokenStorage.saveTokenInStorage(token);
|
||||
try {
|
||||
await sl<WsManager>().clear();
|
||||
await sl<WsManager>().initialize();
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
try {
|
||||
await sl<ProfileAvatarCubit>().getMyProfile();
|
||||
await sl<ChatsCubit>().getChatRoomsWithLoading();
|
||||
await sl<FirebaseApi>().registerPushToken();
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
AppRouter.navigatorKey.currentContext?.go('/feed-screen', extra: {'needToInitializeWebSocket': false});
|
||||
await sl<SessionLifecycleService>().onAuthenticated();
|
||||
AppRouter.navigatorKey.currentContext?.go(
|
||||
'/feed-screen',
|
||||
extra: {'needToInitializeWebSocket': false},
|
||||
);
|
||||
showSuccessToast('Добро пожаловать!');
|
||||
} catch (e) {
|
||||
await controller.resumeCamera();
|
||||
showErrorToast('Что-то пошло не так. Попробуйте обновить страницу с QR-кодом или войдите с помощью логина и пароля.');
|
||||
showErrorToast(
|
||||
'Что-то пошло не так. Попробуйте обновить страницу с QR-кодом или войдите с помощью логина и пароля.',
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:eatvio_app/core/session/session_lifecycle_service.dart';
|
||||
import 'package:eatvio_app/features/menu/presentation/bloc/menu_cubit/menu_state.dart';
|
||||
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@@ -7,7 +8,11 @@ import 'package:eatvio_app/core/error_reporting/report_from.dart';
|
||||
|
||||
class MenuCubit extends Cubit<MenuState> {
|
||||
final ProfileRepository profileRepository;
|
||||
MenuCubit(this.profileRepository) : super(const MenuState.initial());
|
||||
final SessionLifecycleService sessionLifecycleService;
|
||||
MenuCubit(this.profileRepository, this.sessionLifecycleService)
|
||||
: super(const MenuState.initial());
|
||||
|
||||
Future<void> logout() => sessionLifecycleService.logout();
|
||||
|
||||
Future<void> getMyProfile() async {
|
||||
emit(const MenuState.loading());
|
||||
@@ -23,7 +28,10 @@ class MenuCubit extends Cubit<MenuState> {
|
||||
|
||||
Future<String?> makePassKey() async {
|
||||
try {
|
||||
final passKey = await profileRepository.getPassKey(routeName: 'product_nutritional_value.index', routeParams: {});
|
||||
final passKey = await profileRepository.getPassKey(
|
||||
routeName: 'product_nutritional_value.index',
|
||||
routeParams: {},
|
||||
);
|
||||
return passKey;
|
||||
} catch (e, st) {
|
||||
await reportFrom(e, st, this);
|
||||
|
||||
@@ -1,22 +1,15 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/error_reporting/error_reporter.dart';
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/firebase_api.dart';
|
||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||
import 'package:eatvio_app/core/token_storage.dart';
|
||||
import 'package:eatvio_app/core/utils/image_constant.dart';
|
||||
import 'package:eatvio_app/core/utils/date_time_utils.dart';
|
||||
import 'package:eatvio_app/core/widgets/data_error_widget.dart';
|
||||
import 'package:eatvio_app/core/widgets/loader.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/core/websockets/ws_manager.dart';
|
||||
import 'package:eatvio_app/features/consultant_rating/presentation/consultant_rating_screen.dart';
|
||||
import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/consultant_finances_screen.dart';
|
||||
import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/user_purchases_screen.dart';
|
||||
import 'package:eatvio_app/features/menu/presentation/bloc/menu_cubit/menu_cubit.dart';
|
||||
import 'package:eatvio_app/features/video_calls/presentation/bloc/video_call_session/video_call_session_cubit.dart';
|
||||
import 'package:eatvio_app/features/menu/presentation/bloc/menu_cubit/menu_state.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';
|
||||
@@ -155,6 +148,7 @@ class MenuScreen extends StatelessWidget {
|
||||
|
||||
Future<Null> Function() _onTapLogout(BuildContext context) {
|
||||
return () async {
|
||||
final menuCubit = context.read<MenuCubit>();
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => CupertinoAlertDialog(
|
||||
@@ -162,25 +156,8 @@ class MenuScreen extends StatelessWidget {
|
||||
actions: [
|
||||
CupertinoDialogAction(
|
||||
onPressed: () async {
|
||||
try {
|
||||
// Logout revokes the Sanctum token. Unregister first, or
|
||||
// FCM / VoIP tokens stay on the server and calls still ring.
|
||||
await sl<FirebaseApi>().unregisterPushTokens();
|
||||
} catch (e, s) {
|
||||
await sl<ErrorReporter>().report(e, s, origin: 'MenuScreen');
|
||||
}
|
||||
try {
|
||||
await sl<Dio>().post('${sl<AppConfig>().baseUrl}/api/profile/logout');
|
||||
} catch (e, s) {
|
||||
await sl<ErrorReporter>().report(e, s, origin: 'MenuScreen');
|
||||
} finally {
|
||||
await sl<VideoCallSessionCubit>().endCall();
|
||||
await sl<WsManager>().clear();
|
||||
await TokenStorage.removeTokenInStorage();
|
||||
if (context.mounted) {
|
||||
GoRouter.of(context).go('/auth-screen', extra: true);
|
||||
}
|
||||
}
|
||||
Navigator.of(context).pop();
|
||||
await menuCubit.logout();
|
||||
},
|
||||
isDestructiveAction: true,
|
||||
child: const Text("Выйти"),
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:eatvio_app/core/app_router.dart';
|
||||
import 'package:eatvio_app/core/firebase_api.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/mixins/registration_date_cache_mixin.dart';
|
||||
import 'package:eatvio_app/core/prefs.dart';
|
||||
|
||||
import 'package:eatvio_app/core/session/session_lifecycle_service.dart';
|
||||
import 'package:eatvio_app/core/token_storage.dart';
|
||||
import 'package:eatvio_app/features/registration/models/user_registration_model.dart';
|
||||
import 'package:eatvio_app/features/registration/presentation/bloc/registration_cubit/registration_state.dart';
|
||||
@@ -18,7 +16,8 @@ import 'package:go_router/go_router.dart';
|
||||
|
||||
class RegistrationCubit extends Cubit<RegistrationState> with RegistrationDateCacheMixin {
|
||||
final RegistrationRepository registrationRepository;
|
||||
RegistrationCubit({required this.registrationRepository, required Prefs prefs})
|
||||
final SessionLifecycleService sessionLifecycleService;
|
||||
RegistrationCubit({required this.registrationRepository, required this.sessionLifecycleService, required Prefs prefs})
|
||||
: super(
|
||||
const RegistrationState(
|
||||
sheet: UserRegistrationSheet(
|
||||
@@ -62,7 +61,7 @@ class RegistrationCubit extends Cubit<RegistrationState> with RegistrationDateCa
|
||||
state.sheet.copyWith(phone: state.sheet.phone.replaceAll(' ', '').replaceAll('(', '').replaceAll(')', ''), responseBody: '{}'),
|
||||
);
|
||||
await TokenStorage.saveTokenInStorage(token);
|
||||
await sl<FirebaseApi>().registerPushToken();
|
||||
await sessionLifecycleService.onAuthenticated();
|
||||
RegistrationAnalyticsService.reportRegistrationComplete();
|
||||
cacheRegistrationDate(DateTime.now());
|
||||
AppRouter.navigatorKey.currentContext?.replaceNamed('verifyEmailScreen');
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'dart:developer';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:eatvio_app/core/firebase_api.dart';
|
||||
import 'package:eatvio_app/core/session/session_lifecycle_service.dart';
|
||||
import 'package:eatvio_app/core/token_storage.dart';
|
||||
import 'package:eatvio_app/core/utils/date_time_utils.dart';
|
||||
import 'package:eatvio_app/core/utils/validation_utils.dart';
|
||||
@@ -15,9 +16,11 @@ import 'package:gpassword/gpassword.dart';
|
||||
class RegistrationCubitV2 extends Cubit<RegistrationStateV2> {
|
||||
final RegistrationRepositoryV2 registrationRepository;
|
||||
final FirebaseApi firebaseApi;
|
||||
final SessionLifecycleService sessionLifecycleService;
|
||||
RegistrationCubitV2({
|
||||
required this.registrationRepository,
|
||||
required this.firebaseApi,
|
||||
required this.sessionLifecycleService,
|
||||
}) : super(const RegistrationStateV2());
|
||||
|
||||
void setFirstName(String firstName) {
|
||||
@@ -204,7 +207,7 @@ class RegistrationCubitV2 extends Cubit<RegistrationStateV2> {
|
||||
);
|
||||
if (token != null) {
|
||||
await TokenStorage.saveTokenInStorage(token);
|
||||
await firebaseApi.registerPushToken();
|
||||
await sessionLifecycleService.onAuthenticated();
|
||||
emit(state.copyWith(registrationError: null));
|
||||
return (true, false);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:eatvio_app/core/session/session_lifecycle_service.dart';
|
||||
import 'package:eatvio_app/features/consultant/become_consultant/models/consultation_request_model.dart';
|
||||
import 'package:eatvio_app/features/consultant/become_consultant/repositories/consultant_repository.dart';
|
||||
import 'package:eatvio_app/features/consultations/repositories/consultations_repository.dart';
|
||||
@@ -17,12 +19,14 @@ class SettingsCubit extends Cubit<SettingsState> {
|
||||
final SettingsRepository settingsRepository;
|
||||
final ConsultantRepository consultantRepository;
|
||||
final ConsultationsRepository consultationsRepository;
|
||||
final SessionLifecycleService sessionLifecycleService;
|
||||
|
||||
SettingsCubit({
|
||||
required this.profileRepository,
|
||||
required this.settingsRepository,
|
||||
required this.profileRepository,
|
||||
required this.settingsRepository,
|
||||
required this.consultantRepository,
|
||||
required this.consultationsRepository,
|
||||
required this.sessionLifecycleService,
|
||||
}) : super(const SettingsState.loading());
|
||||
|
||||
Future<void> init() async {
|
||||
@@ -30,10 +34,12 @@ class SettingsCubit extends Cubit<SettingsState> {
|
||||
late final ConsultationRequestModel? consultantInfo;
|
||||
late final bool hasActiveConsultation;
|
||||
late final OAuthAccountsDataModel? oauthAccounts;
|
||||
|
||||
|
||||
try {
|
||||
if (profile.phoneConfirmationState.contains('confirmed')) {
|
||||
consultantInfo = await consultantRepository.getMyRequestStatus(profile.slug);
|
||||
consultantInfo = await consultantRepository.getMyRequestStatus(
|
||||
profile.slug,
|
||||
);
|
||||
} else {
|
||||
consultantInfo = null;
|
||||
}
|
||||
@@ -41,14 +47,14 @@ class SettingsCubit extends Cubit<SettingsState> {
|
||||
await reportFrom(e, st, this);
|
||||
consultantInfo = null;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
hasActiveConsultation = await _checkActiveConsultation();
|
||||
} catch (e, st) {
|
||||
await reportFrom(e, st, this);
|
||||
hasActiveConsultation = false;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
final oauthResponse = await settingsRepository.getOAuthAccounts();
|
||||
oauthAccounts = oauthResponse.data;
|
||||
@@ -56,7 +62,7 @@ class SettingsCubit extends Cubit<SettingsState> {
|
||||
await reportFrom(e, st, this);
|
||||
oauthAccounts = null;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
emit(
|
||||
SettingsState.loaded(
|
||||
@@ -74,9 +80,13 @@ class SettingsCubit extends Cubit<SettingsState> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateProfileDetails({required ProfileDetailsModel profileDetailsModel}) async {
|
||||
Future<void> updateProfileDetails({
|
||||
required ProfileDetailsModel profileDetailsModel,
|
||||
}) async {
|
||||
try {
|
||||
await settingsRepository.updateProfileDetails(profileDetailsModel: profileDetailsModel);
|
||||
await settingsRepository.updateProfileDetails(
|
||||
profileDetailsModel: profileDetailsModel,
|
||||
);
|
||||
} catch (e, st) {
|
||||
await reportFrom(e, st, this);
|
||||
} finally {
|
||||
@@ -84,9 +94,13 @@ class SettingsCubit extends Cubit<SettingsState> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateProfileSettings({required ProfileSettingsModel profileSettingsModel}) async {
|
||||
Future<void> updateProfileSettings({
|
||||
required ProfileSettingsModel profileSettingsModel,
|
||||
}) async {
|
||||
try {
|
||||
await settingsRepository.updateProfileSettings(profileSettingsModel: profileSettingsModel);
|
||||
await settingsRepository.updateProfileSettings(
|
||||
profileSettingsModel: profileSettingsModel,
|
||||
);
|
||||
} catch (e, st) {
|
||||
await reportFrom(e, st, this);
|
||||
} finally {
|
||||
@@ -97,9 +111,15 @@ class SettingsCubit extends Cubit<SettingsState> {
|
||||
Future<bool> deleteAccount() async {
|
||||
try {
|
||||
await profileRepository.deleteAccount();
|
||||
await sessionLifecycleService.endSession(
|
||||
reason: SessionEndReason.accountDeleted,
|
||||
);
|
||||
return true;
|
||||
} catch (e, st) {
|
||||
await reportFrom(e, st, this);
|
||||
if (e is DioException && e.response?.statusCode == 401) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -135,11 +155,14 @@ class SettingsCubit extends Cubit<SettingsState> {
|
||||
|
||||
Future<bool> _checkActiveConsultation() async {
|
||||
try {
|
||||
final userPurchases = await consultationsRepository.getUserPurchases(null, null);
|
||||
final userPurchases = await consultationsRepository.getUserPurchases(
|
||||
null,
|
||||
null,
|
||||
);
|
||||
final activeStates = ChosenPlanStateEnum.getActiveStates();
|
||||
|
||||
return userPurchases.any((purchase) =>
|
||||
activeStates.contains(purchase.chosenPlanState)
|
||||
|
||||
return userPurchases.any(
|
||||
(purchase) => activeStates.contains(purchase.chosenPlanState),
|
||||
);
|
||||
} catch (e, st) {
|
||||
await reportFrom(e, st, this);
|
||||
|
||||
@@ -698,7 +698,6 @@ class _DeleteAccountBottomSheet extends State<DeleteAccountBottomSheet> {
|
||||
onPressed: () async {
|
||||
final result = await widget.cubit.deleteAccount();
|
||||
if (result == true) {
|
||||
AppRouter.navigatorKey.currentContext?.go('/auth-screen');
|
||||
return;
|
||||
}
|
||||
AppRouter.navigatorKey.currentContext?.pop();
|
||||
|
||||
Reference in New Issue
Block a user