Enhance avatar upload functionality with progress tracking in AccountSettingsDialog
This commit is contained in:
@@ -208,8 +208,9 @@ class ApiClient {
|
||||
// Avatar upload
|
||||
Future<Map<String, dynamic>> uploadAvatar(
|
||||
List<int> imageBytes,
|
||||
String filename,
|
||||
) async {
|
||||
String filename, {
|
||||
ProgressCallback? onSendProgress,
|
||||
}) async {
|
||||
final formData = FormData.fromMap({
|
||||
'avatar': MultipartFile.fromBytes(
|
||||
imageBytes,
|
||||
@@ -219,7 +220,11 @@ class ApiClient {
|
||||
});
|
||||
|
||||
try {
|
||||
final response = await _dio.post('/user/avatar', data: formData);
|
||||
final response = await _dio.post(
|
||||
'/user/avatar',
|
||||
data: formData,
|
||||
onSendProgress: onSendProgress,
|
||||
);
|
||||
return response.data;
|
||||
} on DioException catch (e) {
|
||||
throw _handleError(e);
|
||||
|
||||
@@ -21,6 +21,8 @@ class AccountSettingsDialog extends StatefulWidget {
|
||||
class _AccountSettingsDialogState extends State<AccountSettingsDialog> {
|
||||
int _selectedTabIndex = 0;
|
||||
bool _isLoading = false;
|
||||
bool _isUploadingAvatar = false;
|
||||
double _avatarUploadProgress = 0.0;
|
||||
String? _error;
|
||||
|
||||
// Profile fields
|
||||
@@ -78,16 +80,28 @@ class _AccountSettingsDialogState extends State<AccountSettingsDialog> {
|
||||
final filename = file.name;
|
||||
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
_isUploadingAvatar = true;
|
||||
_avatarUploadProgress = 0.0;
|
||||
});
|
||||
|
||||
try {
|
||||
final apiClient = GetIt.I<ApiClient>();
|
||||
final response = await apiClient.uploadAvatar(bytes, filename);
|
||||
final response = await apiClient.uploadAvatar(
|
||||
bytes,
|
||||
filename,
|
||||
onSendProgress: (sent, total) {
|
||||
if (total != -1 && mounted) {
|
||||
setState(() {
|
||||
_avatarUploadProgress = sent / total;
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
setState(() {
|
||||
_avatarUrl = response['avatarUrl'] as String?;
|
||||
_isLoading = false;
|
||||
_isUploadingAvatar = false;
|
||||
_avatarUploadProgress = 0.0;
|
||||
});
|
||||
|
||||
if (mounted) {
|
||||
@@ -96,7 +110,10 @@ class _AccountSettingsDialogState extends State<AccountSettingsDialog> {
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
setState(() => _isLoading = false);
|
||||
setState(() {
|
||||
_isUploadingAvatar = false;
|
||||
_avatarUploadProgress = 0.0;
|
||||
});
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Failed to upload avatar: $e')),
|
||||
@@ -510,38 +527,62 @@ class _AccountSettingsDialogState extends State<AccountSettingsDialog> {
|
||||
children: [
|
||||
const SizedBox(height: 48),
|
||||
GestureDetector(
|
||||
onTap: _pickAndUploadAvatar,
|
||||
child: CircleAvatar(
|
||||
radius: 50,
|
||||
backgroundColor: AppTheme.secondaryText.withValues(
|
||||
alpha: 0.2,
|
||||
),
|
||||
child: _avatarUrl != null
|
||||
? ClipOval(
|
||||
child: Image.network(
|
||||
_avatarUrl!,
|
||||
fit: BoxFit.cover,
|
||||
width: 100,
|
||||
height: 100,
|
||||
errorBuilder: (context, error, stackTrace) {
|
||||
return Icon(
|
||||
Icons.person,
|
||||
size: 50,
|
||||
color: AppTheme.secondaryText,
|
||||
);
|
||||
},
|
||||
onTap: _isUploadingAvatar ? null : _pickAndUploadAvatar,
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
CircleAvatar(
|
||||
radius: 50,
|
||||
backgroundColor: AppTheme.secondaryText.withValues(
|
||||
alpha: 0.2,
|
||||
),
|
||||
child: _avatarUrl != null
|
||||
? ClipOval(
|
||||
child: Image.network(
|
||||
_avatarUrl!,
|
||||
fit: BoxFit.cover,
|
||||
width: 100,
|
||||
height: 100,
|
||||
errorBuilder:
|
||||
(context, error, stackTrace) {
|
||||
return Icon(
|
||||
Icons.person,
|
||||
size: 50,
|
||||
color: AppTheme.secondaryText,
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
: Icon(
|
||||
Icons.person,
|
||||
size: 50,
|
||||
color: AppTheme.secondaryText,
|
||||
),
|
||||
),
|
||||
if (_isUploadingAvatar)
|
||||
SizedBox(
|
||||
width: 110,
|
||||
height: 110,
|
||||
child: CircularProgressIndicator(
|
||||
value: _avatarUploadProgress > 0
|
||||
? _avatarUploadProgress
|
||||
: null,
|
||||
strokeWidth: 4,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
AppTheme.accentColor,
|
||||
),
|
||||
)
|
||||
: Icon(
|
||||
Icons.person,
|
||||
size: 50,
|
||||
color: AppTheme.secondaryText,
|
||||
backgroundColor: AppTheme.secondaryText
|
||||
.withValues(alpha: 0.2),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Tap to change avatar',
|
||||
_isUploadingAvatar
|
||||
? 'Uploading avatar...'
|
||||
: 'Tap to change avatar',
|
||||
style: TextStyle(
|
||||
color: AppTheme.secondaryText,
|
||||
fontSize: 12,
|
||||
|
||||
Reference in New Issue
Block a user