fix: v2.0.4+24 — login fixes, signup toggle, forgot-password, guest SnackBar, Google OAuth
- Google Sign-In: wire serverClientId (639347358523-mtkm...apps.googleusercontent.com) so idToken is returned on Android - Email login: raise timeout 10s→25s, add single retry on SocketException/TimeoutException - Forgot Password: real glassmorphism bottom sheet with safe-degrade SnackBar (endpoint missing on backend) - Create Account: same-page AnimatedSwitcher toggle with glassmorphism signup form; delete old RegisterScreen - Desktop parity: DesktopLoginScreen same-page toggle; delete DesktopRegisterScreen - Guest mode: remove ScaffoldMessenger SnackBar from HomeScreen outer catch (inner _safe wrappers already return []) - LoginScreen: clearSnackBars() on postFrameCallback to prevent carried-over SnackBars from prior screens - ProGuard: add Google Sign-In + OkHttp keep rules - Version bump: 2.0.0+20 → 2.0.4+24; settings _appVersion → 2.0.4 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,12 +1,15 @@
|
||||
// lib/core/api/api_client.dart
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io' show SocketException;
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:http_parser/http_parser.dart';
|
||||
import '../storage/token_storage.dart';
|
||||
|
||||
class ApiClient {
|
||||
static const Duration _timeout = Duration(seconds: 10);
|
||||
static const Duration _timeout = Duration(seconds: 25);
|
||||
static const Duration _retryDelay = Duration(milliseconds: 600);
|
||||
// Set to true to enable mock/offline development mode (useful when backend is unavailable)
|
||||
static const bool _developmentMode = false;
|
||||
|
||||
@@ -28,13 +31,7 @@ class ApiClient {
|
||||
|
||||
late http.Response response;
|
||||
try {
|
||||
response = await http
|
||||
.post(
|
||||
Uri.parse(url),
|
||||
headers: headers,
|
||||
body: jsonEncode(finalBody),
|
||||
)
|
||||
.timeout(_timeout);
|
||||
response = await _postWithRetry(url, headers, finalBody);
|
||||
} catch (e) {
|
||||
if (kDebugMode) debugPrint('ApiClient.post network error: $e');
|
||||
|
||||
@@ -100,6 +97,32 @@ class ApiClient {
|
||||
return _handleResponse(url, response, finalBody);
|
||||
}
|
||||
|
||||
/// POST with one retry on transient network errors.
|
||||
/// Retries on SocketException / TimeoutException only.
|
||||
Future<http.Response> _postWithRetry(
|
||||
String url,
|
||||
Map<String, String> headers,
|
||||
Map<String, dynamic> body,
|
||||
) async {
|
||||
try {
|
||||
return await http
|
||||
.post(Uri.parse(url), headers: headers, body: jsonEncode(body))
|
||||
.timeout(_timeout);
|
||||
} on SocketException {
|
||||
if (kDebugMode) debugPrint('ApiClient.post retry after SocketException');
|
||||
await Future.delayed(_retryDelay);
|
||||
return await http
|
||||
.post(Uri.parse(url), headers: headers, body: jsonEncode(body))
|
||||
.timeout(_timeout);
|
||||
} on TimeoutException {
|
||||
if (kDebugMode) debugPrint('ApiClient.post retry after TimeoutException');
|
||||
await Future.delayed(_retryDelay);
|
||||
return await http
|
||||
.post(Uri.parse(url), headers: headers, body: jsonEncode(body))
|
||||
.timeout(_timeout);
|
||||
}
|
||||
}
|
||||
|
||||
/// Upload a single file as multipart/form-data.
|
||||
///
|
||||
/// Returns the `file` object from the server response:
|
||||
|
||||
Reference in New Issue
Block a user