Connect Flutter phone auth and Android platform

This commit is contained in:
Hamza-Ayed
2026-10-04 01:44:07 +03:00
parent 5b90da6b18
commit 98490a17e6
44 changed files with 1271 additions and 168 deletions
+18 -63
View File
@@ -3,17 +3,15 @@ import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:get/get.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:http/http.dart' as http;
import '../models/workout.dart';
import '../utils/hmac_utility.dart';
import 'auth_service.dart';
import 'database_service.dart';
/// محرك المزامنة التلقائي
/// يراقب الاتصال بالإنترنت ويرسل التمارين المحفوظة محلياً عند توفر الشبكة
class SyncService extends GetxService {
final DatabaseService _db = Get.find<DatabaseService>();
final _storage = const FlutterSecureStorage();
final AuthService _auth = Get.find<AuthService>();
final _connectivity = Connectivity();
StreamSubscription? _connectivitySub;
@@ -27,10 +25,6 @@ class SyncService extends GetxService {
bool get isOnline => _isOnline.value;
int get pendingCount => _pendingCount.value;
static const String _apiEndpointKey = 'api_endpoint';
static const String _apiKeyKey = 'api_key';
static const String _apiSecretKey = 'api_secret';
Future<SyncService> init() async {
await _checkConnectivity();
_startConnectivityMonitor();
@@ -73,44 +67,20 @@ class SyncService extends GetxService {
);
}
// ─── Credentials Management ───────────────────────────────────
Future<void> saveCredentials({
required String apiEndpoint,
required String apiKey,
required String apiSecret,
}) async {
await _storage.write(key: _apiEndpointKey, value: apiEndpoint);
await _storage.write(key: _apiKeyKey, value: apiKey);
await _storage.write(key: _apiSecretKey, value: apiSecret);
}
Future<Map<String, String?>> getCredentials() async {
return {
'endpoint': await _storage.read(key: _apiEndpointKey),
'apiKey': await _storage.read(key: _apiKeyKey),
'apiSecret': await _storage.read(key: _apiSecretKey),
};
}
Future<bool> hasCredentials() async {
final creds = await getCredentials();
return creds['apiKey'] != null &&
creds['apiKey']!.isNotEmpty &&
creds['apiSecret'] != null &&
creds['apiSecret']!.isNotEmpty;
}
// ─── Sync Operations ─────────────────────────────────────────
/// Queue a workout for sync
Future<void> queueWorkout(Workout workout) async {
final payload = jsonEncode(workout.toApiPayload());
await _db.saveCompletedWorkout(workout, payload);
final queueForSync = workout.coordinates.length >= 2 &&
workout.routePolyline != null &&
workout.routePolyline!.isNotEmpty;
await _db.saveCompletedWorkout(workout, payload,
queueForSync: queueForSync);
await _updatePendingCount();
// Try immediate sync if online
if (_isOnline.value) {
if (queueForSync && _isOnline.value) {
syncPendingWorkouts();
}
}
@@ -124,32 +94,29 @@ class SyncService extends GetxService {
}
/// Process all pending sync items
Future<void> syncPendingWorkouts() async {
Future<void> syncPendingWorkouts({bool retryNow = false}) async {
if (_isSyncing.value) return;
_isSyncing.value = true;
try {
final creds = await getCredentials();
if (creds['endpoint'] == null || creds['apiKey'] == null ||
creds['apiSecret'] == null) {
debugPrint('[SYNC] No API credentials configured');
if (!_auth.isSignedIn) {
debugPrint('[SYNC] Sign in to sync saved workouts');
return;
}
final pendingItems = await _db.getPendingSyncs();
final pendingItems = await _db.getPendingSyncs(
includeExhausted: retryNow,
);
if (pendingItems.isEmpty) return;
debugPrint('[SYNC] Processing ${pendingItems.length} pending items');
for (final item in pendingItems) {
if (!_isRetryDue(item)) continue;
if (!retryNow && !_isRetryDue(item)) continue;
await _syncSingleItem(
id: item['id'] as int,
workoutId: item['workout_id'] as String,
payload: item['payload'] as String,
endpoint: creds['endpoint']!,
apiKey: creds['apiKey']!,
apiSecret: creds['apiSecret']!,
);
}
@@ -166,25 +133,13 @@ class SyncService extends GetxService {
required int id,
required String workoutId,
required String payload,
required String endpoint,
required String apiKey,
required String apiSecret,
}) async {
try {
final headers = HmacUtility.buildHeaders(
apiKey: apiKey,
apiSecret: apiSecret,
payload: payload,
final response = await _auth.authenticatedPost(
Uri.parse('${_auth.apiBase}/workouts.php'),
payload,
);
final response = await http
.post(
Uri.parse('$endpoint/api/v1/workouts'),
headers: headers,
body: payload,
)
.timeout(const Duration(seconds: 30));
if (response.statusCode == 200 || response.statusCode == 201) {
await _db.updateSyncQueueItem(id, 'sent');
await _db.updateSyncStatus(