bug fixes

This commit is contained in:
2026-08-29 00:00:09 +03:00
parent 6513a004d9
commit a556dc5736
15 changed files with 361 additions and 271 deletions

View File

@@ -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]);
},

View File

@@ -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),

View File

@@ -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),

View File

@@ -19,7 +19,7 @@ class _OnboardingScreenState extends State<OnboardingScreen> {
},
{
'image': 'assets/onboard_2.jpg',
'title': 'Отсканируйте QR-код или введите номер который указан под козлом',
'title': 'Отсканируйте QR-код или введите номер который указан под кодом',
},
{
'image': 'assets/onboard_3.png',

View File

@@ -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 {
),
);
}
}
}

View File

@@ -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,
),
),
],
),
),
),
),
),
],
),
);
}

View File

@@ -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,
));
}
}
}