diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 4bd5aa3..e337cda 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -53,9 +53,11 @@ android { versionName = flutter.versionName } - dependencies { - implementation ("com.yandex.android:maps.mobile:4.5.1-full") - } + +} + +dependencies { + implementation ("com.yandex.android:maps.mobile:4.22.0-full") } flutter { diff --git a/android/app/src/main/kotlin/com/sparkit/be_happy/MainActivity.kt b/android/app/src/main/kotlin/com/sparkit/be_happy/MainActivity.kt index d7c3975..d8259bf 100644 --- a/android/app/src/main/kotlin/com/sparkit/be_happy/MainActivity.kt +++ b/android/app/src/main/kotlin/com/sparkit/be_happy/MainActivity.kt @@ -5,7 +5,6 @@ import com.yandex.mapkit.MapKitFactory import io.flutter.embedding.android.FlutterActivity class MainActivity : FlutterActivity() { - override protected fun onCreate(savedInstanceState: Bundle?) { MapKitFactory.setApiKey("a0ef1404-2650-4f28-9891-c965ecc09174") super.onCreate(savedInstanceState) diff --git a/android/gradle.properties b/android/gradle.properties index f018a61..2716e00 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,3 +1,4 @@ org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true android.enableJetifier=true +yandexMapkit.variant=full diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts index ab39a10..43394ed 100644 --- a/android/settings.gradle.kts +++ b/android/settings.gradle.kts @@ -18,7 +18,7 @@ pluginManagement { plugins { id("dev.flutter.flutter-plugin-loader") version "1.0.0" - id("com.android.application") version "8.7.3" apply false + id("com.android.application") version "8.9.1" apply false id("org.jetbrains.kotlin.android") version "2.1.0" apply false } diff --git a/lib.rar b/lib.rar new file mode 100644 index 0000000..02c830e Binary files /dev/null and b/lib.rar differ diff --git a/lib/data/interceptor/auth_interceptor.dart b/lib/data/interceptor/auth_interceptor.dart new file mode 100644 index 0000000..ef9f69f --- /dev/null +++ b/lib/data/interceptor/auth_interceptor.dart @@ -0,0 +1,26 @@ +import 'package:dio/dio.dart'; +import 'package:be_happy/presentation/viewmodel/splash_bloc.dart'; +import 'package:be_happy/presentation/event/spalsh_event.dart'; + +import '../../di/service_locator.dart'; + +class AuthInterceptor extends Interceptor { + + AuthInterceptor(); + + @override + void onError(DioException err, ErrorInterceptorHandler handler) { + final String path = err.requestOptions.path; + + if (err.response?.statusCode == 401) { + if (!path.contains('/auth/client/refresh') && !path.contains('/auth/client/login')) { + print('Interceptor: 401 на обычном запросе. Пытаемся обновить токен...'); + getIt().add(AuthCheckRequested()); + } else { + print('Interceptor: 401 на запросе авторизации. Пробрасываем ошибку дальше.'); + } + } + + return super.onError(err, handler); + } +} \ No newline at end of file diff --git a/lib/data/network/api_service.dart b/lib/data/network/api_service.dart index 1422b37..f67e822 100644 --- a/lib/data/network/api_service.dart +++ b/lib/data/network/api_service.dart @@ -667,8 +667,22 @@ class ApiService { return ScooterOrder.fromJson(response.data); } on DioException catch (e) { - _handleDioError(e); - return null; + if (e.response?.statusCode == 400) { + final data = e.response?.data; + + if (data is Map && data['message'] is List) { + final firstError = data['message'][0]['message'].toString(); + + if (firstError.contains("Wrong start zone")) { + throw WrongZoneException(message: "Некорректная зона для начала поездки."); + } + } + + if (data is Map && data['message'] is String) {} + } else { + _handleDioError(e); + } + rethrow; } } @@ -684,8 +698,22 @@ class ApiService { } return null; } on DioException catch (e) { - _handleDioError(e); - return null; + if (e.response?.statusCode == 400) { + final data = e.response?.data; + + if (data is Map && data['message'] is List) { + final firstError = data['message'][0]['message'].toString(); + + if (firstError.contains("Wrong start zone")) { + throw WrongZoneException(message: "Некорректная зона для начала поездки."); + } + } + + if (data is Map && data['message'] is String) {} + } else { + _handleDioError(e); + } + rethrow; } } @@ -762,8 +790,8 @@ class ApiService { if (data is Map && data['message'] is List) { final firstError = data['message'][0]['message'].toString(); - if (firstError.contains("Wrong zone")) { - throw WrongZoneException(message: firstError); + if (firstError.contains("Wrong start zone")) { + throw WrongZoneException(message: "Некорректная зона для завершения поездки."); } } diff --git a/lib/data/network/geocoding_remote_datasource.dart b/lib/data/network/geocoding_remote_datasource.dart index 0a43698..1b51a88 100644 --- a/lib/data/network/geocoding_remote_datasource.dart +++ b/lib/data/network/geocoding_remote_datasource.dart @@ -55,32 +55,47 @@ class GeocodingRemoteDataSource { } } - Future?> getPedestrianRoutes(Point userPosition, - Point targetPosition) async { - final (session, resultFuture) = await YandexPedestrian.requestRoutes( - points: [ - RequestPoint( - point: userPosition, requestPointType: RequestPointType.wayPoint), - RequestPoint(point: targetPosition, - requestPointType: RequestPointType.wayPoint) - ], - fitnessOptions: FitnessOptions(avoidSteep: false, avoidStairs: false), - timeOptions: TimeOptions() - ); - + Future?> getPedestrianRoutes( + Point userPosition, + Point targetPosition, + ) async { try { + + print("From: ${userPosition.latitude}, ${userPosition.longitude}"); + print("To: ${targetPosition.latitude}, ${targetPosition.longitude}"); + + final (session, resultFuture) = await YandexPedestrian.requestRoutes( + points: [ + RequestPoint(point: userPosition, requestPointType: RequestPointType.wayPoint), + RequestPoint(point: targetPosition, requestPointType: RequestPointType.wayPoint) + ], + fitnessOptions: const FitnessOptions(avoidSteep: false, avoidStairs: false), + timeOptions: const TimeOptions() + ); + final result = await resultFuture; - final distance = result.routes?.first.metadata.weight.walkingDistance.value; + if (result.routes == null || result.routes!.isEmpty) { + print("Маршруты не найдены: список пуст или null"); + return []; + } - print("Дистанция до самоката: $distance"); + final route = result.routes!.first; + + final distance = route.metadata.weight.walkingDistance?.value; + + if (distance != null) { + print("Дистанция до самоката: $distance м"); + } else { + print("Дистанция не найдена в метаданных маршрута"); + } return result.routes; - } catch (e) { - print('Error: $e'); + } catch (e, stack) { + print('Pedestrian route error: $e'); + print('Stack trace: $stack'); + return null; } - return null; - } } \ No newline at end of file diff --git a/lib/data/repositories/scooter_repository_impl.dart b/lib/data/repositories/scooter_repository_impl.dart index 5cab688..2ba98e3 100644 --- a/lib/data/repositories/scooter_repository_impl.dart +++ b/lib/data/repositories/scooter_repository_impl.dart @@ -164,6 +164,8 @@ class ScooterRepositoryImpl extends ScooterRepository { } } on AuthException catch (e) { result = Failure(AuthFailure(e.attemptsLeft)); + } on WrongZoneException catch (e) { + result = Failure(WrongZoneFailure(e.message)); } catch (e) { result = Failure(UnknownFailure("Неизвестная ошибка")); } diff --git a/lib/di/service_locator.dart b/lib/di/service_locator.dart index 7aecde4..602cd0f 100644 --- a/lib/di/service_locator.dart +++ b/lib/di/service_locator.dart @@ -78,6 +78,7 @@ import 'package:get_it/get_it.dart'; import 'package:http/http.dart' as http; import 'package:shared_preferences/shared_preferences.dart'; +import '../data/interceptor/auth_interceptor.dart'; import '../data/network/api_service.dart'; import '../data/network/geocoding_remote_datasource.dart'; import '../data/repositories/auth_repository_impl.dart'; @@ -110,6 +111,7 @@ Future setupDependencies() async { final sharedPreferences = await SharedPreferences.getInstance(); final dio = Dio(); dio.interceptors.add(LogInterceptor(responseBody: true, requestBody: true)); + dio.interceptors.add(AuthInterceptor()); // HTTP getIt.registerSingleton(http.Client()); //SecureStorage @@ -132,7 +134,7 @@ Future setupDependencies() async { getIt.registerSingleton(AppSettingsService(getIt())); getIt.registerLazySingleton( - () => NewsApiService(getIt()), + () => NewsApiService(getIt()), ); // Repository @@ -163,7 +165,7 @@ Future setupDependencies() async { ); getIt.registerLazySingleton( - () => NewsRepositoryImpl(getIt()), + () => NewsRepositoryImpl(getIt()), ); getIt.registerSingleton( @@ -295,11 +297,11 @@ Future setupDependencies() async { getIt.registerFactory(() => ProfileBloc(getIt(), getIt(), getIt())); getIt.registerFactory( - () => EditProfileBloc(getIt(), getIt()), + () => EditProfileBloc(getIt(), getIt()), ); getIt.registerFactory( - () => MapBloc( + () => MapBloc( getIt(), getIt(), getIt(), @@ -314,24 +316,24 @@ Future setupDependencies() async { getIt.registerFactory(() => ScooterDetailBloc(getIt())); getIt.registerFactory( - () => MapSettingsModalBloc(getIt(), getIt()), + () => MapSettingsModalBloc(getIt(), getIt()), ); getIt.registerFactory(() => AddCardBloc(getIt())); getIt.registerFactory( - () => PaymentMethodSheetBloc(getIt()), + () => PaymentMethodSheetBloc(getIt()), ); getIt.registerFactory(() => CurrentRidesBloc(getIt())); getIt.registerFactory( - () => ActiveRideBloc(getIt(), getIt(), getIt(), getIt(), getIt()), + () => ActiveRideBloc(getIt(), getIt(), getIt(), getIt(), getIt()), ); getIt.registerFactory( - () => ReservedRideBloc(getIt(), getIt()), + () => ReservedRideBloc(getIt(), getIt()), ); getIt.registerFactory(() => NewsBloc(getIt())); @@ -340,7 +342,7 @@ Future setupDependencies() async { getIt.registerFactory(() => SendPhotoBloc(getIt(), getIt())); getIt.registerFactory( - () => SubscriptionListBloc( + () => SubscriptionListBloc( getAvailableSubscriptionsUsecase: getIt(), getClientSubscriptionsUsecase: getIt(), ), @@ -365,10 +367,10 @@ Future setupDependencies() async { // Bloc getIt.registerFactory( - () => OrderHistoryBloc(getIt()), + () => OrderHistoryBloc(getIt()), ); getIt.registerFactory( - () => ScooterCodeBloc(getScooterByTitleUsecase: getIt()), + () => ScooterCodeBloc(getScooterByTitleUsecase: getIt()), ); } diff --git a/lib/domain/entities/tariff.dart b/lib/domain/entities/tariff.dart index a3ce44a..9180b23 100644 --- a/lib/domain/entities/tariff.dart +++ b/lib/domain/entities/tariff.dart @@ -8,8 +8,8 @@ class Tariff { final double drivePrice; // Цена минуты final double pausePrice; // Пауза final double startPrice; // Старт цена - final double cashback; // Процент кэшбэка - final double insurance; // Страховка + final double cashback; // Процент кэшбэка + final double insurance; // Страховка Tariff({ required this.id, diff --git a/lib/presentation/components/sheet/active_ride_sheet.dart b/lib/presentation/components/sheet/active_ride_sheet.dart index 1d26525..416317e 100644 --- a/lib/presentation/components/sheet/active_ride_sheet.dart +++ b/lib/presentation/components/sheet/active_ride_sheet.dart @@ -36,7 +36,6 @@ class _ActiveRideSheetState extends State { _bloc = getIt(); _bloc.add(LoadScooterOrder(widget.orderId)); - // Локальный таймер для обновления UI каждую секунду _localTimer = Timer.periodic(const Duration(seconds: 1), (timer) { if (mounted && !_bloc.state.isPaused) { setState(() {}); @@ -60,7 +59,6 @@ class _ActiveRideSheetState extends State { listener: (context, state) { if (!state.inZone) { BotToast.showCustomNotification( - // duration: const Duration(seconds: 4), toastBuilder: (_) { return NotificationToast( @@ -125,8 +123,7 @@ class _ActiveRideSheetState extends State { ? state.elapsedTime : state.elapsedTime + (DateTime.now().difference(DateTime.now().subtract(const Duration(seconds: 1)))); - // Для отображения текущего времени в реальном времени - final effectiveElapsedTime = state.isPaused + final effectiveElapsedTime = state.isPaused ? state.elapsedTime : DateTime.now().difference(state.order?.startAt ?? state.order?.createdAt ?? DateTime.now()); @@ -189,7 +186,6 @@ class _ActiveRideSheetState extends State { const SizedBox(height: 20), - // 🔹 ТАЙМЕР Container( width: double.infinity, margin: const EdgeInsets.symmetric(horizontal: 20), diff --git a/lib/presentation/components/sheet/tariff_info_sheet.dart b/lib/presentation/components/sheet/tariff_info_sheet.dart index 57f3d0e..522ce5f 100644 --- a/lib/presentation/components/sheet/tariff_info_sheet.dart +++ b/lib/presentation/components/sheet/tariff_info_sheet.dart @@ -5,15 +5,23 @@ import 'package:flutter/cupertino.dart'; class TariffInfoSheet extends StatefulWidget { final Tariff tariff; + final bool isInsurance; + final Function(bool) onChanged; - const TariffInfoSheet({super.key, required this.tariff}); + const TariffInfoSheet({super.key, required this.tariff, required this.isInsurance, required this.onChanged}); @override State createState() => _TariffInfoSheetState(); } class _TariffInfoSheetState extends State { - bool _isInsuranceEnabled = true; + late bool _isInsuranceEnabled; + + @override + void initState() { + super.initState(); + _isInsuranceEnabled = widget.isInsurance; + } @override Widget build(BuildContext context) { @@ -80,7 +88,6 @@ class _TariffInfoSheetState extends State { const Divider(color: Colors.white24, height: 40), - // Страховка Row( children: [ const Text( @@ -100,7 +107,10 @@ class _TariffInfoSheetState extends State { child: CupertinoSwitch( value: _isInsuranceEnabled, activeColor: const Color(0xFF66E3C4), - onChanged: (val) => setState(() => _isInsuranceEnabled = val), + onChanged: (val) { + setState(() => _isInsuranceEnabled = val); + widget.onChanged(val); + }, ), ), ], @@ -108,7 +118,6 @@ class _TariffInfoSheetState extends State { const Divider(color: Colors.white24, height: 40), - // Список правил (Bullet points) _buildInfoBullet('Оплата страховки осуществляется только по банковской карте отдельным платежом'), _buildInfoBullet('В режиме паузы время тарифа приостанавливается'), _buildInfoBullet('При старте заказа будет заблокирована сумма в размере 7 рублей для проверки платежеспособности. Сумма разблокируется по факту списания средств за поездку.'), diff --git a/lib/presentation/components/sheet/tariff_sheet.dart b/lib/presentation/components/sheet/tariff_sheet.dart index c3ba696..f853b23 100644 --- a/lib/presentation/components/sheet/tariff_sheet.dart +++ b/lib/presentation/components/sheet/tariff_sheet.dart @@ -30,7 +30,6 @@ class TariffSheet extends StatefulWidget { class _TariffSheetState extends State { int? _selectedTariffIndex; - bool _hasPaymentCard = true; @override void initState() { @@ -40,7 +39,27 @@ class _TariffSheetState extends State { @override Widget build(BuildContext context) { - return BlocBuilder( + return BlocConsumer( + listener: (context, state) { + if (state.status == TariffSheetStatus.failure && state.errorMessage != null) { + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + state.errorMessage!, + style: const TextStyle(color: Colors.white), + ), + backgroundColor: Colors.redAccent.withOpacity(0.9), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + margin: const EdgeInsets.all(20), + duration: const Duration(seconds: 3), + ), + ); + } + }, builder: (context, state) { if (state.status == TariffSheetStatus.loading) { return Align( @@ -193,7 +212,7 @@ class _TariffSheetState extends State { bottom: 0, child: Center( child: Text( - '${widget.scooter.batteryLevel.toInt()}%', // ✅ Цифры + '${widget.scooter.batteryLevel.toInt()}%', style: const TextStyle( color: Colors.white, fontSize: 10, @@ -245,6 +264,7 @@ class _TariffSheetState extends State { 'Минута на паузе ${tariff.pausePrice.toStringAsFixed(0)} ${tariff.currency}', ], isSelected: _selectedTariffIndex == index, + isInsurance: state.isInsurance, tariff: tariff, onTap: () { setState(() { @@ -380,7 +400,7 @@ class _TariffSheetState extends State { 0, state.useBalance ? null : state.selectedCard?.id, state.useBalance, - false + state.isInsurance ) ); context.pushReplacement('/home/current-rides-sheet'); @@ -421,6 +441,7 @@ class _TariffCard extends StatelessWidget { final String currency; final String subtitle; final List details; + final bool isInsurance; final bool isSelected; final VoidCallback onTap; @@ -432,6 +453,7 @@ class _TariffCard extends StatelessWidget { required this.subtitle, required this.details, required this.isSelected, + required this.isInsurance, required this.onTap, }); @@ -448,14 +470,11 @@ class _TariffCard extends StatelessWidget { : Colors.white.withOpacity(0.19), borderRadius: BorderRadius.circular(20), ), - // Используем Stack, чтобы наложить кнопку поверх контента child: Stack( children: [ - // Основной контент карточки Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // Заголовок с иконкой часов Row( children: [ const Icon( @@ -531,8 +550,16 @@ class _TariffCard extends StatelessWidget { context: context, isScrollControlled: true, backgroundColor: Colors.transparent, - builder: (context) => TariffInfoSheet(tariff: tariff), + builder: (context) => TariffInfoSheet( + tariff: tariff, + isInsurance: isInsurance, + onChanged: (val) { + context.read().add(InsuranceToggled(val)); + }), ); + + + print('Info pressed for $title'); }, child: Image.asset( diff --git a/lib/presentation/event/tariff_sheet_event.dart b/lib/presentation/event/tariff_sheet_event.dart index a66cc8a..7545ddc 100644 --- a/lib/presentation/event/tariff_sheet_event.dart +++ b/lib/presentation/event/tariff_sheet_event.dart @@ -14,6 +14,11 @@ class PaymentCardChanged extends TariffSheetEvent { PaymentCardChanged(this.card); } +class InsuranceToggled extends TariffSheetEvent { + final bool isEnabled; + InsuranceToggled(this.isEnabled); +} + class SelectBalancePressed extends TariffSheetEvent {} class BookScooterPressed extends TariffSheetEvent { diff --git a/lib/presentation/screens/add_card_screen.dart b/lib/presentation/screens/add_card_screen.dart index 01d8dab..e6f55f7 100644 --- a/lib/presentation/screens/add_card_screen.dart +++ b/lib/presentation/screens/add_card_screen.dart @@ -1,3 +1,4 @@ +import 'package:be_happy/presentation/viewmodel/payment_methods_bloc.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -6,6 +7,7 @@ import '../../core/app_colors.dart'; import '../components/custom_app_bar.dart'; import '../components/card_input_field.dart'; // ← новый импорт import '../components/utils/card_formatter.dart'; +import '../event/payment_methods_event.dart'; import '../viewmodel/add_card_bloc.dart'; import '../event/add_card_event.dart'; import '../state/add_card_state.dart'; @@ -28,7 +30,6 @@ class AddCardScreen extends StatelessWidget { child: SafeArea( child: Column( children: [ - // 🔹 ВЕРХНЯЯ ЧАСТЬ (шапка + форма) Expanded( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 20), @@ -37,7 +38,6 @@ class AddCardScreen extends StatelessWidget { const CustomAppBar(title: 'Добавление карты'), const SizedBox(height: 24), - // 🔹 ОСНОВНОЙ КОНТЕЙНЕР С ПОЛЯМИ Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( @@ -46,7 +46,6 @@ class AddCardScreen extends StatelessWidget { ), child: Column( children: [ - // Номер карты BlocBuilder( builder: (context, state) { return CardInputField( @@ -60,7 +59,6 @@ class AddCardScreen extends StatelessWidget { ], letterSpacing: 2, onChanged: (value) { - // Очищаем от пробелов перед отправкой в BLoC final cleanValue = value.replaceAll(' ', ''); context.read().add(CardNumberChanged(cleanValue)); }, @@ -137,7 +135,6 @@ class AddCardScreen extends StatelessWidget { const SizedBox(height: 20), - // 🔹 КНОПКА "ДОБАВИТЬ КАРТУ" BlocBuilder( builder: (context, state) { return Container( @@ -153,7 +150,8 @@ class AddCardScreen extends StatelessWidget { ? () => { context.read().add( AddCardSubmitted()), - context.go("/home/payment-methods") + context.read()..add(PaymentMethodsStarted()), + context.pop() } : null, borderRadius: BorderRadius.circular( @@ -205,7 +203,6 @@ class AddCardScreen extends StatelessWidget { horizontal: 20, vertical: 24), child: Column( children: [ - // Текст о безопасности const Text( 'Мы не сохраняем данные карты у себя. Оплата происходит ' 'через сертифицированный провайдер Беларуси bePaid. ' @@ -221,10 +218,6 @@ class AddCardScreen extends StatelessWidget { ), const SizedBox(height: 24), - - // Логотипы платёжных систем - - const SizedBox(height: 24), ], ), ), diff --git a/lib/presentation/screens/order_history_detail_screen.dart b/lib/presentation/screens/order_history_detail_screen.dart index b0d19e0..e39d1fb 100644 --- a/lib/presentation/screens/order_history_detail_screen.dart +++ b/lib/presentation/screens/order_history_detail_screen.dart @@ -178,7 +178,7 @@ class OrderHistoryDetailScreen extends StatelessWidget { padding: EdgeInsets.all(4), decoration: BoxDecoration( color: Colors.white.withOpacity(0.3), - borderRadius: BorderRadius.circular(12), // Опционально: скругление углов + borderRadius: BorderRadius.circular(12), ), child: Text( order.status == 'Paid' ? 'ОПЛАЧЕН' : 'НЕ ОПЛАЧЕН', @@ -262,7 +262,7 @@ class OrderHistoryDetailScreen extends StatelessWidget { const SizedBox(height: 12), _DetailRow( label: 'Страховка', - value: order.isInsurance ? '1 руб' : '—', + value: order.isInsurance ? "${order.insurancePrice} руб." : '—', ), ], ), diff --git a/lib/presentation/screens/pin_login_screen.dart b/lib/presentation/screens/pin_login_screen.dart index aaa55f3..e532850 100644 --- a/lib/presentation/screens/pin_login_screen.dart +++ b/lib/presentation/screens/pin_login_screen.dart @@ -3,7 +3,9 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; import '../../core/app_colors.dart'; import '../components/code_dots.dart'; +import '../event/map_event.dart'; import '../event/spalsh_event.dart'; +import '../viewmodel/map_bloc.dart'; import '../viewmodel/pin_bloc.dart'; import '../event/pin_event.dart'; import '../state/pin_state.dart'; @@ -143,6 +145,24 @@ class _PinLoginScreenState extends State { style: const TextStyle(color: AppColors.white70), ), + const SizedBox(height: 20), + + if (state is! PinCreateInProgress) + TextButton( + onPressed: () { + context.read().add(LogoutPressed()); + context.read().add(AuthCheckRequested()); + }, + child: const Text( + "Забыли PIN-код?", + style: TextStyle( + color: AppColors.whiteText, + decoration: TextDecoration.underline, + fontSize: 14, + ), + ), + ), + const SizedBox(height: 40), ], diff --git a/lib/presentation/screens/top_up_screen.dart b/lib/presentation/screens/top_up_screen.dart index db8b128..8caba4d 100644 --- a/lib/presentation/screens/top_up_screen.dart +++ b/lib/presentation/screens/top_up_screen.dart @@ -2,9 +2,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; +import '../../core/app_colors.dart'; import '../../di/service_locator.dart'; import '../../domain/entities/payment_card.dart'; import '../../domain/usecase/get_payment_cards_usecase.dart'; +import '../components/app_checkbox.dart'; import '../components/custom_app_bar.dart'; // ✅ Уже есть import '../components/gradient_button.dart'; import '../components/payment_option.dart'; @@ -51,7 +53,6 @@ class TopUpScreen extends StatelessWidget { ), const SizedBox(height: 15), _buildCardSelector(state, context), - _buildAddCardButton(() => context.push("/home/payment-methods/add-card")), const SizedBox(height: 20), _buildAgreement(state, context), const Spacer(), @@ -159,7 +160,6 @@ class TopUpScreen extends StatelessWidget { subtitle: '****${state.selectedCard!.cardLastNumber}', isSelected: true, onTap: () async { - // Открываем модалку как вложенную, не закрывая текущую final selectedCard = await showModalBottomSheet( context: context, isScrollControlled: true, @@ -236,18 +236,22 @@ class TopUpScreen extends StatelessWidget { Widget _buildAgreement(TopUpState state, BuildContext context) { return Row( - crossAxisAlignment: CrossAxisAlignment.start, children: [ - Checkbox( + AppCheckbox( value: state.isAgreed, onChanged: (v) => context.read().add(ToggleAgreement(v ?? false)), - side: const BorderSide(color: Colors.white54), ), - const Expanded( - child: Text( - 'Я принимаю условия покупки бонусного пакета, при котором денежные средства не подлежат возврату...', - style: TextStyle(color: Colors.white54, fontSize: 12), + const SizedBox(width: 12), + Flexible( + child: Text.rich( + TextSpan( + text: 'Я принимаю условия покупки бонусного пакета, при котором денежные средства не подлежат возврату... ', + style: const TextStyle( + color: AppColors.white70, + fontSize: 12, + ), + ), ), ), ], @@ -266,23 +270,4 @@ class TopUpScreen extends StatelessWidget { ); } - Widget _buildAddCardButton(VoidCallback onPressed) { - return Padding( - padding: const EdgeInsets.only(top: 10), - child: OutlinedButton.icon( - onPressed: onPressed, - icon: const Icon(Icons.add, color: Colors.white), - label: const Text( - 'Добавить платежную карту', - style: TextStyle(color: Colors.white), - ), - style: OutlinedButton.styleFrom( - side: const BorderSide(color: Colors.white54), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(30), - ), - ), - ), - ); - } } \ No newline at end of file diff --git a/lib/presentation/state/tariff_sheet_state.dart b/lib/presentation/state/tariff_sheet_state.dart index b7146ce..e0a3787 100644 --- a/lib/presentation/state/tariff_sheet_state.dart +++ b/lib/presentation/state/tariff_sheet_state.dart @@ -9,6 +9,7 @@ class TariffSheetState { final List tariffs; final String? errorMessage; final PaymentCard? selectedCard; + final bool isInsurance; final int userBalance; final bool useBalance ; @@ -16,6 +17,7 @@ class TariffSheetState { this.status = TariffSheetStatus.initial, this.tariffs = const [], this.selectedCard, + this.isInsurance = false, this.errorMessage, this.userBalance = 0, this.useBalance = false, @@ -26,6 +28,7 @@ class TariffSheetState { List? tariffs, PaymentCard? selectedCard, String? errorMessage, + bool? isInsurance, int? userBalance, bool? useBalance, @@ -35,6 +38,7 @@ class TariffSheetState { selectedCard: selectedCard ?? this.selectedCard, useBalance: useBalance ?? this.useBalance, userBalance: userBalance ?? this.userBalance, + isInsurance: isInsurance ?? this.isInsurance, errorMessage: errorMessage ?? this.errorMessage, ); } diff --git a/lib/presentation/viewmodel/active_ride_bloc.dart b/lib/presentation/viewmodel/active_ride_bloc.dart index 1d49fc1..661e95d 100644 --- a/lib/presentation/viewmodel/active_ride_bloc.dart +++ b/lib/presentation/viewmodel/active_ride_bloc.dart @@ -57,8 +57,6 @@ class ActiveRideBloc extends Bloc { if (orderResult is Success) { final order = orderResult.data; - - // Пытаемся достать доп. данные, если они Success final orderData = activeOrderData is Success ? activeOrderData.data : null; @@ -80,7 +78,6 @@ class ActiveRideBloc extends Bloc { inZone: orderData?.zone, )); - //synchronize _syncTimer?.cancel(); _syncTimer = Timer.periodic(const Duration(seconds: 5), (timer) { add(SyncScooterOrder(event.orderId)); diff --git a/lib/presentation/viewmodel/current_rides_bloc.dart b/lib/presentation/viewmodel/current_rides_bloc.dart index 79f0901..4054f47 100644 --- a/lib/presentation/viewmodel/current_rides_bloc.dart +++ b/lib/presentation/viewmodel/current_rides_bloc.dart @@ -20,6 +20,8 @@ class CurrentRidesBloc extends Bloc { ) async { emit(state.copyWith(status: CurrentRidesStatus.loading)); + await Future.delayed(const Duration(seconds: 2)); + final result = await _getClientOrdersUsecase(); if (result is Success>) { diff --git a/lib/presentation/viewmodel/scooter_detail_modal_bloc.dart b/lib/presentation/viewmodel/scooter_detail_modal_bloc.dart index ab3592b..e70eaaa 100644 --- a/lib/presentation/viewmodel/scooter_detail_modal_bloc.dart +++ b/lib/presentation/viewmodel/scooter_detail_modal_bloc.dart @@ -55,10 +55,11 @@ class ScooterDetailModalBloc final routes = await _getPedestrianRoutesUsecase( Point(latitude: event.userLatitude, longitude: event.userLongitude), - Point(latitude: scooter.latitude, longitude: scooter.longitude), + Point(latitude: scooter.longitude, longitude: scooter.latitude), ); final distance = routes?.firstOrNull?.metadata.weight.walkingDistance.value; + print("Distance is: $distance"); final time = routes?.firstOrNull?.metadata.weight.time.value; if (result is Success) { diff --git a/lib/presentation/viewmodel/tariff_sheet_bloc.dart b/lib/presentation/viewmodel/tariff_sheet_bloc.dart index b00dcbd..8d0f9cd 100644 --- a/lib/presentation/viewmodel/tariff_sheet_bloc.dart +++ b/lib/presentation/viewmodel/tariff_sheet_bloc.dart @@ -1,4 +1,6 @@ import 'dart:async'; +import 'package:be_happy/core/failures.dart'; +import 'package:be_happy/domain/entities/scooter_order.dart'; import 'package:be_happy/domain/usecase/get_profile_usecase.dart'; import 'package:collection/collection.dart'; @@ -29,6 +31,7 @@ class TariffSheetBloc extends Bloc { on(_onPaymentCardChanged); on(_onBookScooterPressed); on(_onSelectBalancePressed); + on(_onInsuranceToggled); } Future _onStarted( @@ -92,12 +95,12 @@ class TariffSheetBloc extends Bloc { } } - FutureOr _onBookScooterPressed( + FutureOr _onBookScooterPressed ( BookScooterPressed event, Emitter emit, - ) { + ) async { try { - _bookScooterUsecase( + final result = await _bookScooterUsecase( scooterId: event.scooterId, planId: event.planId, cardId: event.cardId, @@ -105,7 +108,23 @@ class TariffSheetBloc extends Bloc { isBalance: event.isBalance, isInsurance: event.isInsurance, ); - Future.delayed(const Duration(milliseconds: 300)); + + switch (result) { + + case Success(): + // TODO: Handle this case. + throw UnimplementedError(); + case Failure(): + if (result is WrongZoneFailure) { + emit( + state.copyWith( + status: TariffSheetStatus.failure, + errorMessage: result.failure.message, + ), + ); + } + } + } catch (e) { emit( state.copyWith( @@ -125,4 +144,8 @@ class TariffSheetBloc extends Bloc { selectedCard: null, )); } + + FutureOr _onInsuranceToggled(InsuranceToggled event, Emitter emit) async { + emit(state.copyWith(isInsurance: event.isEnabled)); + } } diff --git a/pubspec.lock b/pubspec.lock index b16d3fa..d3d0be7 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -29,10 +29,10 @@ packages: dependency: "direct main" description: name: async - sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 url: "https://pub.dev" source: hosted - version: "2.13.0" + version: "2.13.1" bloc: dependency: transitive description: @@ -125,10 +125,10 @@ packages: dependency: "direct main" description: name: cupertino_icons - sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 + sha256: "41e005c33bd814be4d3096aff55b1908d419fde52ca656c8c47719ec745873cd" url: "https://pub.dev" source: hosted - version: "1.0.8" + version: "1.0.9" device_info_plus: dependency: "direct main" description: @@ -205,10 +205,10 @@ packages: dependency: transitive description: name: file_selector_macos - sha256: "19124ff4a3d8864fdc62072b6a2ef6c222d55a3404fe14893a3c02744907b60c" + sha256: "5e0bbe9c312416f1787a68259ea1505b52f258c587f12920422671807c4d618a" url: "https://pub.dev" source: hosted - version: "0.9.4+4" + version: "0.9.5" file_selector_platform_interface: dependency: transitive description: @@ -274,10 +274,10 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: c2fe1001710127dfa7da89977a08d591398370d099aacdaa6d44da7eb14b8476 + sha256: "38d1c268de9097ff59cf0e844ac38759fc78f76836d37edad06fa21e182055a0" url: "https://pub.dev" source: hosted - version: "2.0.31" + version: "2.0.34" flutter_secure_storage: dependency: "direct main" description: @@ -436,18 +436,18 @@ packages: dependency: "direct main" description: name: image_picker - sha256: "784210112be18ea55f69d7076e2c656a4e24949fa9e76429fe53af0c0f4fa320" + sha256: "91c025426c2881c551100bce834e201c835a170151545f58d17da5180ca7d9ac" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" image_picker_android: dependency: transitive description: name: image_picker_android - sha256: "28f3987ca0ec702d346eae1d90eda59603a2101b52f1e234ded62cff1d5cfa6e" + sha256: d5b3e1774af29c9ab00103afb0d4614070f924d2e0057ac867ec98800114793f url: "https://pub.dev" source: hosted - version: "0.8.13+1" + version: "0.8.13+17" image_picker_for_web: dependency: transitive description: @@ -460,10 +460,10 @@ packages: dependency: transitive description: name: image_picker_ios - sha256: eb06fe30bab4c4497bad449b66448f50edcc695f1c59408e78aa3a8059eb8f0e + sha256: "956c16a42c0c708f914021666ffcd8265dde36e673c9fa68c81f7d085d9774ad" url: "https://pub.dev" source: hosted - version: "0.8.13" + version: "0.8.13+3" image_picker_linux: dependency: transitive description: @@ -476,10 +476,10 @@ packages: dependency: transitive description: name: image_picker_macos - sha256: d58cd9d67793d52beefd6585b12050af0a7663c0c2a6ece0fb110a35d6955e04 + sha256: "86f0f15a309de7e1a552c12df9ce5b59fe927e71385329355aec4776c6a8ec91" url: "https://pub.dev" source: hosted - version: "0.2.2" + version: "0.2.2+1" image_picker_platform_interface: dependency: transitive description: @@ -504,6 +504,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.19.0" + jni: + dependency: transitive + description: + name: jni + sha256: c2230682d5bc2362c1c9e8d3c7f406d9cbba23ab3f2e203a025dd47e0fb2e68f + url: "https://pub.dev" + source: hosted + version: "1.0.0" + jni_flutter: + dependency: transitive + description: + name: jni_flutter + sha256: "8b59e590786050b1cd866677dddaf76b1ade5e7bc751abe04b86e84d379d3ba6" + url: "https://pub.dev" + source: hosted + version: "1.0.1" js: dependency: transitive description: @@ -516,10 +532,10 @@ packages: dependency: transitive description: name: json_annotation - sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + sha256: cb09e7dac6210041fad964ed7fbee004f14258b4eca4040f72d1234062ace4c8 url: "https://pub.dev" source: hosted - version: "4.9.0" + version: "4.11.0" leak_tracker: dependency: transitive description: @@ -616,6 +632,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" + package_config: + dependency: transitive + description: + name: package_config + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc + url: "https://pub.dev" + source: hosted + version: "2.2.0" path: dependency: "direct main" description: @@ -636,18 +660,18 @@ packages: dependency: transitive description: name: path_provider_android - sha256: "3b4c1fc3aa55ddc9cd4aa6759984330d5c8e66aa7702a6223c61540dc6380c37" + sha256: "69cbd515a62b94d32a7944f086b2f82b4ac40a1d45bebfc00813a430ab2dabcd" url: "https://pub.dev" source: hosted - version: "2.2.19" + version: "2.3.1" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "16eef174aacb07e09c351502740fa6254c165757638eba1e9116b0a781201bbd" + sha256: "6d13aece7b3f5c5a9731eaf553ff9dcbc2eff41087fd2df587fd0fed9a3eb0c4" url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.5.1" path_provider_linux: dependency: transitive description: @@ -716,26 +740,26 @@ packages: dependency: "direct main" description: name: shared_preferences - sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" + sha256: c3025c5534b01739267eb7d76959bbc25a6d10f6988e1c2a3036940133dd10bf url: "https://pub.dev" source: hosted - version: "2.5.3" + version: "2.5.5" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: bd14436108211b0d4ee5038689a56d4ae3620fd72fd6036e113bf1345bc74d9e + sha256: e8d4762b1e2e8578fc4d0fd548cebf24afd24f49719c08974df92834565e2c53 url: "https://pub.dev" source: hosted - version: "2.4.13" + version: "2.4.23" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation - sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" + sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f" url: "https://pub.dev" source: hosted - version: "2.5.4" + version: "2.5.6" shared_preferences_linux: dependency: transitive description: @@ -748,10 +772,10 @@ packages: dependency: transitive description: name: shared_preferences_platform_interface - sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" + sha256: "649dc798a33931919ea356c4305c2d1f81619ea6e92244070b520187b5140ef9" url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.2" shared_preferences_web: dependency: transitive description: @@ -777,10 +801,10 @@ packages: dependency: transitive description: name: source_span - sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" url: "https://pub.dev" source: hosted - version: "1.10.1" + version: "1.10.2" stack_trace: dependency: transitive description: @@ -841,18 +865,18 @@ packages: dependency: transitive description: name: url_launcher_android - sha256: "81777b08c498a292d93ff2feead633174c386291e35612f8da438d6e92c4447e" + sha256: "3bb000251e55d4a209aa0e2e563309dc9bb2befea2295fd0cec1f51760aac572" url: "https://pub.dev" source: hosted - version: "6.3.20" + version: "6.3.29" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: d80b3f567a617cb923546034cc94bfe44eb15f989fe670b37f26abdb9d939cb7 + sha256: cfde38aa257dae62ffe79c87fab20165dfdf6988c1d31b58ebf59b9106062aad url: "https://pub.dev" source: hosted - version: "6.3.4" + version: "6.3.6" url_launcher_linux: dependency: transitive description: @@ -865,10 +889,10 @@ packages: dependency: transitive description: name: url_launcher_macos - sha256: c043a77d6600ac9c38300567f33ef12b0ef4f4783a2c1f00231d2b1941fea13f + sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18" url: "https://pub.dev" source: hosted - version: "3.2.3" + version: "3.2.5" url_launcher_platform_interface: dependency: transitive description: @@ -913,10 +937,10 @@ packages: dependency: transitive description: name: vm_service - sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 + sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" url: "https://pub.dev" source: hosted - version: "15.0.0" + version: "15.2.0" web: dependency: transitive description: @@ -974,5 +998,5 @@ packages: source: hosted version: "4.2.1" sdks: - dart: ">=3.8.1 <4.0.0" - flutter: ">=3.32.0" + dart: ">=3.9.0 <4.0.0" + flutter: ">=3.35.6"