fix(profile): refresh bottom-nav avatar on account switch

Reset ProfileAvatarCubit and user-scoped feed, chat and notification state so stale data from the previous account cannot be rendered. Refresh the avatar when the stored token changes and rebuild nav tabs when the profile identity or consultant role changes.

Refs: #1113
This commit is contained in:
2026-10-30 15:15:21 +03:00
parent 3a41a85f9d
commit 5f0b7e026b
8 changed files with 129 additions and 25 deletions

View File

@@ -6,6 +6,13 @@ class NewMessagesCubit extends Cubit<NewMessagesState> {
NewMessagesCubit() : super(const NewMessagesState.empty());
void reset() {
_messagesCount = 0;
if (!isClosed) {
emit(const NewMessagesState.empty());
}
}
void incrementCounter() {
_messagesCount++;
emit(NewMessagesState.loaded(messagesCount: _messagesCount));

View File

@@ -1,18 +1,22 @@
import 'dart:async';
import 'package:appmetrica_plugin/appmetrica_plugin.dart';
import 'package:eatvio_app/core/app_router.dart';
import 'package:eatvio_app/core/token_storage.dart';
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_state.dart';
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:eatvio_app/core/error_reporting/report_from.dart';
import 'package:go_router/go_router.dart';
import 'package:permission_handler/permission_handler.dart';
class ProfileAvatarCubit extends Cubit<ProfileAvatarState> {
final ProfileRepository profileRepository;
ProfileAvatarCubit({required this.profileRepository}) : super(const ProfileAvatarState.loading());
ProfileAvatarCubit({required this.profileRepository})
: super(const ProfileAvatarState.loading());
int _requestId = 0;
int? _sessionFingerprint;
bool _ensuring = false;
int _calculateAge(String birthdate) {
try {
@@ -25,7 +29,8 @@ class ProfileAvatarCubit extends Cubit<ProfileAvatarState> {
final now = DateTime.now();
int age = now.year - birthYear;
if (now.month < birthMonth || (now.month == birthMonth && now.day < birthDay)) {
if (now.month < birthMonth ||
(now.month == birthMonth && now.day < birthDay)) {
age--;
}
@@ -38,30 +43,82 @@ class ProfileAvatarCubit extends Cubit<ProfileAvatarState> {
return 0;
}
void reset() {
_requestId++;
_sessionFingerprint = null;
unawaited(AppMetrica.setUserProfileID(null).catchError((Object _) {}));
if (!isClosed) emit(const ProfileAvatarState.loading());
}
Future<void> ensureFreshForCurrentSession() async {
final token = await TokenStorage.getTokenFromStorage();
if (token == null || token.isEmpty) {
reset();
return;
}
final fingerprint = token.hashCode;
if (_sessionFingerprint == fingerprint &&
state is ProfileAvatarStateLoaded) {
return;
}
if (_ensuring) {
return;
}
_ensuring = true;
try {
await getMyProfile();
} finally {
_ensuring = false;
}
}
Future<void> getMyProfile() async {
final requestId = ++_requestId;
final token = await TokenStorage.getTokenFromStorage();
final fingerprint = token?.hashCode;
if (isClosed || requestId != _requestId) return;
emit(const ProfileAvatarState.loading());
try {
final profile = await profileRepository.getMyProfile();
if (isClosed || requestId != _requestId) return;
final profileDetails = await profileRepository.getMyProfileDetails();
if (isClosed || requestId != _requestId) return;
_sessionFingerprint = fingerprint;
emit(ProfileAvatarState.loaded(profile: profile));
try {
final notificationStatus = await Permission.notification.status;
if (isClosed || requestId != _requestId) return;
final isNotificationEnabled = notificationStatus.isGranted;
AppMetricaUserProfile userProfile = AppMetricaUserProfile([
AppMetricaNameAttribute.withValue(profile.name),
AppMetricaGenderAttribute.withValue(profileDetails.gender == 'male' ? AppMetricaGender.male : AppMetricaGender.female),
AppMetricaBirthDateAttribute.withAge(_calculateAge(profileDetails.birthdate)),
AppMetricaNotificationEnabledAttribute.withValue(isNotificationEnabled),
AppMetricaGenderAttribute.withValue(
profileDetails.gender == 'male'
? AppMetricaGender.male
: AppMetricaGender.female,
),
AppMetricaBirthDateAttribute.withAge(
_calculateAge(profileDetails.birthdate),
),
AppMetricaNotificationEnabledAttribute.withValue(
isNotificationEnabled,
),
]);
AppMetrica.setUserProfileID(profile.slug);
AppMetrica.reportUserProfile(userProfile);
} catch (e, st) {
await reportFrom(e, st, this);
// Игнорируем ошибки плагинов, профиль уже загружен
}
} catch (e, st) {
if (isClosed || requestId != _requestId) return;
await reportFrom(e, st, this);
AppRouter.navigatorKey.currentContext?.go('/auth-screen');
if (isClosed || requestId != _requestId) return;
emit(ProfileAvatarState.error(errorMessage: e.toString()));
}
}

View File

@@ -39,6 +39,8 @@ class BottomNavigationBarWidget extends StatefulWidget {
class _BottomNavigationBarWidgetState extends State<BottomNavigationBarWidget> {
int currentBottomNavBarIndex = 1;
List<String>? pages;
int? _pagesProfileId;
bool? _pagesIsConsultant;
int tapsCount = 0;
List<String> _getPages(bool isConsultant) {
@@ -179,7 +181,16 @@ class _BottomNavigationBarWidgetState extends State<BottomNavigationBarWidget> {
final isConsultant = profileModel.roles.contains('consultant');
// Инициализируем pages на основе profileModel
pages ??= _getPages(isConsultant);
if (pages == null ||
_pagesProfileId != profileModel.id ||
_pagesIsConsultant != isConsultant) {
_pagesProfileId = profileModel.id;
_pagesIsConsultant = isConsultant;
pages = _getPages(isConsultant);
if (currentBottomNavBarIndex >= pages!.length) {
currentBottomNavBarIndex = 0;
}
}
// Expose tab switching to descendants (e.g. notification widget)
final controller = BottomNavScope.maybeOf(context);