first commit
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
/// أدوات تنسيق الوقت والمسافة والسرعة
|
||||
class Formatters {
|
||||
/// Format seconds to HH:MM:SS or MM:SS
|
||||
static String time(int seconds) {
|
||||
int h = seconds ~/ 3600;
|
||||
int m = (seconds % 3600) ~/ 60;
|
||||
int s = seconds % 60;
|
||||
String pad(int n) => n.toString().padLeft(2, '0');
|
||||
if (h > 0) return '${pad(h)}:${pad(m)}:${pad(s)}';
|
||||
return '${pad(m)}:${pad(s)}';
|
||||
}
|
||||
|
||||
/// Format meters to km or m
|
||||
static String distance(double meters) {
|
||||
if (meters >= 1000) {
|
||||
return '${(meters / 1000).toStringAsFixed(2)} km';
|
||||
}
|
||||
return '${meters.toStringAsFixed(0)} m';
|
||||
}
|
||||
|
||||
/// Format distance short (e.g., "5.2")
|
||||
static String distanceKm(double meters) {
|
||||
return (meters / 1000).toStringAsFixed(2);
|
||||
}
|
||||
|
||||
/// Format speed (m/s) to pace (min/km)
|
||||
static String pace(double mps) {
|
||||
if (mps <= 0) return '--:--';
|
||||
double kmh = mps * 3.6;
|
||||
if (kmh <= 0) return '--:--';
|
||||
double paceMinKm = 60.0 / kmh;
|
||||
int min = paceMinKm.toInt();
|
||||
int sec = ((paceMinKm - min) * 60).toInt();
|
||||
return '${min.toString().padLeft(2, '0')}:${sec.toString().padLeft(2, '0')}';
|
||||
}
|
||||
|
||||
/// Format pace with unit
|
||||
static String paceWithUnit(double mps) {
|
||||
return '${pace(mps)} min/km';
|
||||
}
|
||||
|
||||
/// Format speed m/s to km/h
|
||||
static String speedKmh(double mps) {
|
||||
return '${(mps * 3.6).toStringAsFixed(1)} km/h';
|
||||
}
|
||||
|
||||
/// Format calories
|
||||
static String calories(double cal) {
|
||||
return '${cal.toStringAsFixed(0)} kcal';
|
||||
}
|
||||
|
||||
/// Format elevation
|
||||
static String elevation(double meters) {
|
||||
return '${meters.toStringAsFixed(0)} m';
|
||||
}
|
||||
|
||||
/// Format date
|
||||
static String date(DateTime dt) {
|
||||
final months = [
|
||||
'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
|
||||
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec',
|
||||
];
|
||||
return '${dt.day} ${months[dt.month - 1]} ${dt.year}';
|
||||
}
|
||||
|
||||
/// Format time of day
|
||||
static String timeOfDay(DateTime dt) {
|
||||
String pad(int n) => n.toString().padLeft(2, '0');
|
||||
return '${pad(dt.hour)}:${pad(dt.minute)}';
|
||||
}
|
||||
|
||||
/// Format date and time
|
||||
static String dateTime(DateTime dt) {
|
||||
return '${date(dt)} ${timeOfDay(dt)}';
|
||||
}
|
||||
|
||||
/// Format duration in human readable form
|
||||
static String durationHuman(int seconds) {
|
||||
if (seconds < 60) return '${seconds}s';
|
||||
if (seconds < 3600) return '${seconds ~/ 60}m ${seconds % 60}s';
|
||||
int h = seconds ~/ 3600;
|
||||
int m = (seconds % 3600) ~/ 60;
|
||||
return '${h}h ${m}m';
|
||||
}
|
||||
|
||||
/// Get workout type display name
|
||||
static String workoutType(String type) {
|
||||
switch (type) {
|
||||
case 'running':
|
||||
return '🏃 Running';
|
||||
case 'walking':
|
||||
return '🚶 Walking';
|
||||
default:
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
/// Format improvement percentage
|
||||
static String improvement(double percent) {
|
||||
String sign = percent >= 0 ? '+' : '';
|
||||
return '$sign${percent.toStringAsFixed(1)}%';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import 'dart:math';
|
||||
|
||||
/// أدوات حساب المسافات والاتجاهات الجغرافية
|
||||
class GeoUtils {
|
||||
static const double _earthRadiusMeters = 6371000;
|
||||
|
||||
/// Haversine formula - حساب المسافة بين نقطتين
|
||||
static double haversineDistance(
|
||||
double lat1, double lng1,
|
||||
double lat2, double lng2,
|
||||
) {
|
||||
final dLat = _toRad(lat2 - lat1);
|
||||
final dLng = _toRad(lng2 - lng1);
|
||||
final a = sin(dLat / 2) * sin(dLat / 2) +
|
||||
cos(_toRad(lat1)) * cos(_toRad(lat2)) *
|
||||
sin(dLng / 2) * sin(dLng / 2);
|
||||
final c = 2 * atan2(sqrt(a), sqrt(1 - a));
|
||||
return _earthRadiusMeters * c;
|
||||
}
|
||||
|
||||
/// Calculate bearing between two points (degrees from north)
|
||||
static double bearing(
|
||||
double lat1, double lng1,
|
||||
double lat2, double lng2,
|
||||
) {
|
||||
final dLng = _toRad(lng2 - lng1);
|
||||
final y = sin(dLng) * cos(_toRad(lat2));
|
||||
final x = cos(_toRad(lat1)) * sin(_toRad(lat2)) -
|
||||
sin(_toRad(lat1)) * cos(_toRad(lat2)) * cos(dLng);
|
||||
return (_toDeg(atan2(y, x)) + 360) % 360;
|
||||
}
|
||||
|
||||
/// Calculate speed between two points (m/s)
|
||||
static double speed(
|
||||
double lat1, double lng1, DateTime t1,
|
||||
double lat2, double lng2, DateTime t2,
|
||||
) {
|
||||
final dist = haversineDistance(lat1, lng1, lat2, lng2);
|
||||
final seconds = t2.difference(t1).inMilliseconds / 1000.0;
|
||||
if (seconds <= 0) return 0;
|
||||
return dist / seconds;
|
||||
}
|
||||
|
||||
static double _toRad(double deg) => deg * pi / 180;
|
||||
static double _toDeg(double rad) => rad * 180 / pi;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import 'dart:convert';
|
||||
import 'package:crypto/crypto.dart';
|
||||
|
||||
/// أداة توليد توقيع HMAC-SHA256 للمصادقة مع الباك إند
|
||||
class HmacUtility {
|
||||
/// Generate HMAC-SHA256 signature
|
||||
/// message = timestamp|api_key|payload
|
||||
static String generateSignature({
|
||||
required String apiKey,
|
||||
required String apiSecret,
|
||||
required String payload,
|
||||
required int timestamp,
|
||||
}) {
|
||||
final message = '$timestamp|$apiKey|$payload';
|
||||
final hmac = Hmac(sha256, utf8.encode(apiSecret));
|
||||
final digest = hmac.convert(utf8.encode(message));
|
||||
return digest.toString();
|
||||
}
|
||||
|
||||
/// Build authenticated headers for API requests
|
||||
static Map<String, String> buildHeaders({
|
||||
required String apiKey,
|
||||
required String apiSecret,
|
||||
required String payload,
|
||||
}) {
|
||||
final timestamp = DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
||||
final signature = generateSignature(
|
||||
apiKey: apiKey,
|
||||
apiSecret: apiSecret,
|
||||
payload: payload,
|
||||
timestamp: timestamp,
|
||||
);
|
||||
|
||||
return {
|
||||
'Content-Type': 'application/json',
|
||||
'X-API-Key': apiKey,
|
||||
'X-Signature': signature,
|
||||
'X-Timestamp': timestamp.toString(),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
import '../models/gps_coordinate.dart';
|
||||
|
||||
/// أداة ترميز وفك ترميز Polyline
|
||||
/// تستخدم خوارزمية Google Encoded Polyline لضغط المسارات بنسبة ~95%
|
||||
class PolylineUtility {
|
||||
/// Encode a list of GPS coordinates to a polyline string
|
||||
static String encode(List<GpsCoordinate> coordinates) {
|
||||
if (coordinates.isEmpty) return '';
|
||||
|
||||
final buffer = StringBuffer();
|
||||
int prevLat = 0;
|
||||
int prevLng = 0;
|
||||
|
||||
for (final point in coordinates) {
|
||||
int lat = (point.latitude * 1e5).round();
|
||||
int lng = (point.longitude * 1e5).round();
|
||||
|
||||
_encodeValue(lat - prevLat, buffer);
|
||||
_encodeValue(lng - prevLng, buffer);
|
||||
|
||||
prevLat = lat;
|
||||
prevLng = lng;
|
||||
}
|
||||
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
/// Decode a polyline string back to coordinates
|
||||
static List<GpsCoordinate> decode(String encoded) {
|
||||
if (encoded.isEmpty) return [];
|
||||
|
||||
final coordinates = <GpsCoordinate>[];
|
||||
int index = 0;
|
||||
int lat = 0;
|
||||
int lng = 0;
|
||||
|
||||
while (index < encoded.length) {
|
||||
lat += _decodeValue(encoded, index);
|
||||
index = _lastIndex;
|
||||
|
||||
if (index >= encoded.length) break;
|
||||
|
||||
lng += _decodeValue(encoded, index);
|
||||
index = _lastIndex;
|
||||
|
||||
coordinates.add(GpsCoordinate(
|
||||
latitude: lat / 1e5,
|
||||
longitude: lng / 1e5,
|
||||
timestamp: DateTime.now(), // Polylines don't store timestamps
|
||||
));
|
||||
}
|
||||
|
||||
return coordinates;
|
||||
}
|
||||
|
||||
// Internal tracking for decode position
|
||||
static int _lastIndex = 0;
|
||||
|
||||
static void _encodeValue(int value, StringBuffer buffer) {
|
||||
int v = value < 0 ? ~(value << 1) : (value << 1);
|
||||
while (v >= 0x20) {
|
||||
buffer.writeCharCode((0x20 | (v & 0x1f)) + 63);
|
||||
v >>= 5;
|
||||
}
|
||||
buffer.writeCharCode(v + 63);
|
||||
}
|
||||
|
||||
static int _decodeValue(String encoded, int startIndex) {
|
||||
int result = 0;
|
||||
int shift = 0;
|
||||
int index = startIndex;
|
||||
|
||||
while (index < encoded.length) {
|
||||
int byte = encoded.codeUnitAt(index) - 63;
|
||||
index++;
|
||||
result |= (byte & 0x1f) << shift;
|
||||
shift += 5;
|
||||
if (byte < 0x20) break;
|
||||
}
|
||||
|
||||
_lastIndex = index;
|
||||
return (result & 1) != 0 ? ~(result >> 1) : (result >> 1);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user