This commit is contained in:
2026-07-13 01:38:13 +03:00
parent 9eda119527
commit 98c5a7a1d1
54 changed files with 860 additions and 454 deletions

View File

@@ -32,7 +32,7 @@ class ClientNotification {
required this.createdAt,
this.canceledAt,
this.readAt,
});
}) ;
ClientNotification copyWith({
int? id,

View File

@@ -0,0 +1,28 @@
class PaginationMeta {
final int total;
final int lastPage;
final int currentPage;
final int totalPerPage;
final int? prevPage;
final int? nextPage;
PaginationMeta({
required this.total,
required this.lastPage,
required this.currentPage,
required this.totalPerPage,
this.prevPage,
this.nextPage,
});
factory PaginationMeta.fromJson(Map<String, dynamic> json) {
return PaginationMeta(
total: json['total'] as int,
lastPage: json['lastPage'] as int,
currentPage: json['currentPage'] as int,
totalPerPage: json['totalPerPage'] as int,
prevPage: json['prevPage'] as int?,
nextPage: json['nextPage'] as int?,
);
}
}

View File

@@ -29,6 +29,9 @@ class ScooterOrder {
final String? cancelDescription;
final double mileage; //эту
final double avgSpeed; //эту и снизу еще 4 есть
final String payTotalPricePrint;
final String unpayTotalPricePrint;
final String totalOrderPricePrint;
ScooterOrder({
required this.id,
@@ -57,11 +60,16 @@ class ScooterOrder {
this.expiresAt,
this.cancelAt,
this.cancelDescription,
required this.mileage, //эту
required this.avgSpeed, //эту и снизу еще 2 есть
required this.mileage,
required this.avgSpeed,
required this.payTotalPricePrint, // оплачено
required this.unpayTotalPricePrint, //неоплачено
required this.totalOrderPricePrint, //общая
});
factory ScooterOrder.fromJson(Map<String, dynamic> json) {
final paymentInfo = json['paymentInfo'] as Map<String, dynamic>?;
return ScooterOrder(
id: json['id'] ?? 0,
scooterId: json['scooterId'] ?? 0,
@@ -101,8 +109,11 @@ class ScooterOrder {
? DateTime.parse(json['cancelAt'])
: null,
cancelDescription: json['cancelDescription'],
mileage: (json['mileage'] as num?)?.toDouble() ?? 0.0, //эту
avgSpeed: (json['avgSpeed'] as num?)?.toDouble() ?? 0.0, //эту
mileage: (json['mileage'] as num?)?.toDouble() ?? 0.0,
avgSpeed: (json['avgSpeed'] as num?)?.toDouble() ?? 0.0,
payTotalPricePrint: paymentInfo?['payTotalPricePrint'] ?? '',
unpayTotalPricePrint: paymentInfo?['unpayTotalPricePrint'] ?? '',
totalOrderPricePrint: paymentInfo?['totalOrderPricePrint'] ?? '',
);
}

View File

@@ -3,7 +3,7 @@ class UserProfile {
String birthDate;
String phone;
String email;
int? balance;
double? balance;
int? avatarId;
String? avatarUrl;
@@ -21,7 +21,7 @@ class UserProfile {
String? name,
String? birthDate,
String? email,
int? balance,
double? balance,
int? avatarId,
String? avatarUrl,
}) {

View File

@@ -1,5 +1,4 @@
import 'dart:convert';
import 'package:be_happy/domain/entities/point.dart';
class Zone {
@@ -9,7 +8,10 @@ class Zone {
final String type;
final bool isActive;
final String shapeType;
final List<Point> points;
// Трехмерная структура для поддержки MultiPolygon и дыр (holes):
// geometry[индекс_полигона][индекс_кольца][индекс_точки]
// индекс_кольца == 0 — внешняя граница, > 0 — внутренние вырезы (дыры)
final List<List<List<Point>>> geometry;
final String speedLimit;
Zone({
@@ -19,38 +21,84 @@ class Zone {
required this.type,
required this.isActive,
required this.shapeType,
required this.points,
required this.geometry,
required this.speedLimit,
});
// Геттер для обратной совместимости (возвращает внешнее кольцо первого полигона)
List<Point> get points {
if (geometry.isNotEmpty && geometry[0].isNotEmpty) {
return geometry[0][0];
}
return [];
}
factory Zone.fromJson(Map<String, dynamic> json) {
final zoneCoordinates = json['coordinates'] as Map<String, dynamic>? ?? {};
final String coordsString = zoneCoordinates['coordinates'] ?? '[]';
final String shapeType = zoneCoordinates['type'] ?? 'Polygon';
List<Point> points = [];
List<List<List<Point>>> parsedGeometry = [];
try {
final dynamic decoded = jsonDecode(coordsString);
if (decoded is List && decoded.isNotEmpty) {
List<dynamic> targetList = [];
if (shapeType == 'Polygon') {
// У полигона структура [[[lat, lon], ...]] -> уходим на 1 уровень вглубь
targetList = decoded[0] as List<dynamic>;
} else {
// У LineString структура [[lat, lon], ...] -> используем как есть
targetList = decoded;
}
// Структура: [[[lon, lat], [lon, lat], ...], [hole1], ...]
List<List<Point>> polygonRings = [];
for (var ring in decoded) {
if (ring is List) {
List<Point> ringPoints = [];
for (var coord in ring) {
if (coord is List && coord.length >= 2) {
// В GeoJSON сначала идет Longitude, затем Latitude
ringPoints.add(Point(
(coord[1] as num).toDouble(),
(coord[0] as num).toDouble(),
));
}
}
if (ringPoints.isNotEmpty) polygonRings.add(ringPoints);
}
}
if (polygonRings.isNotEmpty) parsedGeometry.add(polygonRings);
points = targetList.map((item) {
final List<dynamic> coords = item as List<dynamic>;
return Point(
(coords[1] as num).toDouble(),
(coords[0] as num).toDouble(),
);
}).toList();
} else if (shapeType == 'MultiPolygon') {
// Структура: [[[[lon, lat], ...]], [[[lon, lat], ...]]]
for (var poly in decoded) {
if (poly is List) {
List<List<Point>> polygonRings = [];
for (var ring in poly) {
if (ring is List) {
List<Point> ringPoints = [];
for (var coord in ring) {
if (coord is List && coord.length >= 2) {
ringPoints.add(Point(
(coord[1] as num).toDouble(),
(coord[0] as num).toDouble(),
));
}
}
if (ringPoints.isNotEmpty) polygonRings.add(ringPoints);
}
}
if (polygonRings.isNotEmpty) parsedGeometry.add(polygonRings);
}
}
} else {
// Fallback для LineString или плоских списков
List<Point> ringPoints = [];
for (var coord in decoded) {
if (coord is List && coord.length >= 2) {
ringPoints.add(Point(
(coord[1] as num).toDouble(),
(coord[0] as num).toDouble(),
));
}
}
if (ringPoints.isNotEmpty) parsedGeometry.add([ringPoints]);
}
}
} catch (e) {
print("PARSE ERROR for Zone ID ${json['id']}: $e");
@@ -64,11 +112,12 @@ class Zone {
isActive: json['isActive'] ?? false,
speedLimit: json['speedLimit'] ?? '',
shapeType: shapeType,
points: points,
geometry: parsedGeometry,
);
}
@override
String toString() {
return 'Zone{id: $id, title: $title, type: $type, points: $points}';
return 'Zone{id: $id, title: $title, type: $type, polygonsCount: ${geometry.length}}';
}
}
}

View File

@@ -1,5 +1,7 @@
import 'package:be_happy/domain/entities/client_notification.dart';
import '../usecase/get_notifications_usecase.dart';
abstract class NotificationRepository {
/// Устанавливает постоянное SSE-соединение и возвращает поток уведомлений
Stream<ClientNotification> getNotificationsStream();
@@ -11,5 +13,7 @@ abstract class NotificationRepository {
void closeStream();
/// получить список уведомлений
Future<List<ClientNotification>> getNotifications();
}
Future<NotificationsResult> getNotifications({
int page = 1,
int pageSize = 10,
});}

View File

@@ -27,10 +27,10 @@ abstract class ScooterRepository {
required bool isInsurance,
});
Future<Result<ScooterOrder>> startRide(int orderId);
Future<Result<ScooterOrder>> cancelRide(int orderId);
Future<Result<void>> cancelRide(int orderId);
Future<Result<ScooterOrder>> pauseRide(int orderId);
Future<Result<ScooterOrder>> resumeRide(int orderId);
Future<Result<ScooterOrder>> finishRide(int orderId, List<int> files);
Future<Result<void>> finishRide(int orderId, List<int> files);
Future<Result<void>> payRide(int orderId);
Future<Result<List<ScooterOrder>>> getClientOrders();
Future<Result<List<int>>> uploadScooterPhotos(List<File> images);

View File

@@ -7,7 +7,7 @@ class CancelRideUsecase {
CancelRideUsecase(this.repository);
Future<Result<ScooterOrder>> call(int orderId) {
Future<Result<void>> call(int orderId) {
return repository.cancelRide(orderId);
}
}

View File

@@ -7,7 +7,7 @@ class FinishRideUsecase {
FinishRideUsecase(this.repository);
Future<Result<ScooterOrder>> call(int orderId, List<int> files) {
Future<Result<void>> call(int orderId, List<int> files) {
return repository.finishRide(orderId, files);
}
}

View File

@@ -1,4 +1,5 @@
import '../entities/client_notification.dart';
import '../entities/pagination_meta.dart';
import '../repositories/notification_repository.dart';
class GetNotificationsUsecase {
@@ -6,7 +7,17 @@ class GetNotificationsUsecase {
GetNotificationsUsecase(this.repository);
Future<List<ClientNotification>> call() {
return repository.getNotifications();
Future<NotificationsResult> call({int page = 1, int pageSize = 10}) {
return repository.getNotifications(page: page, pageSize: pageSize);
}
}
class NotificationsResult {
final List<ClientNotification> notifications;
final PaginationMeta pagination;
NotificationsResult({
required this.notifications,
required this.pagination,
});
}