bug fixes
This commit is contained in:
@@ -240,7 +240,7 @@ class _ScooterCard extends StatelessWidget {
|
||||
showArrows: true,
|
||||
height: 32,
|
||||
width: double.infinity,
|
||||
fontSize: 12,
|
||||
fontSize: 11,
|
||||
onTap: () {
|
||||
context.go('/home/scooter/${scooter.id}', extra: [address, scooter.distance]);
|
||||
},
|
||||
|
||||
@@ -123,8 +123,7 @@ class _TariffInfoSheetState extends State<TariffInfoSheet> {
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: ContentParser.parseContent(
|
||||
textJson: widget.tariff.description,
|
||||
text: widget.tariff.descriptionJson,
|
||||
text: widget.tariff.description,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
|
||||
@@ -265,7 +265,7 @@ class _TariffSheetState extends State<TariffSheet> {
|
||||
const SizedBox(height: 20),
|
||||
|
||||
SizedBox(
|
||||
height: 150,
|
||||
height: 160,
|
||||
child: ListView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
padding: const EdgeInsets.only(left: 20),
|
||||
|
||||
@@ -19,7 +19,7 @@ class _OnboardingScreenState extends State<OnboardingScreen> {
|
||||
},
|
||||
{
|
||||
'image': 'assets/onboard_2.jpg',
|
||||
'title': 'Отсканируйте QR-код или введите номер который указан под козлом',
|
||||
'title': 'Отсканируйте QR-код или введите номер который указан под кодом',
|
||||
},
|
||||
{
|
||||
'image': 'assets/onboard_3.png',
|
||||
|
||||
@@ -37,7 +37,6 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
context.read<ProfileBloc>().add(
|
||||
ProfilePhotoUpdated(File(pickedImage.path)),
|
||||
);
|
||||
|
||||
} catch (e) {
|
||||
print("Error picking or uploading image: $e");
|
||||
}
|
||||
@@ -63,9 +62,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Container(
|
||||
decoration: const BoxDecoration(
|
||||
gradient: AppColors.phoneScreenBg,
|
||||
),
|
||||
decoration: const BoxDecoration(gradient: AppColors.phoneScreenBg),
|
||||
child: SafeArea(
|
||||
child: BlocBuilder<ProfileBloc, ProfileState>(
|
||||
builder: (context, state) {
|
||||
@@ -88,82 +85,91 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
return SingleChildScrollView(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: constraints.maxHeight,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(height: 16),
|
||||
CustomAppBar(title: 'Профиль'),
|
||||
const SizedBox(height: 32),
|
||||
return Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: constraints.maxHeight,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 20,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(height: 16),
|
||||
CustomAppBar(title: 'Профиль'),
|
||||
const SizedBox(height: 32),
|
||||
|
||||
Stack(
|
||||
alignment: Alignment.topRight,
|
||||
children: [
|
||||
CircleAvatar(
|
||||
radius: 60,
|
||||
backgroundColor: AppColors.checkboxFill,
|
||||
backgroundImage:
|
||||
(profile.avatarUrl != null &&
|
||||
profile.avatarUrl!.isNotEmpty)
|
||||
? NetworkImage(
|
||||
"${profile.avatarUrl!}?v=${DateTime.now().minute}",
|
||||
)
|
||||
: null,
|
||||
child:
|
||||
(profile.avatarUrl == null ||
|
||||
profile.avatarUrl!.isEmpty)
|
||||
? Text(
|
||||
profile.name.isNotEmpty
|
||||
? profile.name[0].toUpperCase()
|
||||
: '',
|
||||
style: const TextStyle(
|
||||
fontSize: 50,
|
||||
color: AppColors.darkBlue,
|
||||
),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
Stack(
|
||||
alignment: Alignment.topRight,
|
||||
children: [
|
||||
CircleAvatar(
|
||||
radius: 60,
|
||||
backgroundColor: AppColors.checkboxFill,
|
||||
backgroundImage:
|
||||
(profile.avatarUrl != null &&
|
||||
profile.avatarUrl!.isNotEmpty)
|
||||
? NetworkImage(
|
||||
"${profile.avatarUrl!}?v=${DateTime.now().minute}",
|
||||
)
|
||||
: null,
|
||||
child:
|
||||
(profile.avatarUrl == null ||
|
||||
profile.avatarUrl!.isEmpty)
|
||||
? Text(
|
||||
profile.name.isNotEmpty
|
||||
? profile.name[0]
|
||||
.toUpperCase()
|
||||
: '',
|
||||
style: const TextStyle(
|
||||
fontSize: 50,
|
||||
color: AppColors.darkBlue,
|
||||
),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
|
||||
GestureDetector(
|
||||
onTap: _pickImage,
|
||||
child: Container(
|
||||
child: Image.asset(
|
||||
'assets/icons/edit.png',
|
||||
width: 24,
|
||||
height: 24,
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: _pickImage,
|
||||
child: Container(
|
||||
child: Image.asset(
|
||||
'assets/icons/edit.png',
|
||||
width: 24,
|
||||
height: 24,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 32),
|
||||
const SizedBox(height: 32),
|
||||
|
||||
_ProfileInfoBlock(
|
||||
profile: profile,
|
||||
onEditTap: () => context.go("/home/profile/edit"),
|
||||
),
|
||||
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: GradientButton(
|
||||
text: 'Удалить учётную запись',
|
||||
onTap: () => _confirmDeleteAccount(context),
|
||||
showArrows: true,
|
||||
height: 56,
|
||||
width: double.infinity,
|
||||
fontSize: 16,
|
||||
_ProfileInfoBlock(
|
||||
profile: profile,
|
||||
onEditTap: () =>
|
||||
context.go("/home/profile/edit"),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: GradientButton(
|
||||
text: 'Удалить учётную запись',
|
||||
onTap: () => _confirmDeleteAccount(context),
|
||||
showArrows: true,
|
||||
height: 56,
|
||||
width: double.infinity,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -388,4 +394,4 @@ class _SettingsBlock extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,13 +116,19 @@ class _SendPhotoViewState extends State<SendPhotoView> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isUploading = context.select(
|
||||
(SendPhotoBloc bloc) => bloc.state.status == SendPhotoStatus.loading,
|
||||
);
|
||||
|
||||
return Scaffold(
|
||||
body: Container(
|
||||
width: double.infinity,
|
||||
decoration: const BoxDecoration(gradient: AppColors.phoneScreenBg),
|
||||
child: SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
body: Stack(
|
||||
children: [
|
||||
Container(
|
||||
width: double.infinity,
|
||||
decoration: const BoxDecoration(gradient: AppColors.phoneScreenBg),
|
||||
child: SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(height: 16),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
@@ -190,7 +196,8 @@ class _SendPhotoViewState extends State<SendPhotoView> {
|
||||
},
|
||||
builder: (context, state) {
|
||||
final photoCount = state.selectedImages.length;
|
||||
final isEnabled = photoCount >= 2;
|
||||
final isEnabled = photoCount >= 2 &&
|
||||
state.status != SendPhotoStatus.loading;
|
||||
|
||||
return GradientButton(
|
||||
text: 'Отправить',
|
||||
@@ -210,9 +217,40 @@ 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/map_settings.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_map_settings_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> {
|
||||
final GetAvailableScootersUsecase getScootersUsecase;
|
||||
final GetClientOrdersUsecase getClientOrdersUsecase;
|
||||
final GetAvailableZonesUsecase getAvailableZonesUsecase;
|
||||
final GetAllZonesUsecase getAllZonesUsecase;
|
||||
final GetMapSettingsUsecase getMapSettingsUsecase;
|
||||
final GetNotificationsStreamUseCase getNotificationsStreamUseCase;
|
||||
final GetProfileUseCase getProfileUseCase;
|
||||
@@ -35,17 +35,19 @@ class MapBloc extends Bloc<ScooterEvent, ScooterState> {
|
||||
final SplashBloc splashBloc;
|
||||
StreamSubscription<ClientNotification>? _notificationSubscription;
|
||||
|
||||
Future<List<Zone>>? _zonesFuture;
|
||||
|
||||
MapBloc(
|
||||
this.getAvailableZonesUsecase,
|
||||
this.getScootersUsecase,
|
||||
this.getClientOrdersUsecase,
|
||||
this.getMapSettingsUsecase,
|
||||
this.getNotificationsStreamUseCase,
|
||||
this.getProfileUseCase,
|
||||
this.checkUserUseCase,
|
||||
this.logoutUseCase,
|
||||
this.splashBloc,
|
||||
) : super(ScooterState(isGeomarksShowed: true)) {
|
||||
this.getAllZonesUsecase,
|
||||
this.getScootersUsecase,
|
||||
this.getClientOrdersUsecase,
|
||||
this.getMapSettingsUsecase,
|
||||
this.getNotificationsStreamUseCase,
|
||||
this.getProfileUseCase,
|
||||
this.checkUserUseCase,
|
||||
this.logoutUseCase,
|
||||
this.splashBloc,
|
||||
) : super(ScooterState(isGeomarksShowed: true)) {
|
||||
on<FetchScooters>(_onFetchScooters);
|
||||
on<UpdateMap>(_onUpdateMap);
|
||||
on<UpdateUserLocation>(_onUpdateUserLocation);
|
||||
@@ -58,11 +60,43 @@ class MapBloc extends Bloc<ScooterEvent, ScooterState> {
|
||||
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() {
|
||||
_notificationSubscription?.cancel();
|
||||
|
||||
_notificationSubscription = getNotificationsStreamUseCase().listen(
|
||||
(notification) {
|
||||
(notification) {
|
||||
add(NotificationReceived(notification));
|
||||
},
|
||||
onError: (error) {
|
||||
@@ -94,15 +128,15 @@ class MapBloc extends Bloc<ScooterEvent, ScooterState> {
|
||||
}
|
||||
|
||||
Future<void> _onFetchScooters(
|
||||
FetchScooters event,
|
||||
Emitter<ScooterState> emit,
|
||||
) async {
|
||||
FetchScooters event,
|
||||
Emitter<ScooterState> emit,
|
||||
) async {
|
||||
emit(state.copyWith(status: ScooterStatus.loading));
|
||||
|
||||
try {
|
||||
final results = await Future.wait([
|
||||
getScootersUsecase(event.areaScooters, 0, 100),
|
||||
getAvailableZonesUsecase(event.area, 0, 100),
|
||||
_getZones(),
|
||||
getMapSettingsUsecase(),
|
||||
getClientOrdersUsecase(),
|
||||
]);
|
||||
@@ -112,23 +146,7 @@ class MapBloc extends Bloc<ScooterEvent, ScooterState> {
|
||||
final settings = results[2] as MapSettings;
|
||||
final orders = results[3];
|
||||
|
||||
|
||||
|
||||
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"));
|
||||
}
|
||||
}
|
||||
final filteredZones = _filterZones(zones, settings);
|
||||
|
||||
print(
|
||||
" FETCH: filteredZones.length = ${filteredZones.length}, all_zones=${settings.all_zones}",
|
||||
@@ -162,7 +180,7 @@ class MapBloc extends Bloc<ScooterEvent, ScooterState> {
|
||||
try {
|
||||
final results = await Future.wait([
|
||||
getScootersUsecase(state.areaScooters, 0, 100),
|
||||
getAvailableZonesUsecase(state.area, 0, 100),
|
||||
_getZones(),
|
||||
getMapSettingsUsecase(),
|
||||
]);
|
||||
|
||||
@@ -170,33 +188,17 @@ class MapBloc extends Bloc<ScooterEvent, ScooterState> {
|
||||
final zones = results[1] as List<Zone>;
|
||||
final settings = results[2] as MapSettings;
|
||||
|
||||
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"));
|
||||
}
|
||||
}
|
||||
final filteredZones = _filterZones(zones, settings);
|
||||
|
||||
print(
|
||||
" UPDATE MAP: filteredZones.length = ${filteredZones.length}, all_zones=${settings.all_zones}",
|
||||
);
|
||||
|
||||
final zonesToEmit = filteredZones.isNotEmpty
|
||||
? filteredZones
|
||||
: state.zones;
|
||||
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: ScooterStatus.success,
|
||||
scooters: scooters,
|
||||
zones: zonesToEmit,
|
||||
zones: filteredZones,
|
||||
isGeomarksShowed: settings.all_placemarks,
|
||||
),
|
||||
);
|
||||
@@ -212,9 +214,9 @@ class MapBloc extends Bloc<ScooterEvent, ScooterState> {
|
||||
}
|
||||
|
||||
FutureOr<void> _onUpdateUserLocation(
|
||||
UpdateUserLocation event,
|
||||
Emitter<ScooterState> emit,
|
||||
) {
|
||||
UpdateUserLocation event,
|
||||
Emitter<ScooterState> emit,
|
||||
) {
|
||||
print("USER LOCATION UPDATED EVENT start");
|
||||
state.zones.forEach((z) {
|
||||
if (checkUserInZone(Point(event.latitude, event.longitude), z.points)) {
|
||||
@@ -224,9 +226,9 @@ class MapBloc extends Bloc<ScooterEvent, ScooterState> {
|
||||
}
|
||||
|
||||
FutureOr<void> _onNotificationReceived(
|
||||
NotificationReceived event,
|
||||
Emitter<ScooterState> emit,
|
||||
) {
|
||||
NotificationReceived event,
|
||||
Emitter<ScooterState> emit,
|
||||
) {
|
||||
print("NOTIFICATION RECEIVED: ${event.notification.content}");
|
||||
emit(state.copyWith(lastNotification: event.notification));
|
||||
}
|
||||
@@ -242,9 +244,9 @@ class MapBloc extends Bloc<ScooterEvent, ScooterState> {
|
||||
}
|
||||
|
||||
FutureOr<void> _onFetchProfileData(
|
||||
FetchProfileData event,
|
||||
Emitter<ScooterState> emit,
|
||||
) async {
|
||||
FetchProfileData event,
|
||||
Emitter<ScooterState> emit,
|
||||
) async {
|
||||
try {
|
||||
final profile = await getProfileUseCase();
|
||||
|
||||
@@ -262,18 +264,19 @@ class MapBloc extends Bloc<ScooterEvent, ScooterState> {
|
||||
}
|
||||
|
||||
FutureOr<void> _onLogoutPressed(
|
||||
LogoutPressed event,
|
||||
Emitter<ScooterState> emit,
|
||||
) async {
|
||||
LogoutPressed event,
|
||||
Emitter<ScooterState> emit,
|
||||
) async {
|
||||
await logoutUseCase();
|
||||
stopNotificationStream();
|
||||
_zonesFuture = null;
|
||||
splashBloc.add(AuthLogoutRequested());
|
||||
}
|
||||
|
||||
FutureOr<void> _onCheckUser(
|
||||
CheckUser event,
|
||||
Emitter<ScooterState> emit,
|
||||
) async {
|
||||
CheckUser event,
|
||||
Emitter<ScooterState> emit,
|
||||
) async {
|
||||
try {
|
||||
final flags = await checkUserUseCase();
|
||||
|
||||
@@ -341,6 +344,4 @@ class MapBloc extends Bloc<ScooterEvent, ScooterState> {
|
||||
selectedScooterForFocus: null,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user