修复白屏BUG、UI 边界溢出 BUG

This commit is contained in:
2026-08-09 01:35:48 +08:00
parent 4a357024bc
commit 4385c6941e
24 changed files with 485 additions and 206 deletions
@@ -17,11 +17,34 @@ class ProfileNotifier extends AsyncNotifier<UserProfile> {
Future<UserProfile> build() async {
final isar = await ref.watch(isarProvider.future);
final profile = await isar.userProfiles.get(AppDefaults.singletonId);
if (profile != null) return profile;
// Bootstrap should have created one; create defensively.
final fresh = UserProfile.createDefault();
await isar.writeTxn(() => isar.userProfiles.put(fresh));
return fresh;
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<UserProfile> _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<void> updateNickname(String nickname) async {
@@ -68,6 +91,19 @@ class ProfileNotifier extends AsyncNotifier<UserProfile> {
Future<void> 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,
@@ -0,0 +1,19 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:arcx/core/constants/app_defaults.dart';
import 'package:arcx/data/local/models/models.dart';
/// Resolves a profile avatar to a local file when present, otherwise the
/// default asset. Missing local files fall back safely (no FileImage crash).
ImageProvider resolveAvatarImage(UserProfile? profile) {
final path = profile?.avatarLocalPath?.trim();
if (profile?.avatarSource == AvatarSource.localFile &&
path != null &&
path.isNotEmpty &&
File(path).existsSync()) {
return FileImage(File(path));
}
return const AssetImage(AppDefaults.defaultAvatarAssetPath);
}
@@ -1,12 +1,10 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:arcx/app/app_theme.dart';
import 'package:arcx/core/constants/app_defaults.dart';
import 'package:arcx/data/local/models/models.dart';
import 'package:arcx/features/profile/application/profile_controller.dart';
import 'package:arcx/features/profile/presentation/avatar_image.dart';
class ProfileEditDialog extends ConsumerStatefulWidget {
const ProfileEditDialog({super.key});
@@ -137,23 +135,13 @@ class _AvatarPreview extends StatelessWidget {
@override
Widget build(BuildContext context) {
final p = profile;
final ImageProvider image;
if (p != null &&
p.avatarSource == AvatarSource.localFile &&
p.avatarLocalPath != null &&
p.avatarLocalPath!.isNotEmpty) {
image = FileImage(File(p.avatarLocalPath!));
} else {
image = const AssetImage(AppDefaults.defaultAvatarAssetPath);
}
return Stack(
alignment: Alignment.bottomRight,
children: [
CircleAvatar(
radius: 44,
backgroundColor: AppTheme.surfaceMuted,
backgroundImage: image,
backgroundImage: resolveAvatarImage(profile),
),
Container(
padding: const EdgeInsets.all(6),