bug fixes
This commit is contained in:
@@ -498,6 +498,35 @@ class ApiService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<ZonesResponse?> getAllZones({
|
||||||
|
int page = 1,
|
||||||
|
int pageSize = 500,
|
||||||
|
}) async {
|
||||||
|
const path = "$baseUrl/zone/available2";
|
||||||
|
|
||||||
|
final queryParams = {
|
||||||
|
'readAll': 'true',
|
||||||
|
'page': page,
|
||||||
|
'pageSize': pageSize,
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
final response = await _dio.get(
|
||||||
|
path,
|
||||||
|
queryParameters: queryParams,
|
||||||
|
options: await _getAuthOptions(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
return ZonesResponse.fromJson(response.data);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
} catch (e) {
|
||||||
|
print("ERROR: getZones failed: $e");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<TariffsResponse?> getAvailableTariffs({required int scooterId}) async {
|
Future<TariffsResponse?> getAvailableTariffs({required int scooterId}) async {
|
||||||
final url = "$baseUrl/scooterplan/$scooterId/available";
|
final url = "$baseUrl/scooterplan/$scooterId/available";
|
||||||
|
|
||||||
|
|||||||
@@ -10,60 +10,60 @@ class ZoneRepositoryImpl extends ZoneRepository {
|
|||||||
ZoneRepositoryImpl(this._apiService);
|
ZoneRepositoryImpl(this._apiService);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<Zone>> getZones(List<double> area,
|
Future<List<Zone>> getZones(List<double> area, int page, int pageSize) async {
|
||||||
int page,
|
|
||||||
int pageSize,) async {
|
|
||||||
|
|
||||||
final List<Point> list = [];
|
final List<Point> list = [];
|
||||||
final List<Point> list2 = [];
|
final List<Point> list2 = [];
|
||||||
|
|
||||||
/*list.add(Point(55.182372,30.203347));
|
|
||||||
list.add(Point(55.173746,30.200257));
|
|
||||||
list.add(Point(55.172153,30.212531));
|
|
||||||
list.add(Point(55.179946,30.215964));
|
|
||||||
|
|
||||||
list2.add(Point(55.178304,30.227380));
|
|
||||||
list2.add(Point(55.178059,30.229654));
|
|
||||||
list2.add(Point(55.191339,30.234718));
|
|
||||||
list2.add(Point(55.194352,30.234890));
|
|
||||||
list2.add(Point(55.194377,30.231972));
|
|
||||||
list2.add(Point(55.192123,30.232744));
|
|
||||||
|
|
||||||
final zone = Zone(id: 123,
|
|
||||||
title: "Zone 01",
|
|
||||||
description: "description",
|
|
||||||
type: "Finish",
|
|
||||||
isActive: true,
|
|
||||||
shapeType: "polygon",
|
|
||||||
points: list,
|
|
||||||
speedLimit: "speedLimit");
|
|
||||||
|
|
||||||
final zone2 = Zone(id: 124,
|
|
||||||
title: "Zone 02",
|
|
||||||
description: "description",
|
|
||||||
type: "NotDrive",
|
|
||||||
isActive: true,
|
|
||||||
shapeType: "polygon",
|
|
||||||
points: list2,
|
|
||||||
speedLimit: "speedLimit");
|
|
||||||
*/
|
|
||||||
|
|
||||||
final responce = await _apiService.getZones(
|
final responce = await _apiService.getZones(
|
||||||
area: area,
|
area: area,
|
||||||
page: page,
|
page: page,
|
||||||
pageSize: pageSize,
|
pageSize: pageSize,
|
||||||
);
|
);
|
||||||
|
|
||||||
final List<Zone>? zones = responce?.zones;
|
if (responce == null) {
|
||||||
// zones?.add(zone);
|
throw Exception(
|
||||||
// zones?.add(zone2);
|
'Failed to load zones for area: empty response from server',
|
||||||
|
);
|
||||||
print("Scooters: ${zones?.first.title}");
|
|
||||||
|
|
||||||
if (responce != null) {
|
|
||||||
return zones ?? List.empty();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return List.empty();
|
final zones = responce.zones;
|
||||||
|
|
||||||
|
print("Zones: ${zones.length}");
|
||||||
|
|
||||||
|
return zones;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<Zone>> getAllZones(int page, int pageSize) async {
|
||||||
|
final List<Point> list = [];
|
||||||
|
final List<Point> list2 = [];
|
||||||
|
|
||||||
|
final responce = await _apiService.getAllZones(
|
||||||
|
page: page,
|
||||||
|
pageSize: pageSize,
|
||||||
|
);
|
||||||
|
|
||||||
|
// apiService.getAllZones ловит свои ошибки (сеть/401/5xx) и в этом
|
||||||
|
// случае возвращает null. Раньше это тихо превращалось в "зон нет" —
|
||||||
|
// из-за чего сбой запроса выглядел как легитимный пустой ответ и
|
||||||
|
// (в связке с кешем на уровне MapBloc) навсегда "прятал" зоны на
|
||||||
|
// время сессии. Бросаем исключение, чтобы вызывающий код (usecase/bloc)
|
||||||
|
// мог отличить реальный сбой от честного "зон действительно нет"
|
||||||
|
// и, при необходимости, повторить запрос.
|
||||||
|
if (responce == null) {
|
||||||
|
throw Exception('Failed to load zones: empty response from server');
|
||||||
|
}
|
||||||
|
|
||||||
|
final zones = responce.zones;
|
||||||
|
|
||||||
|
print(zones.map((z) => '${z.type}:${z.id}'));
|
||||||
|
|
||||||
|
for (final z in zones) {
|
||||||
|
print("Zone ${z.type}:${z.id} geometry.length=${z.geometry.length}");
|
||||||
|
}
|
||||||
|
|
||||||
|
print("Zones: ${zones.length}");
|
||||||
|
|
||||||
|
return zones;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import 'package:be_happy/domain/repositories/zone_repository.dart';
|
|||||||
import 'package:be_happy/domain/service/security_service.dart';
|
import 'package:be_happy/domain/service/security_service.dart';
|
||||||
import 'package:be_happy/domain/usecase/add_payment_card_usecase.dart'; // ← новый
|
import 'package:be_happy/domain/usecase/add_payment_card_usecase.dart'; // ← новый
|
||||||
import 'package:be_happy/domain/usecase/delete_profile_usecase.dart';
|
import 'package:be_happy/domain/usecase/delete_profile_usecase.dart';
|
||||||
|
import 'package:be_happy/domain/usecase/get_all_zones_usecase.dart';
|
||||||
import 'package:be_happy/domain/usecase/get_certificates_usecase.dart';
|
import 'package:be_happy/domain/usecase/get_certificates_usecase.dart';
|
||||||
import 'package:be_happy/domain/usecase/get_scooter_order_route_history_usecase.dart';
|
import 'package:be_happy/domain/usecase/get_scooter_order_route_history_usecase.dart';
|
||||||
import 'package:be_happy/domain/usecase/is_pin_set_usecase.dart';
|
import 'package:be_happy/domain/usecase/is_pin_set_usecase.dart';
|
||||||
@@ -239,6 +240,9 @@ Future<void> setupDependencies() async {
|
|||||||
getIt.registerSingleton<GetAvailableZonesUsecase>(
|
getIt.registerSingleton<GetAvailableZonesUsecase>(
|
||||||
GetAvailableZonesUsecase(getIt()),
|
GetAvailableZonesUsecase(getIt()),
|
||||||
);
|
);
|
||||||
|
getIt.registerSingleton<GetAllZonesUsecase>(
|
||||||
|
GetAllZonesUsecase(getIt()),
|
||||||
|
);
|
||||||
getIt.registerSingleton<GetMapSettingsUsecase>(
|
getIt.registerSingleton<GetMapSettingsUsecase>(
|
||||||
GetMapSettingsUsecase(getIt()),
|
GetMapSettingsUsecase(getIt()),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ class Tariff {
|
|||||||
final int id;
|
final int id;
|
||||||
final String title;
|
final String title;
|
||||||
final String description;
|
final String description;
|
||||||
final String descriptionJson;
|
|
||||||
final bool isActive;
|
final bool isActive;
|
||||||
final String currency;
|
final String currency;
|
||||||
final double holdPrice; // Старт / бронь
|
final double holdPrice; // Старт / бронь
|
||||||
@@ -16,7 +15,6 @@ class Tariff {
|
|||||||
required this.id,
|
required this.id,
|
||||||
required this.title,
|
required this.title,
|
||||||
required this.description,
|
required this.description,
|
||||||
required this.descriptionJson,
|
|
||||||
required this.isActive,
|
required this.isActive,
|
||||||
required this.currency,
|
required this.currency,
|
||||||
required this.holdPrice,
|
required this.holdPrice,
|
||||||
@@ -35,7 +33,6 @@ class Tariff {
|
|||||||
id: json['id'] ?? 0,
|
id: json['id'] ?? 0,
|
||||||
title: json['title'] ?? 'Unknown',
|
title: json['title'] ?? 'Unknown',
|
||||||
description: json['description'] ?? '',
|
description: json['description'] ?? '',
|
||||||
descriptionJson: json['descriptionJson'] ?? '',
|
|
||||||
isActive: json['isActive'] ?? false,
|
isActive: json['isActive'] ?? false,
|
||||||
currency: currency['currency'] ?? 'BYN',
|
currency: currency['currency'] ?? 'BYN',
|
||||||
holdPrice: (planPrice['hold'] as num?)?.toDouble() ?? 0.0,
|
holdPrice: (planPrice['hold'] as num?)?.toDouble() ?? 0.0,
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
|
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:be_happy/domain/entities/point.dart';
|
import 'package:be_happy/domain/entities/point.dart';
|
||||||
|
|
||||||
class Zone {
|
class Zone {
|
||||||
@@ -8,9 +10,6 @@ class Zone {
|
|||||||
final String type;
|
final String type;
|
||||||
final bool isActive;
|
final bool isActive;
|
||||||
final String shapeType;
|
final String shapeType;
|
||||||
// Трехмерная структура для поддержки MultiPolygon и дыр (holes):
|
|
||||||
// geometry[индекс_полигона][индекс_кольца][индекс_точки]
|
|
||||||
// индекс_кольца == 0 — внешняя граница, > 0 — внутренние вырезы (дыры)
|
|
||||||
final List<List<List<Point>>> geometry;
|
final List<List<List<Point>>> geometry;
|
||||||
final String speedLimit;
|
final String speedLimit;
|
||||||
|
|
||||||
@@ -25,7 +24,6 @@ class Zone {
|
|||||||
required this.speedLimit,
|
required this.speedLimit,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Геттер для обратной совместимости (возвращает внешнее кольцо первого полигона)
|
|
||||||
List<Point> get points {
|
List<Point> get points {
|
||||||
if (geometry.isNotEmpty && geometry[0].isNotEmpty) {
|
if (geometry.isNotEmpty && geometry[0].isNotEmpty) {
|
||||||
return geometry[0][0];
|
return geometry[0][0];
|
||||||
@@ -45,51 +43,54 @@ class Zone {
|
|||||||
|
|
||||||
if (decoded is List && decoded.isNotEmpty) {
|
if (decoded is List && decoded.isNotEmpty) {
|
||||||
if (shapeType == 'Polygon') {
|
if (shapeType == 'Polygon') {
|
||||||
// Структура: [[[lon, lat], [lon, lat], ...], [hole1], ...]
|
final polygonRings = _parseRings(decoded);
|
||||||
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);
|
if (polygonRings.isNotEmpty) parsedGeometry.add(polygonRings);
|
||||||
|
|
||||||
} else if (shapeType == 'MultiPolygon') {
|
} else if (shapeType == 'MultiPolygon') {
|
||||||
// Структура: [[[[lon, lat], ...]], [[[lon, lat], ...]]]
|
|
||||||
for (var poly in decoded) {
|
for (var poly in decoded) {
|
||||||
if (poly is List) {
|
if (poly is List) {
|
||||||
List<List<Point>> polygonRings = [];
|
final polygonRings = _parseRings(poly);
|
||||||
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);
|
if (polygonRings.isNotEmpty) parsedGeometry.add(polygonRings);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Fallback для LineString или плоских списков
|
final singleRing = _parseCoordinateList(decoded);
|
||||||
|
if (singleRing.isNotEmpty) parsedGeometry.add([singleRing]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e, stackTrace) {
|
||||||
|
// Replace with your project logger (e.g., debugPrint or Logger)
|
||||||
|
assert(() {
|
||||||
|
print("PARSE ERROR for Zone ID ${json['id']}: $e\n$stackTrace");
|
||||||
|
return true;
|
||||||
|
}());
|
||||||
|
}
|
||||||
|
|
||||||
|
return Zone(
|
||||||
|
id: json['id'] as int? ?? 0,
|
||||||
|
title: json['title'] as String? ?? 'Unknown',
|
||||||
|
description: json['description'] as String? ?? '',
|
||||||
|
type: json['type'] as String? ?? '',
|
||||||
|
isActive: json['isActive'] as bool? ?? false,
|
||||||
|
speedLimit: json['speedLimit'] as String? ?? '',
|
||||||
|
shapeType: shapeType,
|
||||||
|
geometry: parsedGeometry,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<List<Point>> _parseRings(List rawRings) {
|
||||||
|
List<List<Point>> polygonRings = [];
|
||||||
|
for (var ring in rawRings) {
|
||||||
|
if (ring is List) {
|
||||||
|
final ringPoints = _parseCoordinateList(ring);
|
||||||
|
if (ringPoints.isNotEmpty) polygonRings.add(ringPoints);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return polygonRings;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<Point> _parseCoordinateList(List rawCoords) {
|
||||||
List<Point> ringPoints = [];
|
List<Point> ringPoints = [];
|
||||||
for (var coord in decoded) {
|
for (var coord in rawCoords) {
|
||||||
if (coord is List && coord.length >= 2) {
|
if (coord is List && coord.length >= 2) {
|
||||||
ringPoints.add(Point(
|
ringPoints.add(Point(
|
||||||
(coord[1] as num).toDouble(),
|
(coord[1] as num).toDouble(),
|
||||||
@@ -97,23 +98,7 @@ class Zone {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (ringPoints.isNotEmpty) parsedGeometry.add([ringPoints]);
|
return ringPoints;
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
print("PARSE ERROR for Zone ID ${json['id']}: $e");
|
|
||||||
}
|
|
||||||
|
|
||||||
return Zone(
|
|
||||||
id: json['id'] ?? 0,
|
|
||||||
title: json['title'] ?? 'Unknown',
|
|
||||||
description: json['description'] ?? '',
|
|
||||||
type: json['type'] ?? '',
|
|
||||||
isActive: json['isActive'] ?? false,
|
|
||||||
speedLimit: json['speedLimit'] ?? '',
|
|
||||||
shapeType: shapeType,
|
|
||||||
geometry: parsedGeometry,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -2,5 +2,6 @@ import '../entities/zone.dart';
|
|||||||
|
|
||||||
abstract class ZoneRepository {
|
abstract class ZoneRepository {
|
||||||
Future<List<Zone>> getZones(List<double> area, int page, int pageSize);
|
Future<List<Zone>> getZones(List<double> area, int page, int pageSize);
|
||||||
|
Future<List<Zone>> getAllZones(int page, int pageSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
30
lib/domain/usecase/get_all_zones_usecase.dart
Normal file
30
lib/domain/usecase/get_all_zones_usecase.dart
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import '../entities/zone.dart';
|
||||||
|
import '../repositories/zone_repository.dart';
|
||||||
|
|
||||||
|
class GetAllZonesUsecase {
|
||||||
|
final ZoneRepository repository;
|
||||||
|
|
||||||
|
GetAllZonesUsecase(this.repository);
|
||||||
|
|
||||||
|
static const int _pageSize = 100;
|
||||||
|
|
||||||
|
Future<List<Zone>> call() async {
|
||||||
|
final List<Zone> allZones = [];
|
||||||
|
int page = 0;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
final zones = await repository.getAllZones(page, _pageSize);
|
||||||
|
|
||||||
|
if (zones == null || zones.isEmpty) break;
|
||||||
|
|
||||||
|
allZones.addAll(zones);
|
||||||
|
|
||||||
|
// если пришло меньше, чем размер страницы — это последняя страница
|
||||||
|
if (zones.length < _pageSize) break;
|
||||||
|
|
||||||
|
page++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return allZones;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -240,7 +240,7 @@ class _ScooterCard extends StatelessWidget {
|
|||||||
showArrows: true,
|
showArrows: true,
|
||||||
height: 32,
|
height: 32,
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
fontSize: 12,
|
fontSize: 11,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.go('/home/scooter/${scooter.id}', extra: [address, scooter.distance]);
|
context.go('/home/scooter/${scooter.id}', extra: [address, scooter.distance]);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -123,8 +123,7 @@ class _TariffInfoSheetState extends State<TariffInfoSheet> {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: ContentParser.parseContent(
|
children: ContentParser.parseContent(
|
||||||
textJson: widget.tariff.description,
|
text: widget.tariff.description,
|
||||||
text: widget.tariff.descriptionJson,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 30),
|
const SizedBox(height: 30),
|
||||||
|
|||||||
@@ -265,7 +265,7 @@ class _TariffSheetState extends State<TariffSheet> {
|
|||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 150,
|
height: 160,
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
padding: const EdgeInsets.only(left: 20),
|
padding: const EdgeInsets.only(left: 20),
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ class _OnboardingScreenState extends State<OnboardingScreen> {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
'image': 'assets/onboard_2.jpg',
|
'image': 'assets/onboard_2.jpg',
|
||||||
'title': 'Отсканируйте QR-код или введите номер который указан под козлом',
|
'title': 'Отсканируйте QR-код или введите номер который указан под кодом',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'image': 'assets/onboard_3.png',
|
'image': 'assets/onboard_3.png',
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||||||
context.read<ProfileBloc>().add(
|
context.read<ProfileBloc>().add(
|
||||||
ProfilePhotoUpdated(File(pickedImage.path)),
|
ProfilePhotoUpdated(File(pickedImage.path)),
|
||||||
);
|
);
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print("Error picking or uploading image: $e");
|
print("Error picking or uploading image: $e");
|
||||||
}
|
}
|
||||||
@@ -63,9 +62,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: Container(
|
body: Container(
|
||||||
decoration: const BoxDecoration(
|
decoration: const BoxDecoration(gradient: AppColors.phoneScreenBg),
|
||||||
gradient: AppColors.phoneScreenBg,
|
|
||||||
),
|
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
child: BlocBuilder<ProfileBloc, ProfileState>(
|
child: BlocBuilder<ProfileBloc, ProfileState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
@@ -88,13 +85,18 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||||||
|
|
||||||
return LayoutBuilder(
|
return LayoutBuilder(
|
||||||
builder: (context, constraints) {
|
builder: (context, constraints) {
|
||||||
return SingleChildScrollView(
|
return Column(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: SingleChildScrollView(
|
||||||
child: ConstrainedBox(
|
child: ConstrainedBox(
|
||||||
constraints: BoxConstraints(
|
constraints: BoxConstraints(
|
||||||
minHeight: constraints.maxHeight,
|
minHeight: constraints.maxHeight,
|
||||||
),
|
),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 20,
|
||||||
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
@@ -119,7 +121,8 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||||||
profile.avatarUrl!.isEmpty)
|
profile.avatarUrl!.isEmpty)
|
||||||
? Text(
|
? Text(
|
||||||
profile.name.isNotEmpty
|
profile.name.isNotEmpty
|
||||||
? profile.name[0].toUpperCase()
|
? profile.name[0]
|
||||||
|
.toUpperCase()
|
||||||
: '',
|
: '',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 50,
|
fontSize: 50,
|
||||||
@@ -146,9 +149,15 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||||||
|
|
||||||
_ProfileInfoBlock(
|
_ProfileInfoBlock(
|
||||||
profile: profile,
|
profile: profile,
|
||||||
onEditTap: () => context.go("/home/profile/edit"),
|
onEditTap: () =>
|
||||||
|
context.go("/home/profile/edit"),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(20),
|
padding: const EdgeInsets.all(20),
|
||||||
child: GradientButton(
|
child: GradientButton(
|
||||||
@@ -161,9 +170,6 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -116,8 +116,14 @@ class _SendPhotoViewState extends State<SendPhotoView> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final isUploading = context.select(
|
||||||
|
(SendPhotoBloc bloc) => bloc.state.status == SendPhotoStatus.loading,
|
||||||
|
);
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: Container(
|
body: Stack(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
decoration: const BoxDecoration(gradient: AppColors.phoneScreenBg),
|
decoration: const BoxDecoration(gradient: AppColors.phoneScreenBg),
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
@@ -190,7 +196,8 @@ class _SendPhotoViewState extends State<SendPhotoView> {
|
|||||||
},
|
},
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final photoCount = state.selectedImages.length;
|
final photoCount = state.selectedImages.length;
|
||||||
final isEnabled = photoCount >= 2;
|
final isEnabled = photoCount >= 2 &&
|
||||||
|
state.status != SendPhotoStatus.loading;
|
||||||
|
|
||||||
return GradientButton(
|
return GradientButton(
|
||||||
text: 'Отправить',
|
text: 'Отправить',
|
||||||
@@ -214,6 +221,37 @@ class _SendPhotoViewState extends State<SendPhotoView> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
// Оверлей загрузки: показывается, пока фото отправляются на сервер
|
||||||
|
if (isUploading)
|
||||||
|
Positioned.fill(
|
||||||
|
child: AbsorbPointer(
|
||||||
|
absorbing: true,
|
||||||
|
child: Container(
|
||||||
|
color: Colors.black.withOpacity(0.45),
|
||||||
|
child: const Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
CircularProgressIndicator(
|
||||||
|
color: Color(0xFF8BFFAA),
|
||||||
|
),
|
||||||
|
SizedBox(height: 16),
|
||||||
|
Text(
|
||||||
|
'Загружаем фото...',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import 'package:be_happy/core/result.dart';
|
|||||||
import 'package:be_happy/domain/entities/client_notification.dart';
|
import 'package:be_happy/domain/entities/client_notification.dart';
|
||||||
import 'package:be_happy/domain/entities/map_settings.dart';
|
import 'package:be_happy/domain/entities/map_settings.dart';
|
||||||
import 'package:be_happy/domain/usecase/check_user_usecase.dart';
|
import 'package:be_happy/domain/usecase/check_user_usecase.dart';
|
||||||
import 'package:be_happy/domain/usecase/get_available_zones_usecase.dart';
|
import 'package:be_happy/domain/usecase/get_all_zones_usecase.dart';
|
||||||
import 'package:be_happy/domain/usecase/get_client_orders_usecase.dart';
|
import 'package:be_happy/domain/usecase/get_client_orders_usecase.dart';
|
||||||
import 'package:be_happy/domain/usecase/get_map_settings_usecase.dart';
|
import 'package:be_happy/domain/usecase/get_map_settings_usecase.dart';
|
||||||
import 'package:be_happy/domain/usecase/get_notifications_stream_usecase.dart';
|
import 'package:be_happy/domain/usecase/get_notifications_stream_usecase.dart';
|
||||||
@@ -26,7 +26,7 @@ import 'package:maps_toolkit/maps_toolkit.dart' as mt;
|
|||||||
class MapBloc extends Bloc<ScooterEvent, ScooterState> {
|
class MapBloc extends Bloc<ScooterEvent, ScooterState> {
|
||||||
final GetAvailableScootersUsecase getScootersUsecase;
|
final GetAvailableScootersUsecase getScootersUsecase;
|
||||||
final GetClientOrdersUsecase getClientOrdersUsecase;
|
final GetClientOrdersUsecase getClientOrdersUsecase;
|
||||||
final GetAvailableZonesUsecase getAvailableZonesUsecase;
|
final GetAllZonesUsecase getAllZonesUsecase;
|
||||||
final GetMapSettingsUsecase getMapSettingsUsecase;
|
final GetMapSettingsUsecase getMapSettingsUsecase;
|
||||||
final GetNotificationsStreamUseCase getNotificationsStreamUseCase;
|
final GetNotificationsStreamUseCase getNotificationsStreamUseCase;
|
||||||
final GetProfileUseCase getProfileUseCase;
|
final GetProfileUseCase getProfileUseCase;
|
||||||
@@ -35,8 +35,10 @@ class MapBloc extends Bloc<ScooterEvent, ScooterState> {
|
|||||||
final SplashBloc splashBloc;
|
final SplashBloc splashBloc;
|
||||||
StreamSubscription<ClientNotification>? _notificationSubscription;
|
StreamSubscription<ClientNotification>? _notificationSubscription;
|
||||||
|
|
||||||
|
Future<List<Zone>>? _zonesFuture;
|
||||||
|
|
||||||
MapBloc(
|
MapBloc(
|
||||||
this.getAvailableZonesUsecase,
|
this.getAllZonesUsecase,
|
||||||
this.getScootersUsecase,
|
this.getScootersUsecase,
|
||||||
this.getClientOrdersUsecase,
|
this.getClientOrdersUsecase,
|
||||||
this.getMapSettingsUsecase,
|
this.getMapSettingsUsecase,
|
||||||
@@ -58,6 +60,38 @@ class MapBloc extends Bloc<ScooterEvent, ScooterState> {
|
|||||||
on<ClearMapFocus>(_onClearMapFocus);
|
on<ClearMapFocus>(_onClearMapFocus);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<List<Zone>> _getZones() async {
|
||||||
|
_zonesFuture ??= getAllZonesUsecase();
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await _zonesFuture!;
|
||||||
|
} catch (e) {
|
||||||
|
// Не кешируем неудачную попытку — иначе сбой первого запроса
|
||||||
|
// (сеть/авторизация) навсегда "выключает" зоны до перезапуска
|
||||||
|
// приложения. Следующий FetchScooters/UpdateMap попробует заново.
|
||||||
|
_zonesFuture = null;
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Zone> _filterZones(List<Zone> zones, MapSettings settings) {
|
||||||
|
List<Zone> filteredZones = [];
|
||||||
|
|
||||||
|
if (settings.all_zones) {
|
||||||
|
if (settings.parking_zones) {
|
||||||
|
filteredZones.addAll(zones.where((el) => el.type == "Finish"));
|
||||||
|
}
|
||||||
|
if (settings.restricted_parking_zones) {
|
||||||
|
filteredZones.addAll(zones.where((el) => el.type == "Drive"));
|
||||||
|
}
|
||||||
|
if (settings.restricted_driving_zones) {
|
||||||
|
filteredZones.addAll(zones.where((el) => el.type == "NotDrive"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return filteredZones;
|
||||||
|
}
|
||||||
|
|
||||||
void startNotificationStream() {
|
void startNotificationStream() {
|
||||||
_notificationSubscription?.cancel();
|
_notificationSubscription?.cancel();
|
||||||
|
|
||||||
@@ -102,7 +136,7 @@ class MapBloc extends Bloc<ScooterEvent, ScooterState> {
|
|||||||
try {
|
try {
|
||||||
final results = await Future.wait([
|
final results = await Future.wait([
|
||||||
getScootersUsecase(event.areaScooters, 0, 100),
|
getScootersUsecase(event.areaScooters, 0, 100),
|
||||||
getAvailableZonesUsecase(event.area, 0, 100),
|
_getZones(),
|
||||||
getMapSettingsUsecase(),
|
getMapSettingsUsecase(),
|
||||||
getClientOrdersUsecase(),
|
getClientOrdersUsecase(),
|
||||||
]);
|
]);
|
||||||
@@ -112,23 +146,7 @@ class MapBloc extends Bloc<ScooterEvent, ScooterState> {
|
|||||||
final settings = results[2] as MapSettings;
|
final settings = results[2] as MapSettings;
|
||||||
final orders = results[3];
|
final orders = results[3];
|
||||||
|
|
||||||
|
final filteredZones = _filterZones(zones, settings);
|
||||||
|
|
||||||
zones.forEach(print);
|
|
||||||
|
|
||||||
List<Zone> filteredZones = [];
|
|
||||||
|
|
||||||
if (settings.all_zones) {
|
|
||||||
if (settings.parking_zones) {
|
|
||||||
filteredZones.addAll(zones.where((el) => el.type == "Finish"));
|
|
||||||
}
|
|
||||||
if (settings.restricted_parking_zones) {
|
|
||||||
filteredZones.addAll(zones.where((el) => el.type == "Drive"));
|
|
||||||
}
|
|
||||||
if (settings.restricted_driving_zones) {
|
|
||||||
filteredZones.addAll(zones.where((el) => el.type == "NotDrive"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
print(
|
print(
|
||||||
" FETCH: filteredZones.length = ${filteredZones.length}, all_zones=${settings.all_zones}",
|
" FETCH: filteredZones.length = ${filteredZones.length}, all_zones=${settings.all_zones}",
|
||||||
@@ -162,7 +180,7 @@ class MapBloc extends Bloc<ScooterEvent, ScooterState> {
|
|||||||
try {
|
try {
|
||||||
final results = await Future.wait([
|
final results = await Future.wait([
|
||||||
getScootersUsecase(state.areaScooters, 0, 100),
|
getScootersUsecase(state.areaScooters, 0, 100),
|
||||||
getAvailableZonesUsecase(state.area, 0, 100),
|
_getZones(),
|
||||||
getMapSettingsUsecase(),
|
getMapSettingsUsecase(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -170,33 +188,17 @@ class MapBloc extends Bloc<ScooterEvent, ScooterState> {
|
|||||||
final zones = results[1] as List<Zone>;
|
final zones = results[1] as List<Zone>;
|
||||||
final settings = results[2] as MapSettings;
|
final settings = results[2] as MapSettings;
|
||||||
|
|
||||||
List<Zone> filteredZones = [];
|
final filteredZones = _filterZones(zones, settings);
|
||||||
|
|
||||||
if (settings.all_zones) {
|
|
||||||
if (settings.parking_zones) {
|
|
||||||
filteredZones.addAll(zones.where((el) => el.type == "Finish"));
|
|
||||||
}
|
|
||||||
if (settings.restricted_parking_zones) {
|
|
||||||
filteredZones.addAll(zones.where((el) => el.type == "Drive"));
|
|
||||||
}
|
|
||||||
if (settings.restricted_driving_zones) {
|
|
||||||
filteredZones.addAll(zones.where((el) => el.type == "NotDrive"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
print(
|
print(
|
||||||
" UPDATE MAP: filteredZones.length = ${filteredZones.length}, all_zones=${settings.all_zones}",
|
" UPDATE MAP: filteredZones.length = ${filteredZones.length}, all_zones=${settings.all_zones}",
|
||||||
);
|
);
|
||||||
|
|
||||||
final zonesToEmit = filteredZones.isNotEmpty
|
|
||||||
? filteredZones
|
|
||||||
: state.zones;
|
|
||||||
|
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
status: ScooterStatus.success,
|
status: ScooterStatus.success,
|
||||||
scooters: scooters,
|
scooters: scooters,
|
||||||
zones: zonesToEmit,
|
zones: filteredZones,
|
||||||
isGeomarksShowed: settings.all_placemarks,
|
isGeomarksShowed: settings.all_placemarks,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -267,6 +269,7 @@ class MapBloc extends Bloc<ScooterEvent, ScooterState> {
|
|||||||
) async {
|
) async {
|
||||||
await logoutUseCase();
|
await logoutUseCase();
|
||||||
stopNotificationStream();
|
stopNotificationStream();
|
||||||
|
_zonesFuture = null;
|
||||||
splashBloc.add(AuthLogoutRequested());
|
splashBloc.add(AuthLogoutRequested());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -342,5 +345,3 @@ class MapBloc extends Bloc<ScooterEvent, ScooterState> {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
20
pubspec.lock
20
pubspec.lock
@@ -77,10 +77,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: characters
|
name: characters
|
||||||
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
|
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.1"
|
version: "1.4.0"
|
||||||
checked_yaml:
|
checked_yaml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -633,26 +633,26 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: matcher
|
name: matcher
|
||||||
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
|
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.12.19"
|
version: "0.12.17"
|
||||||
material_color_utilities:
|
material_color_utilities:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: material_color_utilities
|
name: material_color_utilities
|
||||||
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
|
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.13.0"
|
version: "0.11.1"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: meta
|
name: meta
|
||||||
sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349"
|
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.18.0"
|
version: "1.17.0"
|
||||||
mime:
|
mime:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -910,10 +910,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e"
|
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.11"
|
version: "0.7.7"
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
Reference in New Issue
Block a user