mirror of
https://github.com/we-promise/sure.git
synced 2026-04-10 07:44:48 +00:00
* feat: Add API key login option to mobile app Add a "Via API Key Login" button on the login screen that opens a dialog for entering an API key. The API key is validated by making a test request to /api/v1/accounts with the X-Api-Key header, and on success is persisted in secure storage. All HTTP services now use a centralized ApiConfig.getAuthHeaders() helper that returns the correct auth header (X-Api-Key or Bearer) based on the current auth mode. https://claude.ai/code/session_01DnyCzdMjVpSsbBZK3XbzUH * fix: Improve API key dialog context handling and controller disposal - Use outer context for SnackBar so it displays on the main screen instead of behind the dialog - Explicitly dispose TextEditingController to prevent memory leaks - Close dialog on failure before showing error SnackBar for better UX - Avoid StatefulBuilder context parameter shadowing https://claude.ai/code/session_01DnyCzdMjVpSsbBZK3XbzUH * fix: Use user-friendly error message in API key login catch block Log the technical exception details via LogService.instance.error and show a generic "Unable to connect" message to the user instead of exposing the raw exception string. https://claude.ai/code/session_01DnyCzdMjVpSsbBZK3XbzUH --------- Co-authored-by: Claude <noreply@anthropic.com>
70 lines
2.0 KiB
Dart
70 lines
2.0 KiB
Dart
import 'package:shared_preferences/shared_preferences.dart';
|
|
|
|
class ApiConfig {
|
|
// Base URL for the API - can be changed to point to different environments
|
|
// For local development, use: http://10.0.2.2:3000 (Android emulator)
|
|
// For iOS simulator, use: http://localhost:3000
|
|
// For production, use your actual server URL
|
|
static String _baseUrl = 'http://10.0.2.2:3000';
|
|
|
|
static String get baseUrl => _baseUrl;
|
|
|
|
static void setBaseUrl(String url) {
|
|
_baseUrl = url;
|
|
}
|
|
|
|
// API key authentication mode
|
|
static bool _isApiKeyAuth = false;
|
|
static String? _apiKeyValue;
|
|
|
|
static bool get isApiKeyAuth => _isApiKeyAuth;
|
|
|
|
static void setApiKeyAuth(String apiKey) {
|
|
_isApiKeyAuth = true;
|
|
_apiKeyValue = apiKey;
|
|
}
|
|
|
|
static void clearApiKeyAuth() {
|
|
_isApiKeyAuth = false;
|
|
_apiKeyValue = null;
|
|
}
|
|
|
|
/// Returns the correct auth headers based on the current auth mode.
|
|
/// In API key mode, uses X-Api-Key header.
|
|
/// In token mode, uses Authorization: Bearer header.
|
|
static Map<String, String> getAuthHeaders(String token) {
|
|
if (_isApiKeyAuth && _apiKeyValue != null) {
|
|
return {
|
|
'X-Api-Key': _apiKeyValue!,
|
|
'Accept': 'application/json',
|
|
};
|
|
}
|
|
return {
|
|
'Authorization': 'Bearer $token',
|
|
'Accept': 'application/json',
|
|
};
|
|
}
|
|
|
|
/// Initialize the API configuration by loading the backend URL from storage
|
|
/// Returns true if a saved URL was loaded, false otherwise
|
|
static Future<bool> initialize() async {
|
|
try {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
final savedUrl = prefs.getString('backend_url');
|
|
|
|
if (savedUrl != null && savedUrl.isNotEmpty) {
|
|
_baseUrl = savedUrl;
|
|
return true;
|
|
}
|
|
return false;
|
|
} catch (e) {
|
|
// If initialization fails, keep the default URL
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// API timeout settings
|
|
static const Duration connectTimeout = Duration(seconds: 30);
|
|
static const Duration receiveTimeout = Duration(seconds: 30);
|
|
}
|