init
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
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';
|
||||
|
||||
class ProfileEditDialog extends ConsumerStatefulWidget {
|
||||
const ProfileEditDialog({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<ProfileEditDialog> createState() => _ProfileEditDialogState();
|
||||
}
|
||||
|
||||
class _ProfileEditDialogState extends ConsumerState<ProfileEditDialog> {
|
||||
late final TextEditingController _controller;
|
||||
bool _saving = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final profile = ref.read(profileProvider).value;
|
||||
_controller = TextEditingController(text: profile?.nickname ?? '');
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final profile = ref.watch(profileProvider).value;
|
||||
return AlertDialog(
|
||||
title: const Text('编辑个人资料'),
|
||||
content: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: _saving ? null : () => _pickAvatar(context),
|
||||
child: _AvatarPreview(profile: profile),
|
||||
),
|
||||
if (profile?.avatarSource == AvatarSource.localFile)
|
||||
TextButton.icon(
|
||||
onPressed: _saving ? null : _resetAvatar,
|
||||
icon: const Icon(Icons.refresh, size: 18),
|
||||
label: const Text('恢复默认头像'),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
TextField(
|
||||
controller: _controller,
|
||||
maxLength: 12,
|
||||
decoration: const InputDecoration(
|
||||
labelText: '昵称',
|
||||
hintText: '1~12 个字符',
|
||||
counterText: '',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: _saving ? null : () => Navigator.pop(context),
|
||||
child: const Text('取消'),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: _saving ? null : _save,
|
||||
child: const Text('保存'),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _pickAvatar(BuildContext context) async {
|
||||
final choice = await showModalBottomSheet<AvatarAction>(
|
||||
context: context,
|
||||
builder: (_) => SafeArea(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ListTile(
|
||||
leading: const Icon(Icons.photo_outlined),
|
||||
title: const Text('从相册选择'),
|
||||
onTap: () => Navigator.pop(context, AvatarAction.gallery),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.camera_alt_outlined),
|
||||
title: const Text('拍摄照片'),
|
||||
onTap: () => Navigator.pop(context, AvatarAction.camera),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
if (choice == null) return;
|
||||
setState(() => _saving = true);
|
||||
await ref
|
||||
.read(profileProvider.notifier)
|
||||
.updateAvatar(fromCamera: choice == AvatarAction.camera);
|
||||
if (mounted) setState(() => _saving = false);
|
||||
}
|
||||
|
||||
Future<void> _resetAvatar() async {
|
||||
setState(() => _saving = true);
|
||||
await ref.read(profileProvider.notifier).resetAvatar();
|
||||
if (mounted) setState(() => _saving = false);
|
||||
}
|
||||
|
||||
Future<void> _save() async {
|
||||
final name = _controller.text.trim();
|
||||
if (name.isEmpty) {
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(const SnackBar(content: Text('昵称不能为空')));
|
||||
return;
|
||||
}
|
||||
setState(() => _saving = true);
|
||||
await ref.read(profileProvider.notifier).updateNickname(name);
|
||||
if (mounted) {
|
||||
setState(() => _saving = false);
|
||||
Navigator.pop(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum AvatarAction { gallery, camera }
|
||||
|
||||
class _AvatarPreview extends StatelessWidget {
|
||||
const _AvatarPreview({required this.profile});
|
||||
final UserProfile? profile;
|
||||
|
||||
@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,
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(6),
|
||||
decoration: const BoxDecoration(
|
||||
color: AppTheme.primary,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(Icons.edit, color: Colors.white, size: 16),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user