diff --git a/b0esche_cloud/lib/services/api_client.dart b/b0esche_cloud/lib/services/api_client.dart index 22a73ca..9653355 100644 --- a/b0esche_cloud/lib/services/api_client.dart +++ b/b0esche_cloud/lib/services/api_client.dart @@ -15,7 +15,7 @@ class ApiClient { baseUrl: baseUrl, connectTimeout: const Duration(seconds: 10), receiveTimeout: const Duration( - seconds: 60, + seconds: 120, ), // Increased for file uploads and org operations ), ); diff --git a/b0esche_cloud/lib/widgets/account_settings_dialog.dart b/b0esche_cloud/lib/widgets/account_settings_dialog.dart index 45fb2bc..63b9d51 100644 --- a/b0esche_cloud/lib/widgets/account_settings_dialog.dart +++ b/b0esche_cloud/lib/widgets/account_settings_dialog.dart @@ -27,6 +27,7 @@ class _AccountSettingsDialogState extends State { // Profile fields late TextEditingController _displayNameController; + bool _hasChanges = false; String? _avatarUrl; // Security fields @@ -55,6 +56,14 @@ class _AccountSettingsDialogState extends State { } } }); + + // Listen for changes in display name + _displayNameController.addListener(() { + final newHasChanges = _displayNameController.text != (_currentUser?.displayName ?? ''); + if (_hasChanges != newHasChanges) { + setState(() => _hasChanges = newHasChanges); + } + }); } @override @@ -168,6 +177,8 @@ class _AccountSettingsDialogState extends State { const SnackBar(content: Text('Profile updated successfully')), ); + setState(() => _hasChanges = false); + // Close the dialog Navigator.of(context).pop(); } @@ -640,7 +651,7 @@ class _AccountSettingsDialogState extends State { child: SizedBox( width: 144, child: ModernGlassButton( - onPressed: _isLoading ? () {} : () => _updateProfile(), + onPressed: _isLoading || !_hasChanges ? null : () => _updateProfile(), isLoading: _isLoading, child: _isLoading ? const SizedBox( diff --git a/go_cloud/internal/http/routes.go b/go_cloud/internal/http/routes.go index ffffedc..df884f6 100644 --- a/go_cloud/internal/http/routes.go +++ b/go_cloud/internal/http/routes.go @@ -3848,7 +3848,7 @@ func getUserProfileHandler(w http.ResponseWriter, r *http.Request, db *database. // If avatar exists, return the backend URL instead of the internal WebDAV URL if user.AvatarURL != nil && *user.AvatarURL != "" { - user.AvatarURL = &[]string{"/user/avatar"}[0] + user.AvatarURL = &[]string{fmt.Sprintf("/user/avatar?v=%d", time.Now().Unix())}[0] } w.Header().Set("Content-Type", "application/json") @@ -4048,7 +4048,7 @@ func uploadUserAvatarHandler(w http.ResponseWriter, r *http.Request, db *databas // Get public URL - for now, construct it manually since Nextcloud doesn't provide direct public URLs // In a real setup, you'd configure Nextcloud to serve public URLs or use a CDN - publicURL := "/user/avatar" + publicURL := fmt.Sprintf("/user/avatar?v=%d", time.Now().Unix()) webdavURL := fmt.Sprintf("%s/%s", client.BaseURL, avatarPath) // Update user profile with avatar URL diff --git a/go_cloud/internal/storage/webdav.go b/go_cloud/internal/storage/webdav.go index 87bb727..0166f2e 100644 --- a/go_cloud/internal/storage/webdav.go +++ b/go_cloud/internal/storage/webdav.go @@ -42,7 +42,7 @@ func NewWebDAVClient(cfg *config.Config) *WebDAVClient { user: cfg.NextcloudUser, pass: cfg.NextcloudPass, basePrefix: strings.TrimRight(base, "/"), - httpClient: &http.Client{Timeout: 120 * time.Second}, + httpClient: &http.Client{Timeout: 30 * time.Second}, } }