fix
This commit is contained in:
@@ -31,7 +31,8 @@ class _CardBindingResultScreenState extends State<CardBindingResultScreen> {
|
||||
if (widget.status == CardBindingStatus.success) {
|
||||
_autoPopTimer = Timer(const Duration(seconds: 5), () {
|
||||
if (mounted) {
|
||||
context.go('/home/payment-methods');
|
||||
final timestamp = DateTime.now().millisecondsSinceEpoch;
|
||||
context.go("/home/payment-methods?refreshId=$timestamp");
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -125,7 +126,10 @@ class _CardBindingResultScreenState extends State<CardBindingResultScreen> {
|
||||
|
||||
GradientButton(
|
||||
text: "Попробовать снова",
|
||||
onTap: () {},
|
||||
onTap: () {
|
||||
final timestamp = DateTime.now().millisecondsSinceEpoch;
|
||||
context.go("/home/payment-methods?refreshId=$timestamp");
|
||||
},
|
||||
enabled: true,
|
||||
showArrows: true,
|
||||
),
|
||||
@@ -135,7 +139,11 @@ class _CardBindingResultScreenState extends State<CardBindingResultScreen> {
|
||||
width: double.infinity,
|
||||
height: 56,
|
||||
child: TextButton(
|
||||
onPressed: () => context.go("/home/payment-methods"),
|
||||
onPressed: ()
|
||||
{
|
||||
final timestamp = DateTime.now().millisecondsSinceEpoch;
|
||||
context.go("/home/payment-methods?refreshId=$timestamp");
|
||||
},
|
||||
style: TextButton.styleFrom(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:go_router/go_router.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
import '../../core/app_colors.dart';
|
||||
import '../../di/service_locator.dart';
|
||||
import '../../domain/entities/user_profile.dart';
|
||||
import '../components/custom_app_bar.dart';
|
||||
import '../components/gradient_button.dart';
|
||||
@@ -106,7 +107,7 @@ class _EditProfileScreenState extends State<EditProfileScreen> {
|
||||
}
|
||||
|
||||
if (state.isSuccess) {
|
||||
context.read<ProfileBloc>().add(ProfileUpdated());
|
||||
getIt<ProfileBloc>().add(ProfileUpdated());
|
||||
context.pop();
|
||||
}
|
||||
if (state.error != null) {
|
||||
|
||||
@@ -4,24 +4,9 @@ import 'dart:ui' as ui;
|
||||
|
||||
import 'package:bot_toast/bot_toast.dart';
|
||||
import 'package:be_happy/domain/entities/client_notification.dart';
|
||||
import 'package:be_happy/domain/usecase/book_scooter_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_payment_cards_usecase.dart';
|
||||
import 'package:be_happy/domain/usecase/get_pedestrian_routes_usecase.dart';
|
||||
import 'package:be_happy/domain/usecase/get_scooter_usecase.dart';
|
||||
import 'package:be_happy/domain/usecase/save_map_settings_usecase.dart';
|
||||
import 'package:be_happy/presentation/components/fine_notification_card.dart';
|
||||
import 'package:be_happy/presentation/components/map_icon_painter/clusterized_icon_painter.dart';
|
||||
import 'package:be_happy/presentation/components/payment_notification_card.dart';
|
||||
import 'package:be_happy/presentation/components/sheet/current_rides_sheet.dart';
|
||||
import 'package:be_happy/presentation/components/sheet/map_settings_sheet.dart';
|
||||
import 'package:be_happy/presentation/components/sheet/tariff_sheet.dart';
|
||||
import 'package:be_happy/presentation/event/current_rides_event.dart';
|
||||
import 'package:be_happy/presentation/event/map_settings_modal_event.dart';
|
||||
import 'package:be_happy/presentation/viewmodel/current_rides_bloc.dart';
|
||||
import 'package:be_happy/presentation/viewmodel/map_settings_modal_bloc.dart';
|
||||
import 'package:be_happy/presentation/viewmodel/tariff_sheet_bloc.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
@@ -32,18 +17,12 @@ import '../../core/app_colors.dart';
|
||||
import '../../di/service_locator.dart';
|
||||
import '../../domain/entities/scooter.dart';
|
||||
import '../../domain/entities/zone.dart';
|
||||
import '../../domain/usecase/get_address_by_point_usecase.dart';
|
||||
import '../../domain/usecase/get_available_tariffs_usecase.dart';
|
||||
import '../../domain/usecase/get_notifications_stream_usecase.dart';
|
||||
import '../components/notification_toast.dart';
|
||||
import '../components/sheet/scooter_bottom_sheet.dart';
|
||||
import '../components/side_menu.dart';
|
||||
import '../components/unpaid_order_notification_card.dart';
|
||||
import '../event/map_event.dart';
|
||||
import '../event/scooter_detail_modal_event.dart';
|
||||
import '../state/map_state.dart';
|
||||
import '../viewmodel/map_bloc.dart';
|
||||
import '../viewmodel/scooter_detail_modal_bloc.dart';
|
||||
|
||||
class MapScreen extends StatefulWidget {
|
||||
const MapScreen({super.key});
|
||||
@@ -609,28 +588,33 @@ class _MapScreenState extends State<MapScreen> {
|
||||
if (zones == null || zones.isEmpty) return [];
|
||||
|
||||
List<MapObject> objects = [];
|
||||
|
||||
List<LinearRing> allZoneHoles = [];
|
||||
List<LinearRing> driveOuterRings = [];
|
||||
|
||||
for (var zone in zones) {
|
||||
var points = zone.points.map((p) => Point(latitude: p.latitude, longitude: p.longitude)).toList();
|
||||
if (zone.type == "Drive") {
|
||||
for (var polygonData in zone.geometry) {
|
||||
if (polygonData.isEmpty) continue;
|
||||
|
||||
var cleanPoints = <Point>[];
|
||||
for (var p in points) {
|
||||
if (cleanPoints.isEmpty || cleanPoints.last != p) {
|
||||
cleanPoints.add(p);
|
||||
var outerPoints = polygonData[0].map((p) => Point(latitude: p.latitude, longitude: p.longitude)).toList();
|
||||
|
||||
var cleanPoints = <Point>[];
|
||||
for (var p in outerPoints) {
|
||||
if (cleanPoints.isEmpty || cleanPoints.last != p) {
|
||||
cleanPoints.add(p);
|
||||
}
|
||||
}
|
||||
|
||||
if (cleanPoints.length > 2) {
|
||||
if (cleanPoints.first != cleanPoints.last) {
|
||||
cleanPoints.add(cleanPoints.first);
|
||||
}
|
||||
driveOuterRings.add(LinearRing(points: cleanPoints));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (cleanPoints.length > 2) {
|
||||
if (cleanPoints.first != cleanPoints.last) {
|
||||
cleanPoints.add(cleanPoints.first);
|
||||
}
|
||||
|
||||
allZoneHoles.add(LinearRing(points: cleanPoints));
|
||||
}
|
||||
}
|
||||
|
||||
// Добавляем глобальную маску (все, что НЕ Drive — затеняется красным)
|
||||
objects.add(
|
||||
PolygonMapObject(
|
||||
mapId: const MapObjectId('global_inverse_mask'),
|
||||
@@ -641,7 +625,7 @@ class _MapScreenState extends State<MapScreen> {
|
||||
Point(latitude: -85, longitude: 179.9),
|
||||
Point(latitude: -85, longitude: -179.9),
|
||||
]),
|
||||
innerRings: allZoneHoles,
|
||||
innerRings: driveOuterRings, // Вырезаем только зоны Drive
|
||||
),
|
||||
strokeWidth: 0,
|
||||
fillColor: Colors.red.withOpacity(0.15),
|
||||
@@ -649,27 +633,63 @@ class _MapScreenState extends State<MapScreen> {
|
||||
),
|
||||
);
|
||||
|
||||
// 2. Отрисовываем каждую зону (включая MultiPolygon и вложенные NotDrive/Finish)
|
||||
for (var zone in zones) {
|
||||
Color borderColor;
|
||||
Color fillColor;
|
||||
|
||||
// Стилизация в зависимости от типа зоны
|
||||
if (zone.type == "Drive") {
|
||||
borderColor = const Color(0xFF5ECD4C);
|
||||
borderColor = const Color(0xFF5ECD4C); // Зеленый бордюр
|
||||
fillColor = Colors.transparent; // Прозрачная внутри маски
|
||||
} else if (zone.type == "NotDrive") {
|
||||
borderColor = const Color(0xFFEF4444);
|
||||
borderColor = const Color(0xFFEF4444); // Красный бордюр
|
||||
fillColor = const Color(0xFFEF4444).withOpacity(0.3); // Красная полупрозрачная заливка для наглядности внутри Drive
|
||||
} else {
|
||||
borderColor = const Color(0xFFA78BFA);
|
||||
borderColor = const Color(0xFFA78BFA); // Фиолетовый бордюр (Парковка)
|
||||
fillColor = const Color(0xFFA78BFA).withOpacity(0.25); // Фиолетовая заливка
|
||||
}
|
||||
|
||||
objects.add(
|
||||
PolylineMapObject(
|
||||
mapId: MapObjectId('zone_contour_${zone.id}'),
|
||||
polyline: Polyline(
|
||||
points: zone.points.map((p) => Point(latitude: p.latitude, longitude: p.longitude)).toList(),
|
||||
int polyIndex = 0;
|
||||
for (var polygonData in zone.geometry) {
|
||||
if (polygonData.isEmpty) continue;
|
||||
|
||||
// Строим внешнее кольцо текущего полигона
|
||||
var outerPoints = polygonData[0].map((p) => Point(latitude: p.latitude, longitude: p.longitude)).toList();
|
||||
if (outerPoints.first != outerPoints.last) {
|
||||
outerPoints.add(outerPoints.first);
|
||||
}
|
||||
LinearRing outerRing = LinearRing(points: outerPoints);
|
||||
|
||||
// Строим внутренние кольца (дыры), если они есть у этого полигона
|
||||
List<LinearRing> innerRings = [];
|
||||
for (int i = 1; i < polygonData.length; i++) {
|
||||
var innerPoints = polygonData[i].map((p) => Point(latitude: p.latitude, longitude: p.longitude)).toList();
|
||||
if (innerPoints.isNotEmpty) {
|
||||
if (innerPoints.first != innerPoints.last) {
|
||||
innerPoints.add(innerPoints.first);
|
||||
}
|
||||
innerRings.add(LinearRing(points: innerPoints));
|
||||
}
|
||||
}
|
||||
|
||||
// Рендерим полноценный PolygonMapObject (он поддерживает и заливку, и обводку)
|
||||
objects.add(
|
||||
PolygonMapObject(
|
||||
mapId: MapObjectId('zone_poly_${zone.type}_${zone.id}_$polyIndex'),
|
||||
polygon: Polygon(
|
||||
outerRing: outerRing,
|
||||
innerRings: innerRings,
|
||||
),
|
||||
strokeColor: borderColor,
|
||||
strokeWidth: 2.0,
|
||||
fillColor: fillColor,
|
||||
// Поднимаем зоны ограничений и парковок выше маски и контуров Drive
|
||||
zIndex: zone.type == "Drive" ? 1 : 2,
|
||||
),
|
||||
strokeColor: borderColor,
|
||||
strokeWidth: 2.0,
|
||||
zIndex: 1,
|
||||
),
|
||||
);
|
||||
);
|
||||
polyIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
return objects;
|
||||
@@ -684,7 +704,10 @@ class _MapScreenState extends State<MapScreen> {
|
||||
child: Builder(
|
||||
builder: (innerContext) => _RoundIconButton(
|
||||
icon: Icons.menu,
|
||||
onPressed: () => {Scaffold.of(innerContext).openDrawer()},
|
||||
onPressed: () {
|
||||
context.read<MapBloc>().add(FetchProfileData());
|
||||
Scaffold.of(innerContext).openDrawer();
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -23,7 +23,10 @@ class NotificationsScreen extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _NotificationsScreenContent();
|
||||
return BlocProvider(
|
||||
create: (context) => getIt<NotificationsBloc>()..add(NotificationsFetchRequested(page: 1)),
|
||||
child: _NotificationsScreenContent(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,21 +39,31 @@ class _NotificationsScreenContent extends StatefulWidget {
|
||||
|
||||
class _NotificationsScreenContentState extends State<_NotificationsScreenContent> {
|
||||
NotificationFilter _filter = NotificationFilter.all;
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _setFilter(NotificationFilter filter) {
|
||||
setState(() {
|
||||
_filter = filter;
|
||||
});
|
||||
setState(() => _filter = filter);
|
||||
_scrollController.jumpTo(0);
|
||||
context.read<NotificationsBloc>().add(NotificationsFetchRequested(page: 1));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => getIt<NotificationsBloc>()..add(NotificationsFetchRequested()),
|
||||
child: NotificationsView(
|
||||
filter: _filter,
|
||||
onFilterChanged: _setFilter,
|
||||
),
|
||||
return NotificationsView(
|
||||
filter: _filter,
|
||||
onFilterChanged: _setFilter,
|
||||
scrollController: _scrollController,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -58,11 +71,13 @@ class _NotificationsScreenContentState extends State<_NotificationsScreenContent
|
||||
class NotificationsView extends StatelessWidget {
|
||||
final NotificationFilter filter;
|
||||
final ValueChanged<NotificationFilter> onFilterChanged;
|
||||
final ScrollController? scrollController;
|
||||
|
||||
const NotificationsView({
|
||||
super.key,
|
||||
this.filter = NotificationFilter.all,
|
||||
required this.onFilterChanged,
|
||||
this.scrollController,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -119,7 +134,7 @@ class NotificationsView extends StatelessWidget {
|
||||
const SizedBox(height: 24),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
context.read<NotificationsBloc>().add(NotificationsFetchRequested());
|
||||
context.read<NotificationsBloc>().add(NotificationsFetchRequested(page: 1));
|
||||
},
|
||||
child: const Text('Повторить'),
|
||||
),
|
||||
@@ -138,9 +153,6 @@ class NotificationsView extends StatelessWidget {
|
||||
return n.category == NotificationCategory.payment;
|
||||
case NotificationFilter.order:
|
||||
return n.category == NotificationCategory.scooter;
|
||||
// || n.category == NotificationCategory.adminInfo
|
||||
// || n.category == NotificationCategory.companyInfo;
|
||||
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
@@ -152,14 +164,39 @@ class NotificationsView extends StatelessWidget {
|
||||
|
||||
return RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
context.read<NotificationsBloc>().add(NotificationsFetchRequested());
|
||||
context.read<NotificationsBloc>().add(NotificationsFetchRequested(page: 1));
|
||||
},
|
||||
child: ListView.builder(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
itemCount: filtered.length,
|
||||
itemBuilder: (context, index) {
|
||||
return _NotificationCard(notification: filtered[index]);
|
||||
child: NotificationListener<ScrollNotification>(
|
||||
onNotification: (scrollInfo) {
|
||||
if (scrollInfo.metrics.pixels >= scrollInfo.metrics.maxScrollExtent - 50 &&
|
||||
scrollInfo.metrics.maxScrollExtent > 0 &&
|
||||
state.hasMore &&
|
||||
!state.isLoadingMore &&
|
||||
state.status == NotificationsStatus.success) {
|
||||
context.read<NotificationsBloc>().add(NotificationsLoadMoreRequested());
|
||||
}
|
||||
return false;
|
||||
},
|
||||
child: ListView.builder(
|
||||
controller: scrollController,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
itemCount: filtered.length + (state.hasMore && filtered.length >= 10 ? 1 : 0),
|
||||
itemBuilder: (context, index) {
|
||||
if (index == filtered.length && state.hasMore && filtered.length >= 10) {
|
||||
return const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 16),
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
width: 24,
|
||||
height: 24,
|
||||
child: CircularProgressIndicator(color: Colors.white, strokeWidth: 2),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return _NotificationCard(notification: filtered[index]);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
@@ -308,16 +345,23 @@ class _NotificationCard extends StatelessWidget {
|
||||
}
|
||||
|
||||
String _formatDate(DateTime date) {
|
||||
final localDate = date.toLocal();
|
||||
final now = DateTime.now();
|
||||
final difference = now.difference(date);
|
||||
|
||||
if (difference.inDays == 0) {
|
||||
return 'Сегодня, ${date.hour.toString().padLeft(2, '0')}:${date.minute.toString().padLeft(2, '0')}';
|
||||
} else if (difference.inDays == 1) {
|
||||
return 'Вчера, ${date.hour.toString().padLeft(2, '0')}:${date.minute.toString().padLeft(2, '0')}';
|
||||
} else {
|
||||
return '${date.day.toString().padLeft(2, '0')}.${date.month.toString().padLeft(2, '0')}.${date.year}';
|
||||
if (localDate.year == now.year &&
|
||||
localDate.month == now.month &&
|
||||
localDate.day == now.day) {
|
||||
return 'Сегодня, ${localDate.hour.toString().padLeft(2, '0')}:${localDate.minute.toString().padLeft(2, '0')}';
|
||||
}
|
||||
|
||||
final yesterday = now.subtract(const Duration(days: 1));
|
||||
if (localDate.year == yesterday.year &&
|
||||
localDate.month == yesterday.month &&
|
||||
localDate.day == yesterday.day) {
|
||||
return 'Вчера, ${localDate.hour.toString().padLeft(2, '0')}:${localDate.minute.toString().padLeft(2, '0')}';
|
||||
}
|
||||
|
||||
return '${localDate.day.toString().padLeft(2, '0')}.${localDate.month.toString().padLeft(2, '0')}.${localDate.year}';
|
||||
}
|
||||
|
||||
String _getTypeLabel(NotificationType type) {
|
||||
|
||||
@@ -5,6 +5,7 @@ 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 '../../domain/usecase/get_profile_usecase.dart';
|
||||
import '../../domain/usecase/set_main_payment_card_usecase.dart';
|
||||
import '../components/custom_app_bar.dart';
|
||||
import '../components/gradient_button.dart';
|
||||
@@ -134,11 +135,6 @@ class _PaymentConfirmScreenContent extends StatelessWidget {
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
_buildInfoLabel('Начислено баллов:'),
|
||||
_buildValueRow('points_icon.png', '2 балла'),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
@@ -149,7 +145,7 @@ class _PaymentConfirmScreenContent extends StatelessWidget {
|
||||
_buildInfoLabel('Стоимость поездки:'),
|
||||
_buildValueRow(
|
||||
'money_icon.png',
|
||||
'17,17 BYN',
|
||||
order?.totalOrderPricePrint ?? '- BYN',
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -162,7 +158,7 @@ class _PaymentConfirmScreenContent extends StatelessWidget {
|
||||
_buildInfoLabel('Оплачено:'),
|
||||
_buildValueRow(
|
||||
'money_icon.png',
|
||||
'10,00 BYN',
|
||||
order?.payTotalPricePrint ?? '0,00 BYN',
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -193,9 +189,9 @@ class _PaymentConfirmScreenContent extends StatelessWidget {
|
||||
width: 40,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
const Text(
|
||||
'7,17 BYN',
|
||||
style: TextStyle(
|
||||
Text(
|
||||
order?.unpayTotalPricePrint ?? '0,00 BYN',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 32,
|
||||
fontWeight: FontWeight.bold,
|
||||
@@ -227,6 +223,7 @@ class _PaymentConfirmScreenContent extends StatelessWidget {
|
||||
create: (context) => PaymentMethodSheetBloc(
|
||||
getIt<GetPaymentCardsUsecase>(),
|
||||
getIt<SetMainPaymentCardUsecase>(),
|
||||
getIt<GetProfileUseCase>(),
|
||||
)..add(PaymentMethodSheetStarted()),
|
||||
child: PaymentMethodSheet(
|
||||
initialSelectedCard: state.useBalance ? null : state.selectedCard,
|
||||
|
||||
@@ -80,7 +80,7 @@ class PaymentMethodsScreen extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBalanceCard(BuildContext context, int balance) {
|
||||
Widget _buildBalanceCard(BuildContext context, double balance) {
|
||||
return Container(
|
||||
padding: EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
|
||||
@@ -77,7 +77,7 @@ class _PhoneLoginScreenState extends State<PhoneLoginScreen> {
|
||||
return BlocConsumer<VerifyCodeBloc, VerifyCodeState>(
|
||||
listener: (context, state) {
|
||||
if (state.isSuccess) {
|
||||
context.go("/pin");
|
||||
|
||||
} else if (state.error != null) {
|
||||
setState(() { _isFirstTry = false; });
|
||||
codeController.clear();
|
||||
|
||||
@@ -13,7 +13,8 @@ import '../viewmodel/scooter_detail_bloc.dart';
|
||||
|
||||
class ScooterDetailScreen extends StatelessWidget {
|
||||
final String address;
|
||||
const ScooterDetailScreen({super.key, required this.address});
|
||||
final double distance;
|
||||
const ScooterDetailScreen({super.key, required this.address, required this.distance});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -84,7 +85,7 @@ class ScooterDetailScreen extends StatelessWidget {
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(child: ScooterInfoSection(distance: scooter?.distance ?? 0, location: address)),
|
||||
Expanded(child: ScooterInfoSection(distance: distance, location: address, batteryLevel: scooter?.batteryLevel ?? 0,)),
|
||||
const SizedBox(width: 20),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -6,6 +6,7 @@ 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 '../../domain/usecase/get_profile_usecase.dart';
|
||||
import '../../domain/usecase/set_main_payment_card_usecase.dart';
|
||||
import '../components/app_checkbox.dart';
|
||||
import '../components/custom_app_bar.dart';
|
||||
@@ -170,6 +171,7 @@ class SubscriptionDetailsScreen extends StatelessWidget {
|
||||
create: (context) => PaymentMethodSheetBloc(
|
||||
getIt<GetPaymentCardsUsecase>(),
|
||||
getIt<SetMainPaymentCardUsecase>(),
|
||||
getIt<GetProfileUseCase>(),
|
||||
)..add(PaymentMethodSheetStarted()),
|
||||
child: const PaymentMethodSheet(showBalance: false),
|
||||
),
|
||||
|
||||
@@ -1,84 +1,108 @@
|
||||
import 'package:be_happy/presentation/viewmodel/payment_methods_bloc.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:path/path.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 '../../domain/usecase/get_profile_usecase.dart';
|
||||
import '../../domain/usecase/set_main_payment_card_usecase.dart';
|
||||
import '../components/app_checkbox.dart';
|
||||
import '../components/custom_app_bar.dart'; // ✅ Уже есть
|
||||
import '../components/custom_app_bar.dart';
|
||||
import '../components/gradient_button.dart';
|
||||
import '../components/payment_option.dart';
|
||||
import '../components/sheet/payment_method_sheet.dart';
|
||||
import '../event/payment_method_sheet_event.dart';
|
||||
import '../event/payment_methods_event.dart';
|
||||
import '../event/top_up_event.dart';
|
||||
import '../state/top_up_state.dart';
|
||||
import '../viewmodel/payment_method_sheet_bloc.dart';
|
||||
import '../viewmodel/top_up_bloc.dart';
|
||||
|
||||
class TopUpScreen extends StatelessWidget {
|
||||
const TopUpScreen({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Container(
|
||||
decoration: const BoxDecoration(gradient: AppColors.phoneScreenBg),
|
||||
child: SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
decoration: const BoxDecoration(gradient: AppColors.phoneScreenBg),
|
||||
child: SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: BlocListener<TopUpBloc, TopUpState>(
|
||||
listener: (context, state) {
|
||||
if (state is TopUpPaymentSuccess) {
|
||||
context.go('/home/payment-methods/top-up/result/success');
|
||||
} else if (state is TopUpPaymentFailure) {
|
||||
context.go('/home/payment-methods/top-up/result/failed');
|
||||
}
|
||||
},
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(height: 16),
|
||||
const CustomAppBar(title: 'Пополнение баланса'),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
BlocBuilder<TopUpBloc, TopUpState>(
|
||||
builder: (context, state) {
|
||||
if (state.isLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
if (state is TopUpLoading) {
|
||||
return const Expanded(
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
);
|
||||
}
|
||||
|
||||
return Expanded(
|
||||
child: Stack(
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildTariffList(state, context),
|
||||
const SizedBox(height: 30),
|
||||
_buildPriceInfo(state),
|
||||
const SizedBox(height: 40),
|
||||
const Text(
|
||||
'Способ оплаты',
|
||||
style: TextStyle(color: Colors.white70),
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
_buildCardSelector(state, context),
|
||||
const SizedBox(height: 20),
|
||||
_buildAgreement(state, context),
|
||||
const Spacer(),
|
||||
_buildPayButton(state, () {
|
||||
context.read<TopUpBloc>().add(TopUpButtonPressed());
|
||||
}),
|
||||
const SizedBox(height: 30),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
if (state is TopUpLoaded) {
|
||||
if (state.isProcessingPayment) {
|
||||
return const Expanded(
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
);
|
||||
}
|
||||
|
||||
return Expanded(
|
||||
child: Stack(
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildTariffList(state, context),
|
||||
const SizedBox(height: 30),
|
||||
_buildPriceInfo(state),
|
||||
const SizedBox(height: 40),
|
||||
const Text(
|
||||
'Способ оплаты',
|
||||
style: TextStyle(color: Colors.white70),
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
_buildCardSelector(state, context),
|
||||
const SizedBox(height: 20),
|
||||
_buildAgreement(state, context),
|
||||
const Spacer(),
|
||||
_buildPayButton(state, () {
|
||||
context.read<TopUpBloc>().add(TopUpButtonPressed());
|
||||
}),
|
||||
const SizedBox(height: 30),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTariffList(TopUpState state, BuildContext context) {
|
||||
Widget _buildTariffList(TopUpLoaded state, BuildContext context) {
|
||||
return SizedBox(
|
||||
height: 120,
|
||||
child: ListView.separated(
|
||||
@@ -89,14 +113,11 @@ class TopUpScreen extends StatelessWidget {
|
||||
final tariff = state.certificates[index];
|
||||
final isSelected = state.selectedTariff == tariff;
|
||||
return GestureDetector(
|
||||
onTap: () =>
|
||||
context.read<TopUpBloc>().add(SelectCertificate(tariff)),
|
||||
onTap: () => context.read<TopUpBloc>().add(SelectCertificate(tariff)),
|
||||
child: Container(
|
||||
width: 140,
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected
|
||||
? const Color(0xFF80FFC1)
|
||||
: Colors.white.withOpacity(0.1),
|
||||
color: isSelected ? const Color(0xFF80FFC1) : Colors.white.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: Colors.white24),
|
||||
),
|
||||
@@ -127,7 +148,7 @@ class TopUpScreen extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPriceInfo(TopUpState state) {
|
||||
Widget _buildPriceInfo(TopUpLoaded state) {
|
||||
if (state.selectedTariff == null) return const SizedBox.shrink();
|
||||
return Center(
|
||||
child: Column(
|
||||
@@ -157,7 +178,7 @@ class TopUpScreen extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCardSelector(TopUpState state, BuildContext context) {
|
||||
Widget _buildCardSelector(TopUpLoaded state, BuildContext context) {
|
||||
return state.selectedCard != null
|
||||
? Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
@@ -170,16 +191,14 @@ class TopUpScreen extends StatelessWidget {
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (innerContext) =>
|
||||
BlocProvider(
|
||||
create: (context) =>
|
||||
PaymentMethodSheetBloc(
|
||||
getIt<GetPaymentCardsUsecase>(),
|
||||
getIt<SetMainPaymentCardUsecase>(),
|
||||
)
|
||||
..add(PaymentMethodSheetStarted()),
|
||||
child: const PaymentMethodSheet(showBalance: false,),
|
||||
),
|
||||
builder: (innerContext) => BlocProvider(
|
||||
create: (context) => PaymentMethodSheetBloc(
|
||||
getIt<GetPaymentCardsUsecase>(),
|
||||
getIt<SetMainPaymentCardUsecase>(),
|
||||
getIt<GetProfileUseCase>(),
|
||||
)..add(PaymentMethodSheetStarted()),
|
||||
child: const PaymentMethodSheet(showBalance: false),
|
||||
),
|
||||
);
|
||||
|
||||
if (selectedCard != null) {
|
||||
@@ -210,7 +229,6 @@ class TopUpScreen extends StatelessWidget {
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
|
||||
const Text(
|
||||
'Способ оплаты',
|
||||
style: TextStyle(
|
||||
@@ -220,21 +238,9 @@ class TopUpScreen extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 12,
|
||||
color: Colors.white.withOpacity(0.6),
|
||||
),
|
||||
Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 12,
|
||||
color: Colors.white.withOpacity(0.4),
|
||||
),
|
||||
Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 12,
|
||||
color: Colors.white.withOpacity(0.2),
|
||||
),
|
||||
Icon(Icons.arrow_forward_ios, size: 12, color: Colors.white.withOpacity(0.6)),
|
||||
Icon(Icons.arrow_forward_ios, size: 12, color: Colors.white.withOpacity(0.4)),
|
||||
Icon(Icons.arrow_forward_ios, size: 12, color: Colors.white.withOpacity(0.2)),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -244,20 +250,19 @@ class TopUpScreen extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAgreement(TopUpState state, BuildContext context) {
|
||||
Widget _buildAgreement(TopUpLoaded state, BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
AppCheckbox(
|
||||
value: state.isAgreed,
|
||||
onChanged: (v) =>
|
||||
context.read<TopUpBloc>().add(ToggleAgreement(v ?? false)),
|
||||
onChanged: (v) => context.read<TopUpBloc>().add(ToggleAgreement(v ?? false)),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Flexible(
|
||||
const Flexible(
|
||||
child: Text.rich(
|
||||
TextSpan(
|
||||
text: 'Я принимаю условия покупки бонусного пакета, при котором денежные средства не подлежат возврату... ',
|
||||
style: const TextStyle(
|
||||
text: 'Я принимаю условия покупки бонусного пакека, при котором денежные средства не подлежат возврату... ',
|
||||
style: TextStyle(
|
||||
color: AppColors.white70,
|
||||
fontSize: 12,
|
||||
),
|
||||
@@ -268,7 +273,7 @@ class TopUpScreen extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPayButton(TopUpState state, VoidCallback onTap) {
|
||||
Widget _buildPayButton(TopUpLoaded state, VoidCallback onTap) {
|
||||
return GradientButton(
|
||||
text: 'Оплатить',
|
||||
onTap: state.isAgreed ? onTap : null,
|
||||
@@ -279,5 +284,4 @@ class TopUpScreen extends StatelessWidget {
|
||||
fontSize: 16,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user