import 'dart:io'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:image_picker/image_picker.dart'; import 'package:path/path.dart' as p; import 'package:path_provider/path_provider.dart'; import '../../../core/constants/app_defaults.dart'; import '../../../core/providers/app_providers.dart'; import '../../../data/local/models/models.dart'; final profileProvider = AsyncNotifierProvider(ProfileNotifier.new); class ProfileNotifier extends AsyncNotifier { @override Future build() async { final isar = await ref.watch(isarProvider.future); final profile = await isar.userProfiles.get(AppDefaults.singletonId); if (profile == null) { // Bootstrap should have created one; create defensively. final fresh = UserProfile.createDefault(); await isar.writeTxn(() => isar.userProfiles.put(fresh)); return fresh; } if (_isLocalAvatarMissing(profile)) { return _clearMissingLocalAvatar(profile); } return profile; } bool _isLocalAvatarMissing(UserProfile profile) { if (profile.avatarSource != AvatarSource.localFile) return false; final path = profile.avatarLocalPath?.trim(); if (path == null || path.isEmpty) return true; return !File(path).existsSync(); } Future _clearMissingLocalAvatar(UserProfile current) async { final updated = current.copyWith( avatarAssetPath: AppDefaults.defaultAvatarAssetPath, avatarLocalPath: null, avatarSource: AvatarSource.asset, ); final isar = await ref.read(isarProvider.future); await isar.writeTxn(() => isar.userProfiles.put(updated)); return updated; } Future updateNickname(String nickname) async { final trimmed = nickname.trim(); if (trimmed.isEmpty) return; final current = state.value; if (current == null) return; final updated = current.copyWith(nickname: trimmed); final isar = await ref.read(isarProvider.future); await isar.writeTxn(() => isar.userProfiles.put(updated)); state = AsyncData(updated); } /// Picks an image from gallery or camera, copies it into the app sandbox /// and stores the local file path on the profile. Future updateAvatar({required bool fromCamera}) async { final current = state.value; if (current == null) return false; final picker = ImagePicker(); final xfile = await picker.pickImage( source: fromCamera ? ImageSource.camera : ImageSource.gallery, maxWidth: 512, maxHeight: 512, imageQuality: 85, ); if (xfile == null) return false; final docs = await getApplicationDocumentsDirectory(); final destPath = p.join(docs.path, AppDefaults.avatarFileName); final sourceFile = File(xfile.path); await sourceFile.copy(destPath); final updated = current.copyWith( avatarLocalPath: destPath, avatarSource: AvatarSource.localFile, ); final isar = await ref.read(isarProvider.future); await isar.writeTxn(() => isar.userProfiles.put(updated)); state = AsyncData(updated); return true; } Future resetAvatar() async { final current = state.value; if (current == null) return; final path = current.avatarLocalPath; if (path != null && path.isNotEmpty) { final file = File(path); if (await file.exists()) { try { await file.delete(); } catch (_) { // Best-effort cleanup; profile reset still proceeds. } } } final updated = current.copyWith( avatarAssetPath: AppDefaults.defaultAvatarAssetPath, avatarLocalPath: null, avatarSource: AvatarSource.asset, ); final isar = await ref.read(isarProvider.future); await isar.writeTxn(() => isar.userProfiles.put(updated)); state = AsyncData(updated); } }