137 lines
4.1 KiB
Dart
137 lines
4.1 KiB
Dart
import 'package:flutter/material.dart';
|
|
|
|
import 'package:provider/provider.dart';
|
|
|
|
import 'package:image_picker/image_picker.dart';
|
|
|
|
import 'package:pshared/provider/account.dart';
|
|
|
|
import 'package:pweb/widgets/drawer/avatar.dart';
|
|
|
|
|
|
class AvatarTile extends StatefulWidget {
|
|
final String? avatarUrl;
|
|
final String title;
|
|
final String description;
|
|
final String errorText;
|
|
|
|
const AvatarTile({
|
|
super.key,
|
|
required this.avatarUrl,
|
|
required this.title,
|
|
required this.description,
|
|
required this.errorText,
|
|
});
|
|
|
|
@override
|
|
State<AvatarTile> createState() => _AvatarTileState();
|
|
}
|
|
|
|
class _AvatarTileState extends State<AvatarTile> {
|
|
static const double _avatarSize = 96.0;
|
|
static const double _iconSize = 32.0;
|
|
static const double _titleSpacing = 4.0;
|
|
|
|
bool _isHovering = false;
|
|
bool _isUploading = false;
|
|
String _errorText = '';
|
|
|
|
Future<void> _pickImage(AccountProvider provider) async {
|
|
if (_isUploading) return;
|
|
|
|
final picker = ImagePicker();
|
|
final file = await picker.pickImage(source: ImageSource.gallery);
|
|
if (file == null) return;
|
|
|
|
setState(() {
|
|
_isUploading = true;
|
|
_errorText = '';
|
|
});
|
|
|
|
try {
|
|
await provider.uploadAvatar(file);
|
|
} catch (_) {
|
|
if (!mounted) return;
|
|
setState(() => _errorText = widget.errorText);
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(content: Text(widget.errorText)),
|
|
);
|
|
} finally {
|
|
if (mounted) {
|
|
setState(() => _isUploading = false);
|
|
}
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Consumer<AccountProvider>(
|
|
builder: (context, provider, _) {
|
|
final theme = Theme.of(context);
|
|
final isBusy = _isUploading || provider.isLoading;
|
|
|
|
return Column(
|
|
children: [
|
|
MouseRegion(
|
|
onEnter: (_) => setState(() => _isHovering = true),
|
|
onExit: (_) => setState(() => _isHovering = false),
|
|
child: GestureDetector(
|
|
onTap: isBusy ? null : () => _pickImage(provider),
|
|
child: Stack(
|
|
alignment: Alignment.center,
|
|
children: [
|
|
AccountAvatar(
|
|
size: _avatarSize,
|
|
showHeader: false,
|
|
provider: provider,
|
|
fallbackUrl: widget.avatarUrl,
|
|
),
|
|
if (_isHovering || _isUploading)
|
|
ClipOval(
|
|
child: Container(
|
|
width: _avatarSize,
|
|
height: _avatarSize,
|
|
color: theme.colorScheme.primary.withAlpha(90),
|
|
child: _isUploading
|
|
? SizedBox(
|
|
width: _iconSize,
|
|
height: _iconSize,
|
|
child: CircularProgressIndicator(
|
|
strokeWidth: 3,
|
|
valueColor: AlwaysStoppedAnimation(theme.colorScheme.onSecondary),
|
|
),
|
|
)
|
|
: Icon(
|
|
Icons.camera_alt,
|
|
color: theme.colorScheme.onSecondary,
|
|
size: _iconSize,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
SizedBox(height: _titleSpacing),
|
|
Text(
|
|
widget.description,
|
|
style: theme.textTheme.bodySmall?.copyWith(
|
|
color: theme.colorScheme.onSecondary,
|
|
),
|
|
),
|
|
if (_errorText.isNotEmpty) ...[
|
|
SizedBox(height: _titleSpacing),
|
|
Text(
|
|
_errorText,
|
|
style: theme.textTheme.bodySmall?.copyWith(
|
|
color: theme.colorScheme.error,
|
|
),
|
|
),
|
|
],
|
|
],
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|