profile picture fix

pull/243/head
tahaalam 2 days ago
parent 203c025183
commit c19451a6b6

@ -2,6 +2,8 @@ import 'dart:developer';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/cupertino.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/enums.dart';
import 'package:hmg_patient_app_new/features/health_trackers/health_trackers_repo.dart';
import 'package:hmg_patient_app_new/features/health_trackers/models/blood_pressure/blood_pressure_result.dart';
@ -76,6 +78,10 @@ class HealthTrackersViewModel extends ChangeNotifier {
"آخر",
];
List<String> getBloodSugarTimeList(){
return getIt.get<AppState>().isArabic()?bloodSugarMeasureTimeArList:bloodSugarMeasureTimeEnList;
}
// Setters with notification
void setBloodSugarMeasureTime(String duration) async {
_selectedBloodSugarMeasureTime = duration;

@ -251,7 +251,7 @@ class _AddHealthTrackerEntryPageState extends State<AddHealthTrackerEntryPage> {
_showSelectionBottomSheet(
context: context,
title: LocaleKeys.selectMeasureTime.tr(context: context),
items: viewModel.bloodSugarMeasureTimeEnList,
items: viewModel.getBloodSugarTimeList(),
selectedValue: viewModel.selectedBloodSugarMeasureTime,
onSelected: viewModel.setBloodSugarMeasureTime,
useUpperCase: false,

@ -450,16 +450,16 @@ class _FamilyCardWidgetState extends State<FamilyCardWidget> {
});
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
// Also sync when dependencies change (e.g., after user switch)
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
_syncProfileImageFromAppState();
}
});
}
// @override
// void didChangeDependencies() {
// super.didChangeDependencies();
// // Also sync when dependencies change (e.g., after user switch)
// WidgetsBinding.instance.addPostFrameCallback((_) {
// if (mounted) {
// _syncProfileImageFromAppState();
// }
// });
// }
void _syncProfileImageFromAppState() {
try {

@ -1,8 +1,10 @@
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/app_export.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
@ -29,16 +31,24 @@ class _ProfilePictureWidgetState extends State<ProfilePictureWidget> {
final AppState _appState = getIt.get<AppState>();
File? _selectedImage;
int? _currentPatientId;
bool _isInitialLoadTriggered = false;
/// Cache decoded image bytes to avoid decoding base64 on every rebuild
Uint8List? _cachedImageBytes;
String? _cachedImageDataHash;
@override
void initState() {
super.initState();
_currentPatientId = _appState.getAuthenticatedUser()?.patientId;
// Pre-cache existing image data if available (prevents blink from default loaded)
_tryCacheExistingImage();
// Use addPostFrameCallback to ensure widget is built before loading
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!mounted) return;
if (!mounted || _isInitialLoadTriggered) return;
_isInitialLoadTriggered = true;
final patientID = _appState.getAuthenticatedUser()?.patientId;
@ -47,17 +57,20 @@ class _ProfilePictureWidgetState extends State<ProfilePictureWidget> {
return;
}
// Always load fresh data from API - do NOT use AppState cache
// Load fresh data from API
print('📥 Loading fresh profile image from API for patient: $patientID');
_loadProfileImage(forceRefresh: true);
_loadProfileImage(forceRefresh: false);
});
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
// Only check for user switch, NOT on initial load (initState handles that)
if (_isInitialLoadTriggered) {
_checkAndUpdateUserImage();
}
}
@override
void didUpdateWidget(ProfilePictureWidget oldWidget) {
@ -65,6 +78,20 @@ class _ProfilePictureWidgetState extends State<ProfilePictureWidget> {
_checkAndUpdateUserImage();
}
/// Pre-cache already-loaded image bytes so we don't flash default avatar
void _tryCacheExistingImage() {
final imageData = _appState.getProfileImageData;
if (imageData != null && imageData.isNotEmpty) {
try {
_cachedImageBytes = base64Decode(imageData);
_cachedImageDataHash = '${imageData.length}_${imageData.hashCode}';
} catch (_) {
_cachedImageBytes = null;
_cachedImageDataHash = null;
}
}
}
void _checkAndUpdateUserImage() {
// Check if the authenticated user has changed (family member switch)
final currentPatientId = _appState.getAuthenticatedUser()?.patientId;
@ -88,6 +115,10 @@ class _ProfilePictureWidgetState extends State<ProfilePictureWidget> {
// Then clear ViewModel cache
profileVm.clearProfileImageCache();
// Clear local decoded bytes cache
_cachedImageBytes = null;
_cachedImageDataHash = null;
// Force rebuild to show default avatar immediately
if (mounted) {
setState(() {
@ -103,6 +134,7 @@ class _ProfilePictureWidgetState extends State<ProfilePictureWidget> {
onSuccess: (data) {
print('✅ Profile image loaded successfully for user: $currentPatientId');
if (mounted) {
_tryCacheExistingImage();
setState(() {}); // Force rebuild to show new data
}
},
@ -136,7 +168,10 @@ class _ProfilePictureWidgetState extends State<ProfilePictureWidget> {
forceRefresh: forceRefresh,
onSuccess: (data) {
print('✅ Profile image loaded successfully');
// Image loaded successfully
if (mounted) {
_tryCacheExistingImage();
setState(() {}); // Rebuild with new cached bytes
}
},
onError: (error) {
print('❌ Error loading profile image: $error');
@ -437,6 +472,8 @@ class _ProfilePictureWidgetState extends State<ProfilePictureWidget> {
imageData: base64String,
onSuccess: (data) {
if (mounted) {
// Update cached bytes immediately from the uploaded data
_tryCacheExistingImage();
setState(() {
_selectedImage = null; // Clear selected image after successful upload
});
@ -481,72 +518,75 @@ class _ProfilePictureWidgetState extends State<ProfilePictureWidget> {
);
}
// // IMPORTANT: Verify the cached image belongs to the current user
// // If _currentPatientId doesn't match currentPatientId, don't show cached image
// if (currentPatientId != _currentPatientId) {
// print('⚠️ Patient ID mismatch - showing default avatar (current: $currentPatientId, cached: $_currentPatientId)');
// return Image.asset(
// defaultImage,
// width: 136.w,
// height: 136.h,
// );
// }
// IMPORTANT: Use ONLY ViewModel data, DO NOT fallback to AppState
// This prevents showing stale cached data when switching users
final String? imageData = profileVm.profileImageData;
print('🖼️ Building profile image - has data: ${imageData != null && imageData.isNotEmpty}, patient: $currentPatientId (ViewModel ONLY)');
// Use cached decoded bytes update cache if source data changed
final String? imageData = GetIt.instance<AppState>().getProfileImageData;
final String? currentHash = (imageData != null && imageData.isNotEmpty)
? '${imageData.length}_${imageData.hashCode}'
: null;
// Show uploaded image if available
if (imageData != null && imageData.isNotEmpty) {
// Re-decode only if the underlying data actually changed
if (currentHash != null && currentHash != _cachedImageDataHash) {
try {
final bytes = base64Decode(imageData);
_cachedImageBytes = base64Decode(imageData!);
_cachedImageDataHash = currentHash;
} catch (e) {
print('❌ Error decoding profile image: $e');
_cachedImageBytes = null;
_cachedImageDataHash = null;
}
} else if (currentHash == null) {
_cachedImageBytes = null;
_cachedImageDataHash = null;
}
// Show cached decoded image if available
if (_cachedImageBytes != null) {
return ClipOval(
child: Image.memory(
bytes,
_cachedImageBytes!,
key: ValueKey('profile_$currentPatientId'),
width: 136.w,
height: 136.h,
fit: BoxFit.cover,
gaplessPlayback: true, // Prevents blink during image rebuild
),
);
} catch (e) {
print('❌ Error decoding profile image: $e');
// If decoding fails, show default image
return Image.asset(
defaultImage,
width: 136.w,
height: 136.h,
);
}
}
// Show default image (no image data or user has no uploaded image)
print('📷 Showing default avatar for user $currentPatientId');
return Image.asset(
defaultImage,
key: ValueKey('default_$currentPatientId'),
width: 136.w,
height: 136.h,
gaplessPlayback: true,
);
}
@override
Widget build(BuildContext context) {
// Check for user change on every build (handles navigation scenarios)
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
_checkAndUpdateUserImage();
}
});
// Removed addPostFrameCallback from build() it was causing redundant
// _checkAndUpdateUserImage calls on every single rebuild.
// didChangeDependencies + didUpdateWidget already handle user switches.
return Consumer<ProfileSettingsViewModel>(
builder: (context, profileVm, _) {
// If we already have cached bytes, show the image even while "loading"
// to prevent the shimmerimage blink on page open
final bool showShimmer = profileVm.isProfileImageLoading &&
_cachedImageBytes == null &&
_selectedImage == null;
return Center(
child: Stack(
children: [
// Profile Image
profileVm.isProfileImageLoading
// Profile Image use AnimatedSwitcher to smooth transition
AnimatedSwitcher(
duration: const Duration(milliseconds: 200),
child: showShimmer
? Container(
key: const ValueKey('shimmer'),
width: 136.w,
height: 136.h,
decoration: BoxDecoration(
@ -555,6 +595,7 @@ class _ProfilePictureWidgetState extends State<ProfilePictureWidget> {
),
).toShimmer2(isShow: true)
: _buildProfileImage(profileVm),
),
// Edit button
Positioned(

Loading…
Cancel
Save