Fixes for Settings Page

This commit is contained in:
Arseni
2025-12-22 21:09:58 +03:00
parent 41abf723e6
commit 47ada0691c
25 changed files with 1126 additions and 270 deletions

View File

@@ -1,9 +1,12 @@
import 'package:flutter/material.dart';
//import 'package:provider/provider.dart';
import 'package:provider/provider.dart';
import 'package:image_picker/image_picker.dart';
import 'package:pweb/generated/i18n/app_localizations.dart';
import 'package:pshared/provider/account.dart';
import 'package:pweb/widgets/drawer/avatar.dart';
class AvatarTile extends StatefulWidget {
@@ -28,80 +31,106 @@ class _AvatarTileState extends State<AvatarTile> {
static const double _avatarSize = 96.0;
static const double _iconSize = 32.0;
static const double _titleSpacing = 4.0;
static const String _placeholderAsset = 'assets/images/avatar_placeholder.png';
bool _isHovering = false;
bool _isUploading = false;
String _errorText = '';
Future<void> _pickImage(AccountProvider provider) async {
if (_isUploading) return;
Future<void> _pickImage() async {
final picker = ImagePicker();
final file = await picker.pickImage(source: ImageSource.gallery);
if (file != null) {
debugPrint('Selected new avatar: ${file.path}');
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) {
final loc = AppLocalizations.of(context)!;
final safeUrl =
widget.avatarUrl?.trim().isNotEmpty == true ? widget.avatarUrl : null;
final theme = Theme.of(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: _pickImage,
child: Stack(
alignment: Alignment.center,
children: [
ClipOval(
child: safeUrl != null
? Image.network(
safeUrl,
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,
fit: BoxFit.cover,
errorBuilder: (_, _, _) => _buildPlaceholder(),
)
: _buildPlaceholder(),
),
if (_isHovering)
ClipOval(
child: Container(
width: _avatarSize,
height: _avatarSize,
color: theme.colorScheme.primary.withAlpha(90),
child: Icon(
Icons.camera_alt,
color: theme.colorScheme.onSecondary,
size: _iconSize,
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(
loc.avatarHint,
style: theme.textTheme.bodySmall?.copyWith(
color: theme.colorScheme.onSecondary,
),
),
],
);
}
Widget _buildPlaceholder() {
return Image.asset(
_placeholderAsset,
width: _avatarSize,
height: _avatarSize,
fit: BoxFit.cover,
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,
),
),
],
],
);
},
);
}
}