fix
This commit is contained in:
@@ -8,6 +8,7 @@
|
|||||||
<queries>
|
<queries>
|
||||||
<intent>
|
<intent>
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
<data android:scheme="https" />
|
<data android:scheme="https" />
|
||||||
</intent>
|
</intent>
|
||||||
<intent>
|
<intent>
|
||||||
@@ -49,6 +50,14 @@
|
|||||||
<action android:name="android.intent.action.MAIN"/>
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
<category android:name="android.intent.category.LAUNCHER"/>
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
|
<intent-filter android:label="card_binding_deep_link">
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
|
<data android:scheme="com.behappy.app" android:host="card-result" />
|
||||||
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<!-- Don't delete the meta-data below.
|
<!-- Don't delete the meta-data below.
|
||||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||||
|
|||||||
@@ -2,3 +2,7 @@ org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m
|
|||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
android.enableJetifier=true
|
android.enableJetifier=true
|
||||||
yandexMapkit.variant=full
|
yandexMapkit.variant=full
|
||||||
|
# This builtInKotlin flag was added automatically by Flutter migrator
|
||||||
|
android.builtInKotlin=false
|
||||||
|
# This newDsl flag was added automatically by Flutter migrator
|
||||||
|
android.newDsl=false
|
||||||
|
|||||||
BIN
assets/icons/card_result_screen.png
Normal file
BIN
assets/icons/card_result_screen.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 275 KiB |
BIN
assets/icons/failed.png
Normal file
BIN
assets/icons/failed.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.9 KiB |
BIN
assets/icons/success.png
Normal file
BIN
assets/icons/success.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.7 KiB |
@@ -2,7 +2,7 @@ import '../../domain/entities/payment_card.dart';
|
|||||||
|
|
||||||
class PaymentCardResponseDto {
|
class PaymentCardResponseDto {
|
||||||
final int id;
|
final int id;
|
||||||
final int clientId;
|
final String bindingId;
|
||||||
final int expirationMonth;
|
final int expirationMonth;
|
||||||
final int expirationYear;
|
final int expirationYear;
|
||||||
final String cardHolder;
|
final String cardHolder;
|
||||||
@@ -12,7 +12,7 @@ class PaymentCardResponseDto {
|
|||||||
|
|
||||||
PaymentCardResponseDto({
|
PaymentCardResponseDto({
|
||||||
required this.id,
|
required this.id,
|
||||||
required this.clientId,
|
required this.bindingId,
|
||||||
required this.expirationMonth,
|
required this.expirationMonth,
|
||||||
required this.expirationYear,
|
required this.expirationYear,
|
||||||
required this.cardHolder,
|
required this.cardHolder,
|
||||||
@@ -24,7 +24,7 @@ class PaymentCardResponseDto {
|
|||||||
factory PaymentCardResponseDto.fromJson(Map<String, dynamic> json) {
|
factory PaymentCardResponseDto.fromJson(Map<String, dynamic> json) {
|
||||||
return PaymentCardResponseDto(
|
return PaymentCardResponseDto(
|
||||||
id: json['id'] as int,
|
id: json['id'] as int,
|
||||||
clientId: json['clientId'] as int,
|
bindingId: json['bindingId'] as String,
|
||||||
expirationMonth: json['expirationMonth'] as int,
|
expirationMonth: json['expirationMonth'] as int,
|
||||||
expirationYear: json['expirationYear'] as int,
|
expirationYear: json['expirationYear'] as int,
|
||||||
cardHolder: json['cardHolder'] as String,
|
cardHolder: json['cardHolder'] as String,
|
||||||
@@ -37,13 +37,9 @@ class PaymentCardResponseDto {
|
|||||||
PaymentCard toEntity(String? fullCardNumber) {
|
PaymentCard toEntity(String? fullCardNumber) {
|
||||||
return PaymentCard(
|
return PaymentCard(
|
||||||
id: id,
|
id: id,
|
||||||
clientId: clientId,
|
bindingId: bindingId,
|
||||||
expirationMonth: expirationMonth,
|
|
||||||
expirationYear: expirationYear,
|
|
||||||
cardHolder: cardHolder,
|
|
||||||
cardLastNumber: cardLastNumber,
|
cardLastNumber: cardLastNumber,
|
||||||
isMain: isMain,
|
isMain: isMain,
|
||||||
type: type,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,11 @@ class ApiService {
|
|||||||
try {
|
try {
|
||||||
final response = await _dio.post(
|
final response = await _dio.post(
|
||||||
"$baseUrl/auth/client/login",
|
"$baseUrl/auth/client/login",
|
||||||
data: {"phone": phone, "model": model, "systemId": systemId},
|
data: {
|
||||||
|
"phone": phone,
|
||||||
|
"model": model,
|
||||||
|
"systemId": systemId
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.statusCode == 200 || response.statusCode == 201) {
|
if (response.statusCode == 200 || response.statusCode == 201) {
|
||||||
@@ -177,13 +181,9 @@ class ApiService {
|
|||||||
cardsJson.map((cardJson) async {
|
cardsJson.map((cardJson) async {
|
||||||
return PaymentCard(
|
return PaymentCard(
|
||||||
id: cardJson['id'],
|
id: cardJson['id'],
|
||||||
clientId: cardJson['clientId'],
|
bindingId: cardJson['bindingId'],
|
||||||
expirationMonth: cardJson['expirationMonth'],
|
|
||||||
expirationYear: cardJson['expirationYear'],
|
|
||||||
cardHolder: cardJson['cardHolder'],
|
|
||||||
cardLastNumber: cardJson['cardLastNumber'],
|
cardLastNumber: cardJson['cardLastNumber'],
|
||||||
isMain: cardJson['isMain'],
|
isMain: cardJson['isMain'],
|
||||||
type: cardJson['cardType'],
|
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -517,13 +517,13 @@ class ApiService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> activateSubscription({required int optionId}) async {
|
Future<bool> activateSubscription({required int optionId, required int cardId}) async {
|
||||||
const url = "$baseUrl/scootersubscription/client";
|
const url = "$baseUrl/scootersubscription/client";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final response = await _dio.post(
|
final response = await _dio.post(
|
||||||
url,
|
url,
|
||||||
data: {"optionId": optionId},
|
data: {"optionId": optionId, "cardId": cardId},
|
||||||
options: await _getAuthOptions(),
|
options: await _getAuthOptions(),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -539,31 +539,25 @@ class ApiService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> addPaymentCard({
|
Future<Map<String, dynamic>> addPaymentCard() async {
|
||||||
required String cardNumber,
|
|
||||||
required String cardHolder,
|
|
||||||
required int expirationMonth,
|
|
||||||
required int expirationYear,
|
|
||||||
required String cvv,
|
|
||||||
}) async {
|
|
||||||
const url = "$baseUrl/client/card";
|
const url = "$baseUrl/client/card";
|
||||||
final cleanCardNumber = cardNumber.replaceAll(' ', '');
|
/*final cleanCardNumber = cardNumber.replaceAll(' ', '');*/
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final response = await _dio.post(
|
final response = await _dio.post(
|
||||||
url,
|
url,
|
||||||
data: {
|
/*data: {
|
||||||
"cardNumber": cleanCardNumber,
|
"cardNumber": cleanCardNumber,
|
||||||
"cardHolder": cardHolder,
|
"cardHolder": cardHolder,
|
||||||
"expirationMonth": expirationMonth,
|
"expirationMonth": expirationMonth,
|
||||||
"expirationYear": expirationYear,
|
"expirationYear": expirationYear,
|
||||||
"cvv": cvv,
|
"cvv": cvv,
|
||||||
},
|
},*/
|
||||||
options: await _getAuthOptions(),
|
options: await _getAuthOptions(),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.statusCode == 200 || response.statusCode == 201) {
|
if (response.statusCode == 200 || response.statusCode == 201) {
|
||||||
return;
|
return response.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw AuthException('Непредвиденный статус: ${response.statusCode}', 0);
|
throw AuthException('Непредвиденный статус: ${response.statusCode}', 0);
|
||||||
|
|||||||
@@ -32,25 +32,25 @@ class PaymentRepositoryImpl implements PaymentRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Result<void>> addPaymentCard({
|
Future<Result<Map<String, dynamic>>> addPaymentCard(
|
||||||
required String cardNumber,
|
/*required String cardNumber,
|
||||||
required String cardHolder,
|
required String cardHolder,
|
||||||
required String expiryMonth,
|
required String expiryMonth,
|
||||||
required String expiryYear,
|
required String expiryYear,
|
||||||
required String cvv,
|
required String cvv,*/
|
||||||
}) async {
|
) async {
|
||||||
try {
|
try {
|
||||||
await apiService.addPaymentCard(
|
final result = await apiService.addPaymentCard(
|
||||||
cardNumber: cardNumber,
|
/*cardNumber: cardNumber,
|
||||||
cardHolder: cardHolder,
|
cardHolder: cardHolder,
|
||||||
expirationMonth: int.parse(expiryMonth),
|
expirationMonth: int.parse(expiryMonth),
|
||||||
expirationYear: int.parse(expiryYear),
|
expirationYear: int.parse(expiryYear),
|
||||||
cvv: cvv,
|
cvv: cvv,*/
|
||||||
);
|
);
|
||||||
|
|
||||||
// await securityService.saveCardFullNumber(cardId, cardNumber);
|
// await securityService.saveCardFullNumber(cardId, cardNumber);
|
||||||
|
|
||||||
return Success(null);
|
return Success(result);
|
||||||
} on AuthException catch (e) {
|
} on AuthException catch (e) {
|
||||||
return Failure(AuthFailure(e.attemptsLeft));
|
return Failure(AuthFailure(e.attemptsLeft));
|
||||||
} on AuthBlockException catch (_) {
|
} on AuthBlockException catch (_) {
|
||||||
@@ -98,9 +98,9 @@ class PaymentRepositoryImpl implements PaymentRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Result<bool>> activateSubscription(int optionId) async {
|
Future<Result<bool>> activateSubscription(int optionId, int cardId) async {
|
||||||
try {
|
try {
|
||||||
final success = await apiService.activateSubscription(optionId: optionId);
|
final success = await apiService.activateSubscription(optionId: optionId, cardId: cardId);
|
||||||
return Success(success);
|
return Success(success);
|
||||||
} on AuthException catch (e) {
|
} on AuthException catch (e) {
|
||||||
return Failure(AuthFailure(e.attemptsLeft));
|
return Failure(AuthFailure(e.attemptsLeft));
|
||||||
|
|||||||
@@ -305,7 +305,7 @@ Future<void> setupDependencies() async {
|
|||||||
|
|
||||||
getIt.registerFactory<PhoneAuthBloc>(() => PhoneAuthBloc(getIt()));
|
getIt.registerFactory<PhoneAuthBloc>(() => PhoneAuthBloc(getIt()));
|
||||||
|
|
||||||
getIt.registerFactory<VerifyCodeBloc>(() => VerifyCodeBloc(getIt()));
|
getIt.registerFactory<VerifyCodeBloc>(() => VerifyCodeBloc(getIt(), getIt()));
|
||||||
|
|
||||||
getIt.registerFactory<ProfileBloc>(() => ProfileBloc(getIt(), getIt(), getIt()));
|
getIt.registerFactory<ProfileBloc>(() => ProfileBloc(getIt(), getIt(), getIt()));
|
||||||
|
|
||||||
@@ -337,7 +337,7 @@ Future<void> setupDependencies() async {
|
|||||||
|
|
||||||
|
|
||||||
getIt.registerFactory<PaymentMethodSheetBloc>(
|
getIt.registerFactory<PaymentMethodSheetBloc>(
|
||||||
() => PaymentMethodSheetBloc(getIt()),
|
() => PaymentMethodSheetBloc(getIt(), getIt()),
|
||||||
);
|
);
|
||||||
|
|
||||||
getIt.registerFactory<CurrentRidesBloc>(() => CurrentRidesBloc(getIt()));
|
getIt.registerFactory<CurrentRidesBloc>(() => CurrentRidesBloc(getIt()));
|
||||||
|
|||||||
@@ -1,43 +1,27 @@
|
|||||||
class PaymentCard {
|
class PaymentCard {
|
||||||
final int id;
|
final int id;
|
||||||
final int clientId;
|
final String? bindingId;
|
||||||
final int expirationMonth;
|
|
||||||
final int expirationYear;
|
|
||||||
final String cardHolder;
|
|
||||||
final String cardLastNumber;
|
final String cardLastNumber;
|
||||||
final bool isMain;
|
final bool isMain;
|
||||||
final String type;
|
|
||||||
|
|
||||||
PaymentCard({
|
PaymentCard({
|
||||||
required this.id,
|
required this.id,
|
||||||
required this.clientId,
|
required this.bindingId,
|
||||||
required this.expirationMonth,
|
|
||||||
required this.expirationYear,
|
|
||||||
required this.cardHolder,
|
|
||||||
required this.cardLastNumber,
|
required this.cardLastNumber,
|
||||||
required this.isMain,
|
required this.isMain,
|
||||||
required this.type,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
PaymentCard copyWith({
|
PaymentCard copyWith({
|
||||||
int? id,
|
int? id,
|
||||||
int? clientId,
|
String? bindingId,
|
||||||
int? expirationMonth,
|
|
||||||
int? expirationYear,
|
|
||||||
String? cardHolder,
|
|
||||||
String? cardLastNumber,
|
String? cardLastNumber,
|
||||||
bool? isMain,
|
bool? isMain,
|
||||||
String? type,
|
|
||||||
}) {
|
}) {
|
||||||
return PaymentCard(
|
return PaymentCard(
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
clientId: clientId ?? this.clientId,
|
bindingId: bindingId ?? this.bindingId,
|
||||||
expirationMonth: expirationMonth ?? this.expirationMonth,
|
|
||||||
expirationYear: expirationYear ?? this.expirationYear,
|
|
||||||
cardHolder: cardHolder ?? this.cardHolder,
|
|
||||||
cardLastNumber: cardLastNumber ?? this.cardLastNumber,
|
cardLastNumber: cardLastNumber ?? this.cardLastNumber,
|
||||||
isMain: isMain ?? this.isMain,
|
isMain: isMain ?? this.isMain,
|
||||||
type: type ?? this.type,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,17 +4,17 @@ import '../entities/payment_card.dart';
|
|||||||
abstract class PaymentRepository {
|
abstract class PaymentRepository {
|
||||||
Future<Result<List<PaymentCard>>> getPaymentCards();
|
Future<Result<List<PaymentCard>>> getPaymentCards();
|
||||||
|
|
||||||
Future<Result<void>> addPaymentCard({
|
Future<Result<Map<String, dynamic>>> addPaymentCard(
|
||||||
required String cardNumber,
|
/*required String cardNumber,
|
||||||
required String cardHolder,
|
required String cardHolder,
|
||||||
required String expiryMonth,
|
required String expiryMonth,
|
||||||
required String expiryYear,
|
required String expiryYear,
|
||||||
required String cvv,
|
required String cvv,*/
|
||||||
});
|
);
|
||||||
|
|
||||||
Future<Result<void>> setMainPaymentCard(int cardId);
|
Future<Result<void>> setMainPaymentCard(int cardId);
|
||||||
|
|
||||||
Future<Result<void>> removePaymentCard(int cardId);
|
Future<Result<void>> removePaymentCard(int cardId);
|
||||||
|
|
||||||
Future<Result<bool>> activateSubscription(int optionId);
|
Future<Result<bool>> activateSubscription(int optionId, int cardId);
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,7 @@ class ActivateSubscriptionUsecase {
|
|||||||
|
|
||||||
ActivateSubscriptionUsecase(this.repository);
|
ActivateSubscriptionUsecase(this.repository);
|
||||||
|
|
||||||
Future<Result<bool>> call(int optionId) {
|
Future<Result<bool>> call(int optionId, int cardId) {
|
||||||
return repository.activateSubscription(optionId);
|
return repository.activateSubscription(optionId, cardId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,19 +6,19 @@ class AddPaymentCardUsecase {
|
|||||||
|
|
||||||
AddPaymentCardUsecase(this.repository);
|
AddPaymentCardUsecase(this.repository);
|
||||||
|
|
||||||
Future<Result<void>> call({
|
Future<Result<Map<String, dynamic>>> call(
|
||||||
required String cardNumber,
|
/*required String cardNumber,
|
||||||
required String cardHolder,
|
required String cardHolder,
|
||||||
required String expiryMonth,
|
required String expiryMonth,
|
||||||
required String expiryYear,
|
required String expiryYear,
|
||||||
required String cvv,
|
required String cvv,*/
|
||||||
}) {
|
) {
|
||||||
return repository.addPaymentCard(
|
return repository.addPaymentCard(
|
||||||
cardNumber: cardNumber,
|
/*cardNumber: cardNumber,
|
||||||
cardHolder: cardHolder,
|
cardHolder: cardHolder,
|
||||||
expiryMonth: expiryMonth,
|
expiryMonth: expiryMonth,
|
||||||
expiryYear: expiryYear,
|
expiryYear: expiryYear,
|
||||||
cvv: cvv,
|
cvv: cvv,*/
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,18 +2,22 @@ import 'package:flutter/material.dart';
|
|||||||
import 'scooter_info_item.dart';
|
import 'scooter_info_item.dart';
|
||||||
|
|
||||||
class ScooterInfoSection extends StatelessWidget {
|
class ScooterInfoSection extends StatelessWidget {
|
||||||
const ScooterInfoSection({super.key});
|
final double distance;
|
||||||
|
final String location;
|
||||||
|
const ScooterInfoSection({super.key, required this.distance, required this.location});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Column(
|
return Column(
|
||||||
children: const [
|
children: [
|
||||||
ScooterInfoItem(icon: 'assets/icons/bolt.png', text: '47 км или 4 часа 17 минут'),
|
ScooterInfoItem(icon: 'assets/icons/bolt.png', text: '47 км или 4 часа 17 минут'),
|
||||||
ScooterInfoItem(icon: 'assets/icons/speed.png', text: 'max = 25 км/ч'),
|
ScooterInfoItem(icon: 'assets/icons/speed.png', text: 'max = 25 км/ч'),
|
||||||
ScooterInfoItem(icon: 'assets/icons/location.png', text: 'пр. Московский, 33'),
|
ScooterInfoItem(icon: 'assets/icons/location.png', text: location),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
ScooterInfoItem(icon: 'assets/icons/person.png', text: '120 м'),
|
ScooterInfoItem(icon: 'assets/icons/person.png', text: distance.toInt() >= 1000
|
||||||
|
? '${((distance.toInt()) / 1000).toStringAsFixed(1)} km'
|
||||||
|
: '${distance.toInt()} m',),
|
||||||
SizedBox(width: 16),
|
SizedBox(width: 16),
|
||||||
ScooterInfoItem(icon: 'assets/icons/time.png', text: '1 минута'),
|
ScooterInfoItem(icon: 'assets/icons/time.png', text: '1 минута'),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -128,6 +128,7 @@ class _ScooterBottomSheetState extends State<ScooterBottomSheet> {
|
|||||||
child: _ScooterCard(
|
child: _ScooterCard(
|
||||||
scooter: scooter,
|
scooter: scooter,
|
||||||
isActive: isActive,
|
isActive: isActive,
|
||||||
|
address: state.address ?? "Unknown address",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -151,8 +152,9 @@ class _ScooterBottomSheetState extends State<ScooterBottomSheet> {
|
|||||||
class _ScooterCard extends StatelessWidget {
|
class _ScooterCard extends StatelessWidget {
|
||||||
final Scooter scooter;
|
final Scooter scooter;
|
||||||
final bool isActive;
|
final bool isActive;
|
||||||
|
final String address;
|
||||||
|
|
||||||
const _ScooterCard({required this.scooter, required this.isActive});
|
const _ScooterCard({required this.scooter, required this.isActive, required this.address});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -186,7 +188,9 @@ class _ScooterCard extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
Text(
|
Text(
|
||||||
"${(scooter.distance?.toInt()) ?? 0}m",
|
(scooter.distance?.toInt() ?? 0) >= 1000
|
||||||
|
? '${((scooter.distance?.toInt() ?? 0) / 1000).toStringAsFixed(1)} km'
|
||||||
|
: '${scooter.distance?.toInt() ?? 0} m',
|
||||||
style: const TextStyle(color: Colors.white, fontSize: 14),
|
style: const TextStyle(color: Colors.white, fontSize: 14),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -229,7 +233,7 @@ class _ScooterCard extends StatelessWidget {
|
|||||||
|
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.go('/home/scooter/${scooter.id}');
|
context.go('/home/scooter/${scooter.id}', extra: address);
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
height: 42,
|
height: 42,
|
||||||
@@ -241,7 +245,7 @@ class _ScooterCard extends StatelessWidget {
|
|||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.go('/home/scooter/${scooter.id}');
|
context.go('/home/scooter/${scooter.id}', extra: address);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -192,13 +192,18 @@ class _PaymentMethodSheetState extends State<PaymentMethodSheet> {
|
|||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 12),
|
padding: const EdgeInsets.only(bottom: 12),
|
||||||
child: PaymentOption(
|
child: PaymentOption(
|
||||||
title: card.type,
|
title: card.isMain ? "Основная" : "",
|
||||||
subtitle: '****${card.cardLastNumber}',
|
subtitle: '${card.cardLastNumber}',
|
||||||
isSelected: _selectedPaymentMethod == index,
|
isSelected: _selectedPaymentMethod == index,
|
||||||
onTap: () {
|
onTap: () async {
|
||||||
setState(() {
|
setState(() {
|
||||||
_selectedPaymentMethod = index;
|
_selectedPaymentMethod = index;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
context.read<PaymentMethodSheetBloc>().add(
|
||||||
|
SelectPaymentCardRequested(card),
|
||||||
|
);
|
||||||
|
|
||||||
Navigator.pop(context, card);
|
Navigator.pop(context, card);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -151,6 +151,7 @@ class _ScooterBottomSheetState extends State<ScooterBottomSheet> {
|
|||||||
child: _ScooterCard(
|
child: _ScooterCard(
|
||||||
scooter: scooter,
|
scooter: scooter,
|
||||||
isActive: isActive,
|
isActive: isActive,
|
||||||
|
address: state.address ?? "Unknown address",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -174,9 +175,10 @@ class _ScooterBottomSheetState extends State<ScooterBottomSheet> {
|
|||||||
|
|
||||||
class _ScooterCard extends StatelessWidget {
|
class _ScooterCard extends StatelessWidget {
|
||||||
final Scooter scooter;
|
final Scooter scooter;
|
||||||
|
final String address;
|
||||||
final bool isActive;
|
final bool isActive;
|
||||||
|
|
||||||
const _ScooterCard({required this.scooter, required this.isActive});
|
const _ScooterCard({required this.scooter, required this.isActive, required this.address});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -210,7 +212,9 @@ class _ScooterCard extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
Text(
|
Text(
|
||||||
"${(scooter.distance?.toInt()) ?? 0}m",
|
(scooter.distance?.toInt() ?? 0) >= 1000
|
||||||
|
? '${((scooter.distance?.toInt() ?? 0) / 1000).toStringAsFixed(1)} km'
|
||||||
|
: '${scooter.distance?.toInt() ?? 0} m',
|
||||||
style: const TextStyle(color: Colors.white, fontSize: 14),
|
style: const TextStyle(color: Colors.white, fontSize: 14),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -262,7 +266,7 @@ class _ScooterCard extends StatelessWidget {
|
|||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.pop(context, scooter);
|
Navigator.pop(context, [scooter, address]);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import '../../../domain/entities/payment_card.dart';
|
|||||||
import '../../../domain/entities/scooter.dart';
|
import '../../../domain/entities/scooter.dart';
|
||||||
import '../../../domain/entities/tariff.dart';
|
import '../../../domain/entities/tariff.dart';
|
||||||
import '../../../domain/usecase/get_payment_cards_usecase.dart';
|
import '../../../domain/usecase/get_payment_cards_usecase.dart';
|
||||||
|
import '../../../domain/usecase/set_main_payment_card_usecase.dart';
|
||||||
import '../../event/payment_method_sheet_event.dart';
|
import '../../event/payment_method_sheet_event.dart';
|
||||||
import '../../event/tariff_sheet_event.dart';
|
import '../../event/tariff_sheet_event.dart';
|
||||||
import '../../state/tariff_sheet_state.dart';
|
import '../../state/tariff_sheet_state.dart';
|
||||||
@@ -288,10 +289,10 @@ class _TariffSheetState extends State<TariffSheet> {
|
|||||||
child: PaymentOption(
|
child: PaymentOption(
|
||||||
title: state.useBalance
|
title: state.useBalance
|
||||||
? 'Баланс'
|
? 'Баланс'
|
||||||
: state.selectedCard!.type,
|
: "Карта",
|
||||||
subtitle: state.useBalance
|
subtitle: state.useBalance
|
||||||
? '${state.userBalance.toStringAsFixed(2)} BYN'
|
? '${state.userBalance.toStringAsFixed(2)} BYN'
|
||||||
: '****${state.selectedCard!.cardLastNumber}',
|
: '${state.selectedCard!.cardLastNumber}',
|
||||||
isSelected: true,
|
isSelected: true,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
final result = await showModalBottomSheet<dynamic>(
|
final result = await showModalBottomSheet<dynamic>(
|
||||||
@@ -301,6 +302,7 @@ class _TariffSheetState extends State<TariffSheet> {
|
|||||||
builder: (innerContext) => BlocProvider(
|
builder: (innerContext) => BlocProvider(
|
||||||
create: (context) => PaymentMethodSheetBloc(
|
create: (context) => PaymentMethodSheetBloc(
|
||||||
getIt<GetPaymentCardsUsecase>(),
|
getIt<GetPaymentCardsUsecase>(),
|
||||||
|
getIt<SetMainPaymentCardUsecase>(),
|
||||||
)..add(PaymentMethodSheetStarted()),
|
)..add(PaymentMethodSheetStarted()),
|
||||||
child: PaymentMethodSheet(
|
child: PaymentMethodSheet(
|
||||||
initialSelectedCard: state.useBalance ? null : state.selectedCard,
|
initialSelectedCard: state.useBalance ? null : state.selectedCard,
|
||||||
@@ -381,7 +383,6 @@ class _TariffSheetState extends State<TariffSheet> {
|
|||||||
|
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
|
|
||||||
// 🔹 КНОПКА "ЗАБРОНИРОВАТЬ"
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
child: GradientButton(
|
child: GradientButton(
|
||||||
@@ -494,13 +495,10 @@ class _TariffCard extends StatelessWidget {
|
|||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// Резервируем место под иконку инфо справа,
|
|
||||||
// чтобы текст не залез под неё
|
|
||||||
const SizedBox(width: 24),
|
const SizedBox(width: 24),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
// Цена + текст рядом
|
|
||||||
Row(
|
Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.baseline,
|
crossAxisAlignment: CrossAxisAlignment.baseline,
|
||||||
textBaseline: TextBaseline.alphabetic,
|
textBaseline: TextBaseline.alphabetic,
|
||||||
@@ -540,7 +538,6 @@ class _TariffCard extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
||||||
// Кнопка-иконка в верхнем правом углу
|
|
||||||
Positioned(
|
Positioned(
|
||||||
top: 0,
|
top: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
|
|||||||
@@ -121,10 +121,10 @@ class SideMenu extends StatelessWidget {
|
|||||||
_MenuItem(
|
_MenuItem(
|
||||||
icon: 'assets/icons/logout_icon.png',
|
icon: 'assets/icons/logout_icon.png',
|
||||||
title: 'Выход',
|
title: 'Выход',
|
||||||
onTap: () => {
|
onTap: () {
|
||||||
context.go('/login'),
|
context.read<MapBloc>().stopNotificationStream();
|
||||||
context.read<MapBloc>().stopNotificationStream(),
|
context.read<MapBloc>().add(LogoutPressed());
|
||||||
context.read<MapBloc>().add(LogoutPressed()),
|
context.go('/login');
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
import '../../domain/entities/payment_card.dart';
|
||||||
|
|
||||||
sealed class PaymentMethodSheetEvent {}
|
sealed class PaymentMethodSheetEvent {}
|
||||||
|
|
||||||
class PaymentMethodSheetStarted extends PaymentMethodSheetEvent {}
|
class PaymentMethodSheetStarted extends PaymentMethodSheetEvent {}
|
||||||
|
|
||||||
|
class SelectPaymentCardRequested extends PaymentMethodSheetEvent {
|
||||||
|
final PaymentCard card;
|
||||||
|
SelectPaymentCardRequested(this.card);
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ sealed class PaymentMethodsEvent {}
|
|||||||
|
|
||||||
class PaymentMethodsStarted extends PaymentMethodsEvent {}
|
class PaymentMethodsStarted extends PaymentMethodsEvent {}
|
||||||
|
|
||||||
|
class AddPayemntCardPressed extends PaymentMethodsEvent {}
|
||||||
|
|
||||||
class PaymentMethodsDeleteCard extends PaymentMethodsEvent {
|
class PaymentMethodsDeleteCard extends PaymentMethodsEvent {
|
||||||
final int cardId;
|
final int cardId;
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import '../../domain/entities/payment_card.dart';
|
||||||
import '../../domain/entities/subscription_period.dart';
|
import '../../domain/entities/subscription_period.dart';
|
||||||
|
|
||||||
abstract class SubscriptionDetailsEvent {}
|
abstract class SubscriptionDetailsEvent {}
|
||||||
@@ -18,3 +19,9 @@ class ToggleAgreementEvent extends SubscriptionDetailsEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ActivateSubscriptionPressed extends SubscriptionDetailsEvent {}
|
class ActivateSubscriptionPressed extends SubscriptionDetailsEvent {}
|
||||||
|
|
||||||
|
class SelectCardEvent extends SubscriptionDetailsEvent {
|
||||||
|
final PaymentCard card;
|
||||||
|
|
||||||
|
SelectCardEvent(this.card);
|
||||||
|
}
|
||||||
@@ -21,3 +21,5 @@ class ToggleAgreement extends TopUpEvent {
|
|||||||
final bool value;
|
final bool value;
|
||||||
ToggleAgreement(this.value);
|
ToggleAgreement(this.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class TopUpButtonPressed extends TopUpEvent {}
|
||||||
@@ -13,6 +13,11 @@ class CodeChanged extends VerifyCodeEvent {
|
|||||||
CodeChanged(this.code);
|
CodeChanged(this.code);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class TimerTicked extends VerifyCodeEvent {
|
||||||
|
final int secondsLeft;
|
||||||
|
TimerTicked(this.secondsLeft);
|
||||||
|
}
|
||||||
|
|
||||||
class ResendCodePressed extends VerifyCodeEvent {}
|
class ResendCodePressed extends VerifyCodeEvent {}
|
||||||
|
|
||||||
class VerifyCodeSubmitted extends VerifyCodeEvent {}
|
class VerifyCodeSubmitted extends VerifyCodeEvent {}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:be_happy/presentation/screens/card_binding_result_screen.dart';
|
||||||
import 'package:bot_toast/bot_toast.dart';
|
import 'package:bot_toast/bot_toast.dart';
|
||||||
import 'package:be_happy/core/app_colors.dart';
|
import 'package:be_happy/core/app_colors.dart';
|
||||||
import 'package:be_happy/di/service_locator.dart';
|
import 'package:be_happy/di/service_locator.dart';
|
||||||
@@ -43,6 +44,7 @@ import 'package:path/path.dart';
|
|||||||
|
|
||||||
import '../../domain/entities/scooter.dart';
|
import '../../domain/entities/scooter.dart';
|
||||||
import '../../domain/usecase/activate_subscription_usecase.dart';
|
import '../../domain/usecase/activate_subscription_usecase.dart';
|
||||||
|
import '../../domain/usecase/add_payment_card_usecase.dart';
|
||||||
import '../../domain/usecase/book_scooter_usecase.dart';
|
import '../../domain/usecase/book_scooter_usecase.dart';
|
||||||
import '../../domain/usecase/create_pin_usecase.dart';
|
import '../../domain/usecase/create_pin_usecase.dart';
|
||||||
import '../../domain/usecase/get_address_by_point_usecase.dart';
|
import '../../domain/usecase/get_address_by_point_usecase.dart';
|
||||||
@@ -139,7 +141,8 @@ class AppRouter {
|
|||||||
createPinUseCase: getIt<CreatePinUseCase>(),
|
createPinUseCase: getIt<CreatePinUseCase>(),
|
||||||
verifyPinUsecase: getIt<VerifyPinUseCase>(),
|
verifyPinUsecase: getIt<VerifyPinUseCase>(),
|
||||||
isPinSetUsecase: getIt<IsPinSetUsecase>(),
|
isPinSetUsecase: getIt<IsPinSetUsecase>(),
|
||||||
)..add(PinScreenStarted()),
|
)
|
||||||
|
..add(PinScreenStarted()),
|
||||||
child: const PinLoginScreen(),
|
child: const PinLoginScreen(),
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
@@ -151,6 +154,26 @@ class AppRouter {
|
|||||||
path: '/license-agreement',
|
path: '/license-agreement',
|
||||||
builder: (context, state) => const LicenseAgreementScreen(),
|
builder: (context, state) => const LicenseAgreementScreen(),
|
||||||
),
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/card-result',
|
||||||
|
builder: (context, state) {
|
||||||
|
final modeParam = state.uri.queryParameters['status'];
|
||||||
|
|
||||||
|
CardBindingStatus status = CardBindingStatus.success;
|
||||||
|
String title = 'Карта успешно\nпривязана!';
|
||||||
|
|
||||||
|
if (modeParam == 'failed') {
|
||||||
|
status = CardBindingStatus.failed;
|
||||||
|
title = 'Извините, что-то\nпошло не так';
|
||||||
|
}
|
||||||
|
|
||||||
|
return CardBindingResultScreen(
|
||||||
|
status: status,
|
||||||
|
title: title,
|
||||||
|
cardImagePath: 'assets/icons/card_result_screen.png',
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/home',
|
path: '/home',
|
||||||
builder: (context, state) => const MapScreen(),
|
builder: (context, state) => const MapScreen(),
|
||||||
@@ -237,7 +260,8 @@ class AppRouter {
|
|||||||
type: MaterialType.transparency,
|
type: MaterialType.transparency,
|
||||||
child: BlocProvider(
|
child: BlocProvider(
|
||||||
create: (context) =>
|
create: (context) =>
|
||||||
PaymentMethodSheetBloc(getIt<GetPaymentCardsUsecase>()),
|
PaymentMethodSheetBloc(getIt<GetPaymentCardsUsecase>(),
|
||||||
|
getIt<SetMainPaymentCardUsecase>()),
|
||||||
child: PaymentMethodSheet(),
|
child: PaymentMethodSheet(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -297,12 +321,14 @@ class AppRouter {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: 'scooter/:id',
|
path: 'scooter/:id',
|
||||||
builder: (context, state) =>
|
builder: (context, state) {
|
||||||
BlocProvider(
|
String address = state.extra as String;
|
||||||
|
return BlocProvider(
|
||||||
create: (context) => getIt<ScooterDetailBloc>(),
|
create: (context) => getIt<ScooterDetailBloc>(),
|
||||||
child: ScooterDetailScreen(),
|
child: ScooterDetailScreen(address: address),
|
||||||
),
|
);
|
||||||
|
}
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: 'order-photos/:orderId',
|
path: 'order-photos/:orderId',
|
||||||
@@ -371,6 +397,7 @@ class AppRouter {
|
|||||||
create: (context) =>
|
create: (context) =>
|
||||||
SubscriptionDetailsBloc(
|
SubscriptionDetailsBloc(
|
||||||
getIt<GetSubscriptionByIdUsecase>(),
|
getIt<GetSubscriptionByIdUsecase>(),
|
||||||
|
getIt<GetPaymentCardsUsecase>(),
|
||||||
getIt<ActivateSubscriptionUsecase>(),
|
getIt<ActivateSubscriptionUsecase>(),
|
||||||
getIt<GetClientSubscriptionsUsecase>(),
|
getIt<GetClientSubscriptionsUsecase>(),
|
||||||
)
|
)
|
||||||
@@ -394,24 +421,26 @@ class AppRouter {
|
|||||||
builder: (context, state) => const NewsScreen(),
|
builder: (context, state) => const NewsScreen(),
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: 'qr-info',
|
path: 'qr-info',
|
||||||
builder: (context, state) => const QRScanInfoScreen(),
|
builder: (context, state) => const QRScanInfoScreen(),
|
||||||
routes: [
|
routes: [
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: 'qr-scan',
|
path: 'qr-scan',
|
||||||
builder: (context, state) => const QrScanScreen(),
|
builder: (context, state) => const QrScanScreen(),
|
||||||
),
|
|
||||||
GoRoute(
|
|
||||||
path: 'qr-input',
|
|
||||||
builder: (context, state) => BlocProvider(
|
|
||||||
create: (context) =>
|
|
||||||
ScooterCodeBloc(
|
|
||||||
getScooterByTitleUsecase: getIt<GetScooterByTitleUsecase>(),
|
|
||||||
),
|
|
||||||
child: ScooterCodeInputScreen(),
|
|
||||||
),
|
),
|
||||||
),
|
GoRoute(
|
||||||
]
|
path: 'qr-input',
|
||||||
|
builder: (context, state) =>
|
||||||
|
BlocProvider(
|
||||||
|
create: (context) =>
|
||||||
|
ScooterCodeBloc(
|
||||||
|
getScooterByTitleUsecase: getIt<
|
||||||
|
GetScooterByTitleUsecase>(),
|
||||||
|
),
|
||||||
|
child: ScooterCodeInputScreen(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: 'payment-methods',
|
path: 'payment-methods',
|
||||||
@@ -423,6 +452,7 @@ class AppRouter {
|
|||||||
getIt<RemovePaymentCardUsecase>(),
|
getIt<RemovePaymentCardUsecase>(),
|
||||||
getIt<SetMainPaymentCardUsecase>(),
|
getIt<SetMainPaymentCardUsecase>(),
|
||||||
getIt<GetProfileUseCase>(),
|
getIt<GetProfileUseCase>(),
|
||||||
|
getIt<AddPaymentCardUsecase>(),
|
||||||
)
|
)
|
||||||
..add(PaymentMethodsStarted()),
|
..add(PaymentMethodsStarted()),
|
||||||
child: PaymentMethodsScreen(),
|
child: PaymentMethodsScreen(),
|
||||||
@@ -454,9 +484,9 @@ class AppRouter {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: 'order-history',
|
path: 'order-history',
|
||||||
builder: (context, state) => const OrderHistoryScreen(),
|
builder: (context, state) => const OrderHistoryScreen(),
|
||||||
routes: []
|
routes: []
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: 'notifications',
|
path: 'notifications',
|
||||||
@@ -466,6 +496,24 @@ class AppRouter {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
||||||
|
onException: (context, state, router) {
|
||||||
|
final location = state.uri.toString();
|
||||||
|
|
||||||
|
if (location.startsWith('com.behappy.app://')) {
|
||||||
|
|
||||||
|
String normalizedPath = location
|
||||||
|
.replaceFirst('com.behappy.app://', '/')
|
||||||
|
.replaceAll('/?', '?')
|
||||||
|
.replaceAll(' ', '');
|
||||||
|
|
||||||
|
print("Нормализованный путь диплинка: $normalizedPath");
|
||||||
|
|
||||||
|
router.go(normalizedPath);
|
||||||
|
} else {
|
||||||
|
print('GoRouter Exception: ${state.error}');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
observers: [BotToastNavigatorObserver()],
|
observers: [BotToastNavigatorObserver()],
|
||||||
|
|
||||||
redirect: (BuildContext context, GoRouterState state) {
|
redirect: (BuildContext context, GoRouterState state) {
|
||||||
|
|||||||
165
lib/presentation/screens/card_binding_result_screen.dart
Normal file
165
lib/presentation/screens/card_binding_result_screen.dart
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
|
import '../components/gradient_button.dart';
|
||||||
|
|
||||||
|
enum CardBindingStatus { success, failed }
|
||||||
|
|
||||||
|
class CardBindingResultScreen extends StatefulWidget {
|
||||||
|
final CardBindingStatus status;
|
||||||
|
final String title;
|
||||||
|
final String cardImagePath;
|
||||||
|
|
||||||
|
const CardBindingResultScreen({
|
||||||
|
super.key,
|
||||||
|
required this.status,
|
||||||
|
required this.title,
|
||||||
|
required this.cardImagePath,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<CardBindingResultScreen> createState() => _CardBindingResultScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CardBindingResultScreenState extends State<CardBindingResultScreen> {
|
||||||
|
Timer? _autoPopTimer;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
if (widget.status == CardBindingStatus.success) {
|
||||||
|
_autoPopTimer = Timer(const Duration(seconds: 5), () {
|
||||||
|
if (mounted) {
|
||||||
|
context.go('/home/payment-methods');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_autoPopTimer?.cancel();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final isSuccess = widget.status == CardBindingStatus.success;
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
body: Container(
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
begin: Alignment.topCenter,
|
||||||
|
end: Alignment.bottomCenter,
|
||||||
|
colors: [
|
||||||
|
Color(0xFF1B284F),
|
||||||
|
Color(0xFF0F172A),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: SafeArea(
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
// Positioned(
|
||||||
|
// bottom: 50,
|
||||||
|
// left: 0,
|
||||||
|
// right: 0,
|
||||||
|
// child: Image.asset(
|
||||||
|
// 'assets/wave.png',
|
||||||
|
// fit: BoxFit.cover,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 16.0),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
// const Spacer(),
|
||||||
|
|
||||||
|
Text(
|
||||||
|
widget.title,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 40),
|
||||||
|
|
||||||
|
Image.asset(
|
||||||
|
isSuccess
|
||||||
|
? 'assets/icons/success.png'
|
||||||
|
: 'assets/icons/failed.png',
|
||||||
|
width: 140,
|
||||||
|
height: 140,
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 40),
|
||||||
|
|
||||||
|
if (isSuccess) ...[
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 40.0),
|
||||||
|
// child: Image.asset(
|
||||||
|
// widget.cardImagePath,
|
||||||
|
// width: double.infinity,
|
||||||
|
// fit: BoxFit.contain,
|
||||||
|
// ),
|
||||||
|
),
|
||||||
|
] else ...[
|
||||||
|
const Text(
|
||||||
|
'Проверьте подключение к\nинтернету и попробуйте снова',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Color(0xFF94A3B8),
|
||||||
|
fontSize: 16,
|
||||||
|
height: 1.4,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 40),
|
||||||
|
|
||||||
|
GradientButton(
|
||||||
|
text: "Попробовать снова",
|
||||||
|
onTap: () {},
|
||||||
|
enabled: true,
|
||||||
|
showArrows: true,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
|
||||||
|
SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
height: 56,
|
||||||
|
child: TextButton(
|
||||||
|
onPressed: () => context.go("/home/payment-methods"),
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(28),
|
||||||
|
),
|
||||||
|
backgroundColor: const Color(0xFF0F172A).withOpacity(0.4),
|
||||||
|
),
|
||||||
|
child: const Text(
|
||||||
|
'Закрыть',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import '../../core/app_colors.dart';
|
|||||||
import '../../di/service_locator.dart';
|
import '../../di/service_locator.dart';
|
||||||
import '../../domain/entities/payment_card.dart';
|
import '../../domain/entities/payment_card.dart';
|
||||||
import '../../domain/usecase/get_payment_cards_usecase.dart';
|
import '../../domain/usecase/get_payment_cards_usecase.dart';
|
||||||
|
import '../../domain/usecase/set_main_payment_card_usecase.dart';
|
||||||
import '../components/custom_app_bar.dart';
|
import '../components/custom_app_bar.dart';
|
||||||
import '../components/gradient_button.dart';
|
import '../components/gradient_button.dart';
|
||||||
import '../components/payment_option.dart';
|
import '../components/payment_option.dart';
|
||||||
@@ -212,10 +213,10 @@ class _PaymentConfirmScreenContent extends StatelessWidget {
|
|||||||
(state.useBalance || state.selectedCard != null)
|
(state.useBalance || state.selectedCard != null)
|
||||||
? Padding(padding: const EdgeInsets.symmetric(horizontal: 20),
|
? Padding(padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
child: PaymentOption(
|
child: PaymentOption(
|
||||||
title: state.useBalance ? 'Баланс' : state.selectedCard!.type,
|
title: state.useBalance ? 'Баланс' : "",
|
||||||
subtitle: state.useBalance
|
subtitle: state.useBalance
|
||||||
? '${state.userBalance.toStringAsFixed(2)} BYN'
|
? '${state.userBalance.toStringAsFixed(2)} BYN'
|
||||||
: '****${state.selectedCard!.cardLastNumber}',
|
: '${state.selectedCard!.cardLastNumber}',
|
||||||
isSelected: true,
|
isSelected: true,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
final result = await showModalBottomSheet<dynamic>(
|
final result = await showModalBottomSheet<dynamic>(
|
||||||
@@ -225,6 +226,7 @@ class _PaymentConfirmScreenContent extends StatelessWidget {
|
|||||||
builder: (innerContext) => BlocProvider(
|
builder: (innerContext) => BlocProvider(
|
||||||
create: (context) => PaymentMethodSheetBloc(
|
create: (context) => PaymentMethodSheetBloc(
|
||||||
getIt<GetPaymentCardsUsecase>(),
|
getIt<GetPaymentCardsUsecase>(),
|
||||||
|
getIt<SetMainPaymentCardUsecase>(),
|
||||||
)..add(PaymentMethodSheetStarted()),
|
)..add(PaymentMethodSheetStarted()),
|
||||||
child: PaymentMethodSheet(
|
child: PaymentMethodSheet(
|
||||||
initialSelectedCard: state.useBalance ? null : state.selectedCard,
|
initialSelectedCard: state.useBalance ? null : state.selectedCard,
|
||||||
|
|||||||
@@ -184,11 +184,14 @@ class PaymentMethodsScreen extends StatelessWidget {
|
|||||||
color: Colors.transparent,
|
color: Colors.transparent,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
final isCardAdded = await context.push<bool>('/home/payment-methods/add-card');
|
context.read<PaymentMethodsBloc>().add(AddPayemntCardPressed());
|
||||||
|
/*final isCardAdded = await context.push<bool>('/home/payment-methods/add-card');
|
||||||
|
|
||||||
if (isCardAdded == true && context.mounted) {
|
if (isCardAdded == true && context.mounted) {
|
||||||
context.read<PaymentMethodsBloc>().add(PaymentMethodsStarted());
|
context.read<PaymentMethodsBloc>().add(PaymentMethodsStarted());
|
||||||
}
|
}*/
|
||||||
|
|
||||||
|
|
||||||
},
|
},
|
||||||
borderRadius: BorderRadius.circular(24),
|
borderRadius: BorderRadius.circular(24),
|
||||||
child: const Padding(
|
child: const Padding(
|
||||||
@@ -264,12 +267,7 @@ class _CardItem extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
Image.asset(
|
Icon(Icons.credit_card, color: Color(0xFFFFFFFF), size: 40),
|
||||||
_getDefaultIconPath(card.type),
|
|
||||||
width: 40,
|
|
||||||
height: 40,
|
|
||||||
fit: BoxFit.contain,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
@@ -279,7 +277,7 @@ class _CardItem extends StatelessWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'${card.type} ****${card.cardLastNumber}',
|
'Карта ${card.cardLastNumber}',
|
||||||
style: const TextStyle(color: Colors.white, fontSize: 15, fontWeight: FontWeight.w600),
|
style: const TextStyle(color: Colors.white, fontSize: 15, fontWeight: FontWeight.w600),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
|
|||||||
@@ -79,17 +79,18 @@ class _PhoneLoginScreenState extends State<PhoneLoginScreen> {
|
|||||||
if (state.isSuccess) {
|
if (state.isSuccess) {
|
||||||
context.go("/pin");
|
context.go("/pin");
|
||||||
} else if (state.error != null) {
|
} else if (state.error != null) {
|
||||||
setState(() {
|
setState(() { _isFirstTry = false; });
|
||||||
_isFirstTry = false;
|
|
||||||
});
|
|
||||||
codeController.clear();
|
codeController.clear();
|
||||||
FocusScope.of(context).requestFocus(codeFocusNode);
|
FocusScope.of(context).requestFocus(codeFocusNode);
|
||||||
ScaffoldMessenger.of(
|
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(state.error!)));
|
||||||
context,
|
|
||||||
).showSnackBar(SnackBar(content: Text(state.error!)));
|
|
||||||
} else if (state.isBlocked) {
|
} else if (state.isBlocked) {
|
||||||
context.go("/block");
|
context.go("/block");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (state.secondsLeft == 0 && codeFocusNode.hasFocus) {
|
||||||
|
codeFocusNode.unfocus();
|
||||||
|
codeController.clear();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
@@ -126,12 +127,21 @@ class _PhoneLoginScreenState extends State<PhoneLoginScreen> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 35),
|
const SizedBox(height: 35),
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: openKeyboard,
|
onTap: state.secondsLeft > 0 ? openKeyboard : null,
|
||||||
child: CodeDots(code: state.code, length: 6),
|
child: CodeDots(code: state.code, length: 6),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
|
|
||||||
if (!_isFirstTry)
|
if (state.secondsLeft == 0)
|
||||||
|
const Text(
|
||||||
|
"Время действия кода истекло. Запросите новый.",
|
||||||
|
style: TextStyle(
|
||||||
|
color: AppColors.pinError,
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else if (!_isFirstTry)
|
||||||
Text(
|
Text(
|
||||||
"Осталось ${state.attemptsLeft} попытк${state.attemptsLeft == 1 ? 'а' : 'и'}",
|
"Осталось ${state.attemptsLeft} попытк${state.attemptsLeft == 1 ? 'а' : 'и'}",
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
@@ -145,14 +155,14 @@ class _PhoneLoginScreenState extends State<PhoneLoginScreen> {
|
|||||||
text: state.secondsLeft == 0
|
text: state.secondsLeft == 0
|
||||||
? "Отправить код повторно"
|
? "Отправить код повторно"
|
||||||
: "Отправить код повторно\nчерез 00:${state.secondsLeft.toString().padLeft(2, '0')} сек.",
|
: "Отправить код повторно\nчерез 00:${state.secondsLeft.toString().padLeft(2, '0')} сек.",
|
||||||
onTap: state.secondsLeft == 0
|
onTap: (state.secondsLeft == 0 && !state.isSubmitting)
|
||||||
? () {
|
? () {
|
||||||
context.read<VerifyCodeBloc>().add(
|
context.read<VerifyCodeBloc>().add(
|
||||||
ResendCodePressed(),
|
ResendCodePressed(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
: () {},
|
: null,
|
||||||
enabled: state.secondsLeft == 0,
|
enabled: state.secondsLeft == 0 && !state.isSubmitting,
|
||||||
width: 250,
|
width: 250,
|
||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
@@ -167,6 +177,7 @@ class _PhoneLoginScreenState extends State<PhoneLoginScreen> {
|
|||||||
keyboardType: TextInputType.number,
|
keyboardType: TextInputType.number,
|
||||||
maxLength: 6,
|
maxLength: 6,
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
|
enabled: state.secondsLeft > 0,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ import '../state/scooter_detail_state.dart';
|
|||||||
import '../viewmodel/scooter_detail_bloc.dart';
|
import '../viewmodel/scooter_detail_bloc.dart';
|
||||||
|
|
||||||
class ScooterDetailScreen extends StatelessWidget {
|
class ScooterDetailScreen extends StatelessWidget {
|
||||||
const ScooterDetailScreen({super.key});
|
final String address;
|
||||||
|
const ScooterDetailScreen({super.key, required this.address});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -83,7 +84,7 @@ class ScooterDetailScreen extends StatelessWidget {
|
|||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
const Expanded(child: ScooterInfoSection()),
|
Expanded(child: ScooterInfoSection(distance: scooter?.distance ?? 0, location: address)),
|
||||||
const SizedBox(width: 20),
|
const SizedBox(width: 20),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -3,13 +3,21 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
import '../../core/app_colors.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/set_main_payment_card_usecase.dart';
|
||||||
import '../components/app_checkbox.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/gradient_button.dart';
|
||||||
|
import '../components/payment_option.dart';
|
||||||
import '../components/period_selector.dart';
|
import '../components/period_selector.dart';
|
||||||
import '../components/content_parser.dart'; // ✅ Парсер контента
|
import '../components/content_parser.dart'; // ✅ Парсер контента
|
||||||
|
import '../components/sheet/payment_method_sheet.dart';
|
||||||
|
import '../event/payment_method_sheet_event.dart';
|
||||||
import '../event/subscription_details_event.dart';
|
import '../event/subscription_details_event.dart';
|
||||||
import '../state/susbcription_details_state.dart';
|
import '../state/susbcription_details_state.dart';
|
||||||
|
import '../viewmodel/payment_method_sheet_bloc.dart';
|
||||||
import '../viewmodel/susbcription_details_bloc.dart';
|
import '../viewmodel/susbcription_details_bloc.dart';
|
||||||
|
|
||||||
class SubscriptionDetailsScreen extends StatelessWidget {
|
class SubscriptionDetailsScreen extends StatelessWidget {
|
||||||
@@ -50,11 +58,9 @@ class SubscriptionDetailsScreen extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
// 🔹 Контент
|
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
// Волна снизу
|
|
||||||
Positioned(
|
Positioned(
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
@@ -108,10 +114,9 @@ class SubscriptionDetailsScreen extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
// 🔹 Обёртка для растягивания контента на всю ширину
|
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch, // ✅ Растягивает детей на всю ширину
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
mainAxisSize: MainAxisSize.min, // ✅ Не занимает лишнее место по вертикали
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: ContentParser.parseContent(
|
children: ContentParser.parseContent(
|
||||||
textJson: state.subscription.fullDescriptionJson,
|
textJson: state.subscription.fullDescriptionJson,
|
||||||
text: state.subscription.fullDescription,
|
text: state.subscription.fullDescription,
|
||||||
@@ -121,6 +126,14 @@ class SubscriptionDetailsScreen extends StatelessWidget {
|
|||||||
if (isAvailableForPurchase) ...[
|
if (isAvailableForPurchase) ...[
|
||||||
const SizedBox(height: 30),
|
const SizedBox(height: 30),
|
||||||
_ActionCard(state: state),
|
_ActionCard(state: state),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
const Text(
|
||||||
|
'Способ оплаты',
|
||||||
|
style: TextStyle(color: Colors.white70, fontSize: 14),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
_buildCardSelector(state, context),
|
||||||
|
|
||||||
const SizedBox(height: 30),
|
const SizedBox(height: 30),
|
||||||
GradientButton(
|
GradientButton(
|
||||||
text: state.isAlreadyPurchased ? 'Продлить' : 'Активировать',
|
text: state.isAlreadyPurchased ? 'Продлить' : 'Активировать',
|
||||||
@@ -129,7 +142,7 @@ class SubscriptionDetailsScreen extends StatelessWidget {
|
|||||||
ActivateSubscriptionPressed(),
|
ActivateSubscriptionPressed(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
enabled: state.isAgreed,
|
enabled: state.isAgreed && state.selectedCard != null,
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: 56,
|
height: 56,
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
@@ -141,8 +154,77 @@ class SubscriptionDetailsScreen extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildCardSelector(DetailsContentState state, BuildContext context) {
|
||||||
|
if (state.selectedCard != null) {
|
||||||
|
return PaymentOption(
|
||||||
|
title: "Карта",
|
||||||
|
subtitle: state.selectedCard.cardLastNumber,
|
||||||
|
isSelected: true,
|
||||||
|
onTap: () async {
|
||||||
|
final selectedCard = await showModalBottomSheet<PaymentCard>(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
builder: (innerContext) => BlocProvider(
|
||||||
|
create: (context) => PaymentMethodSheetBloc(
|
||||||
|
getIt<GetPaymentCardsUsecase>(),
|
||||||
|
getIt<SetMainPaymentCardUsecase>(),
|
||||||
|
)..add(PaymentMethodSheetStarted()),
|
||||||
|
child: const PaymentMethodSheet(showBalance: false),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (selectedCard != null && context.mounted) {
|
||||||
|
context.read<SubscriptionDetailsBloc>().add(SelectCardEvent(selectedCard));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
height: 56,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(24),
|
||||||
|
border: Border.all(
|
||||||
|
color: Colors.white.withOpacity(0.4),
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
context.pushReplacement('/home/payment-method-sheet');
|
||||||
|
},
|
||||||
|
borderRadius: BorderRadius.circular(24),
|
||||||
|
child: Center(
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
'Добавить способ оплаты',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
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)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class _ActionCard extends StatelessWidget {
|
class _ActionCard extends StatelessWidget {
|
||||||
final DetailsContentState state;
|
final DetailsContentState state;
|
||||||
|
|
||||||
@@ -216,6 +298,7 @@ class _ActionCard extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class _PriceRow extends StatelessWidget {
|
class _PriceRow extends StatelessWidget {
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:path/path.dart';
|
||||||
|
|
||||||
import '../../core/app_colors.dart';
|
import '../../core/app_colors.dart';
|
||||||
import '../../di/service_locator.dart';
|
import '../../di/service_locator.dart';
|
||||||
import '../../domain/entities/payment_card.dart';
|
import '../../domain/entities/payment_card.dart';
|
||||||
import '../../domain/usecase/get_payment_cards_usecase.dart';
|
import '../../domain/usecase/get_payment_cards_usecase.dart';
|
||||||
|
import '../../domain/usecase/set_main_payment_card_usecase.dart';
|
||||||
import '../components/app_checkbox.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/gradient_button.dart';
|
||||||
@@ -22,54 +24,56 @@ class TopUpScreen extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: Container(
|
body: Container(
|
||||||
decoration: const BoxDecoration(gradient: AppColors.phoneScreenBg),
|
decoration: const BoxDecoration(gradient: AppColors.phoneScreenBg),
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
const CustomAppBar(title: 'Пополнение баланса'),
|
const CustomAppBar(title: 'Пополнение баланса'),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
BlocBuilder<TopUpBloc, TopUpState>(
|
BlocBuilder<TopUpBloc, TopUpState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
if (state.isLoading) {
|
if (state.isLoading) {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
}
|
}
|
||||||
|
|
||||||
return Expanded(
|
return Expanded(
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
_buildTariffList(state, context),
|
_buildTariffList(state, context),
|
||||||
const SizedBox(height: 30),
|
const SizedBox(height: 30),
|
||||||
_buildPriceInfo(state),
|
_buildPriceInfo(state),
|
||||||
const SizedBox(height: 40),
|
const SizedBox(height: 40),
|
||||||
const Text(
|
const Text(
|
||||||
'Способ оплаты',
|
'Способ оплаты',
|
||||||
style: TextStyle(color: Colors.white70),
|
style: TextStyle(color: Colors.white70),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 15),
|
const SizedBox(height: 15),
|
||||||
_buildCardSelector(state, context),
|
_buildCardSelector(state, context),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
_buildAgreement(state, context),
|
_buildAgreement(state, context),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
_buildPayButton(state),
|
_buildPayButton(state, () {
|
||||||
const SizedBox(height: 30),
|
context.read<TopUpBloc>().add(TopUpButtonPressed());
|
||||||
],
|
}),
|
||||||
),
|
const SizedBox(height: 30),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
],
|
||||||
},
|
),
|
||||||
),
|
);
|
||||||
],
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
)
|
||||||
)
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -158,20 +162,24 @@ class TopUpScreen extends StatelessWidget {
|
|||||||
? Padding(
|
? Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
child: PaymentOption(
|
child: PaymentOption(
|
||||||
title: state.selectedCard!.type,
|
title: "Карта",
|
||||||
subtitle: '****${state.selectedCard!.cardLastNumber}',
|
subtitle: '${state.selectedCard!.cardLastNumber}',
|
||||||
isSelected: true,
|
isSelected: true,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
final selectedCard = await showModalBottomSheet<PaymentCard>(
|
final selectedCard = await showModalBottomSheet<PaymentCard>(
|
||||||
context: context,
|
context: context,
|
||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
builder: (innerContext) => BlocProvider(
|
builder: (innerContext) =>
|
||||||
create: (context) =>
|
BlocProvider(
|
||||||
PaymentMethodSheetBloc(getIt<GetPaymentCardsUsecase>())
|
create: (context) =>
|
||||||
..add(PaymentMethodSheetStarted()),
|
PaymentMethodSheetBloc(
|
||||||
child: const PaymentMethodSheet(showBalance: false,),
|
getIt<GetPaymentCardsUsecase>(),
|
||||||
),
|
getIt<SetMainPaymentCardUsecase>(),
|
||||||
|
)
|
||||||
|
..add(PaymentMethodSheetStarted()),
|
||||||
|
child: const PaymentMethodSheet(showBalance: false,),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (selectedCard != null) {
|
if (selectedCard != null) {
|
||||||
@@ -260,10 +268,10 @@ class TopUpScreen extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildPayButton(TopUpState state) {
|
Widget _buildPayButton(TopUpState state, VoidCallback onTap) {
|
||||||
return GradientButton(
|
return GradientButton(
|
||||||
text: 'Оплатить',
|
text: 'Оплатить',
|
||||||
onTap: state.isAgreed ? () {} : null,
|
onTap: state.isAgreed ? onTap : null,
|
||||||
enabled: state.isAgreed,
|
enabled: state.isAgreed,
|
||||||
showArrows: true,
|
showArrows: true,
|
||||||
height: 56,
|
height: 56,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:be_happy/domain/entities/payment_card.dart';
|
||||||
import 'package:be_happy/domain/entities/subscription.dart';
|
import 'package:be_happy/domain/entities/subscription.dart';
|
||||||
|
|
||||||
import '../../domain/entities/subscription_period.dart';
|
import '../../domain/entities/subscription_period.dart';
|
||||||
@@ -8,19 +9,24 @@ class DetailsLoading extends SubscriptionDetailsState {}
|
|||||||
|
|
||||||
class DetailsError extends SubscriptionDetailsState {
|
class DetailsError extends SubscriptionDetailsState {
|
||||||
final String message;
|
final String message;
|
||||||
|
|
||||||
DetailsError(this.message);
|
DetailsError(this.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
class DetailsContentState extends SubscriptionDetailsState {
|
class DetailsContentState extends SubscriptionDetailsState {
|
||||||
final Subscription subscription;
|
final Subscription subscription;
|
||||||
final SubscriptionPeriod selectedPeriod;
|
final SubscriptionPeriod selectedPeriod;
|
||||||
|
final List<PaymentCard> cards;
|
||||||
|
final PaymentCard selectedCard;
|
||||||
final bool isAgreed;
|
final bool isAgreed;
|
||||||
final bool isAlreadyPurchased; // ✅ Куплена ли эта подписка сейчас
|
final bool isAlreadyPurchased;
|
||||||
final bool isSuccess; // ✅ Сигнал для навигатора назад
|
final bool isSuccess;
|
||||||
|
|
||||||
DetailsContentState({
|
DetailsContentState({
|
||||||
required this.subscription,
|
required this.subscription,
|
||||||
required this.selectedPeriod,
|
required this.selectedPeriod,
|
||||||
|
required this.selectedCard,
|
||||||
|
this.cards = const [],
|
||||||
this.isAgreed = false,
|
this.isAgreed = false,
|
||||||
this.isAlreadyPurchased = false,
|
this.isAlreadyPurchased = false,
|
||||||
this.isSuccess = false,
|
this.isSuccess = false,
|
||||||
@@ -28,6 +34,7 @@ class DetailsContentState extends SubscriptionDetailsState {
|
|||||||
|
|
||||||
DetailsContentState copyWith({
|
DetailsContentState copyWith({
|
||||||
SubscriptionPeriod? selectedPeriod,
|
SubscriptionPeriod? selectedPeriod,
|
||||||
|
PaymentCard? selectedCard,
|
||||||
bool? isAgreed,
|
bool? isAgreed,
|
||||||
bool? isAlreadyPurchased,
|
bool? isAlreadyPurchased,
|
||||||
bool? isSuccess,
|
bool? isSuccess,
|
||||||
@@ -35,6 +42,7 @@ class DetailsContentState extends SubscriptionDetailsState {
|
|||||||
return DetailsContentState(
|
return DetailsContentState(
|
||||||
subscription: this.subscription,
|
subscription: this.subscription,
|
||||||
selectedPeriod: selectedPeriod ?? this.selectedPeriod,
|
selectedPeriod: selectedPeriod ?? this.selectedPeriod,
|
||||||
|
selectedCard: selectedCard ?? this.selectedCard,
|
||||||
isAgreed: isAgreed ?? this.isAgreed,
|
isAgreed: isAgreed ?? this.isAgreed,
|
||||||
isAlreadyPurchased: isAlreadyPurchased ?? this.isAlreadyPurchased,
|
isAlreadyPurchased: isAlreadyPurchased ?? this.isAlreadyPurchased,
|
||||||
isSuccess: isSuccess ?? this.isSuccess,
|
isSuccess: isSuccess ?? this.isSuccess,
|
||||||
|
|||||||
@@ -52,17 +52,17 @@ class AddCardBloc extends Bloc<AddCardEvent, AddCardState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final result = await addPaymentCardUsecase(
|
final result = await addPaymentCardUsecase(
|
||||||
cardNumber: state.cardNumber.replaceAll(' ', ''),
|
/*cardNumber: state.cardNumber.replaceAll(' ', ''),
|
||||||
expiryMonth: expiryParts[0],
|
expiryMonth: expiryParts[0],
|
||||||
expiryYear: expiryParts[1],
|
expiryYear: expiryParts[1],
|
||||||
cardHolder: state.cardHolder.trim(),
|
cardHolder: state.cardHolder.trim(),
|
||||||
cvv: state.cvv,
|
cvv: state.cvv,*/
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result is Success) {
|
if (result is Success) {
|
||||||
emit(state.copyWith(status: AddCardStatus.success));
|
emit(state.copyWith(status: AddCardStatus.success));
|
||||||
} else if (result is Failure) {
|
} else if (result is Failure) {
|
||||||
final failure = result.failure;
|
/*final failure = result.failure;
|
||||||
|
|
||||||
String errorMessage = 'Ошибка добавления карты';
|
String errorMessage = 'Ошибка добавления карты';
|
||||||
|
|
||||||
@@ -72,12 +72,12 @@ class AddCardBloc extends Bloc<AddCardEvent, AddCardState> {
|
|||||||
errorMessage = 'Доступ заблокирован';
|
errorMessage = 'Доступ заблокирован';
|
||||||
} else if (failure is UnknownFailure) {
|
} else if (failure is UnknownFailure) {
|
||||||
errorMessage = failure.message ?? 'Неизвестная ошибка';
|
errorMessage = failure.message ?? 'Неизвестная ошибка';
|
||||||
}
|
}*/
|
||||||
|
|
||||||
emit(state.copyWith(
|
/* emit(state.copyWith(
|
||||||
status: AddCardStatus.failure,
|
status: AddCardStatus.failure,
|
||||||
errorMessage: errorMessage,
|
errorMessage: errorMessage,
|
||||||
));
|
));*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -264,8 +264,9 @@ class MapBloc extends Bloc<ScooterEvent, ScooterState> {
|
|||||||
FutureOr<void> _onLogoutPressed(
|
FutureOr<void> _onLogoutPressed(
|
||||||
LogoutPressed event,
|
LogoutPressed event,
|
||||||
Emitter<ScooterState> emit,
|
Emitter<ScooterState> emit,
|
||||||
) {
|
) async {
|
||||||
logoutUseCase();
|
await logoutUseCase();
|
||||||
|
stopNotificationStream();
|
||||||
splashBloc.add(AuthCheckRequested());
|
splashBloc.add(AuthCheckRequested());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,13 +3,15 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
import '../../core/result.dart';
|
import '../../core/result.dart';
|
||||||
import '../../domain/entities/payment_card.dart';
|
import '../../domain/entities/payment_card.dart';
|
||||||
import '../../domain/usecase/get_payment_cards_usecase.dart';
|
import '../../domain/usecase/get_payment_cards_usecase.dart';
|
||||||
|
import '../../domain/usecase/set_main_payment_card_usecase.dart';
|
||||||
import '../event/payment_method_sheet_event.dart';
|
import '../event/payment_method_sheet_event.dart';
|
||||||
import '../state/payment_method_sheet_state.dart';
|
import '../state/payment_method_sheet_state.dart';
|
||||||
|
|
||||||
class PaymentMethodSheetBloc extends Bloc<PaymentMethodSheetEvent, PaymentMethodSheetState> {
|
class PaymentMethodSheetBloc extends Bloc<PaymentMethodSheetEvent, PaymentMethodSheetState> {
|
||||||
final GetPaymentCardsUsecase _getPaymentCardsUsecase;
|
final GetPaymentCardsUsecase _getPaymentCardsUsecase;
|
||||||
|
final SetMainPaymentCardUsecase _setMainPaymentCardUsecase;
|
||||||
|
|
||||||
PaymentMethodSheetBloc(this._getPaymentCardsUsecase)
|
PaymentMethodSheetBloc(this._getPaymentCardsUsecase, this._setMainPaymentCardUsecase)
|
||||||
: super(PaymentMethodSheetState(status: PaymentMethodSheetStatus.initial)) {
|
: super(PaymentMethodSheetState(status: PaymentMethodSheetStatus.initial)) {
|
||||||
on<PaymentMethodSheetStarted>(_onStarted);
|
on<PaymentMethodSheetStarted>(_onStarted);
|
||||||
}
|
}
|
||||||
@@ -41,4 +43,53 @@ class PaymentMethodSheetBloc extends Bloc<PaymentMethodSheetEvent, PaymentMethod
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Future<void> _onSelectCard(
|
||||||
|
SelectPaymentCardRequested event,
|
||||||
|
Emitter<PaymentMethodSheetState> emit,
|
||||||
|
) async {
|
||||||
|
// Включаем локальный лоадер на шторке, либо просто ждем ответа
|
||||||
|
emit(state.copyWith(status: PaymentMethodSheetStatus.loading));
|
||||||
|
|
||||||
|
final result = await _setMainPaymentCardUsecase(event.card.id);
|
||||||
|
|
||||||
|
if (result is Success<void>) {
|
||||||
|
final cardsResult = await _getPaymentCardsUsecase();
|
||||||
|
if (cardsResult is Success<List<PaymentCard>>) {
|
||||||
|
emit(state.copyWith(
|
||||||
|
status: PaymentMethodSheetStatus.success,
|
||||||
|
cards: cardsResult.data ?? [],
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
emit(state.copyWith(status: PaymentMethodSheetStatus.failure, errorMessage: 'Ошибка обновления списка карт'));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
emit(state.copyWith(
|
||||||
|
status: PaymentMethodSheetStatus.failure,
|
||||||
|
errorMessage: 'Не удалось установить карту основной',
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Выносим загрузку в отдельный метод для переиспользования
|
||||||
|
Future<void> _loadCards(Emitter<PaymentMethodSheetState> emit) async {
|
||||||
|
try {
|
||||||
|
final result = await _getPaymentCardsUsecase();
|
||||||
|
if (result is Success<List<PaymentCard>>) {
|
||||||
|
emit(state.copyWith(
|
||||||
|
status: PaymentMethodSheetStatus.success,
|
||||||
|
cards: result.data ?? [],
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
emit(state.copyWith(
|
||||||
|
status: PaymentMethodSheetStatus.failure,
|
||||||
|
errorMessage: 'Failed to load payment cards',
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
emit(state.copyWith(
|
||||||
|
status: PaymentMethodSheetStatus.failure,
|
||||||
|
errorMessage: e.toString(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:be_happy/domain/usecase/add_payment_card_usecase.dart';
|
||||||
import 'package:be_happy/domain/usecase/get_profile_usecase.dart';
|
import 'package:be_happy/domain/usecase/get_profile_usecase.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
import '../../core/result.dart';
|
import '../../core/result.dart';
|
||||||
import '../../core/failures.dart';
|
import '../../core/failures.dart';
|
||||||
@@ -15,16 +19,19 @@ class PaymentMethodsBloc extends Bloc<PaymentMethodsEvent, PaymentMethodsState>
|
|||||||
final RemovePaymentCardUsecase _removePaymentCardUsecase;
|
final RemovePaymentCardUsecase _removePaymentCardUsecase;
|
||||||
final SetMainPaymentCardUsecase _setMainPaymentCardUsecase;
|
final SetMainPaymentCardUsecase _setMainPaymentCardUsecase;
|
||||||
final GetProfileUseCase _getProfileUseCase;
|
final GetProfileUseCase _getProfileUseCase;
|
||||||
|
final AddPaymentCardUsecase _addPaymentCardUsecase;
|
||||||
|
|
||||||
PaymentMethodsBloc(
|
PaymentMethodsBloc(
|
||||||
this._getPaymentCardsUsecase,
|
this._getPaymentCardsUsecase,
|
||||||
this._removePaymentCardUsecase,
|
this._removePaymentCardUsecase,
|
||||||
this._setMainPaymentCardUsecase,
|
this._setMainPaymentCardUsecase,
|
||||||
this._getProfileUseCase,
|
this._getProfileUseCase,
|
||||||
|
this._addPaymentCardUsecase,
|
||||||
) : super(PaymentMethodsState(status: PaymentMethodsStatus.initial)) {
|
) : super(PaymentMethodsState(status: PaymentMethodsStatus.initial)) {
|
||||||
on<PaymentMethodsStarted>(_onStarted);
|
on<PaymentMethodsStarted>(_onStarted);
|
||||||
on<PaymentMethodsDeleteCard>(_onDeleteCard);
|
on<PaymentMethodsDeleteCard>(_onDeleteCard);
|
||||||
on<PaymentMethodsSetMainCard>(_onSetMainCard);
|
on<PaymentMethodsSetMainCard>(_onSetMainCard);
|
||||||
|
on<AddPayemntCardPressed>(_onAddPayemntCardPressed);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onStarted(
|
Future<void> _onStarted(
|
||||||
@@ -136,4 +143,54 @@ class PaymentMethodsBloc extends Bloc<PaymentMethodsEvent, PaymentMethodsState>
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
FutureOr<void> _onAddPayemntCardPressed(
|
||||||
|
AddPayemntCardPressed event,
|
||||||
|
Emitter<PaymentMethodsState> emit
|
||||||
|
) async {
|
||||||
|
emit(state.copyWith(status: PaymentMethodsStatus.loading));
|
||||||
|
|
||||||
|
try {
|
||||||
|
final result = await _addPaymentCardUsecase();
|
||||||
|
|
||||||
|
if (result is Success<Map<String, dynamic>>) {
|
||||||
|
final formUrlString = result.data?['formUrl'] as String?;
|
||||||
|
|
||||||
|
if (formUrlString != null && formUrlString.isNotEmpty) {
|
||||||
|
final url = Uri.parse(formUrlString);
|
||||||
|
|
||||||
|
if (await canLaunchUrl(url)) {
|
||||||
|
launchUrl(
|
||||||
|
url,
|
||||||
|
mode: LaunchMode.externalApplication,
|
||||||
|
);
|
||||||
|
emit(state.copyWith(status: PaymentMethodsStatus.success));
|
||||||
|
} else {
|
||||||
|
emit(state.copyWith(
|
||||||
|
status: PaymentMethodsStatus.failure,
|
||||||
|
errorMessage: 'Не удалось открыть ссылку для оплаты',
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
emit(state.copyWith(
|
||||||
|
status: PaymentMethodsStatus.failure,
|
||||||
|
errorMessage: 'Ссылка для привязки карты не получена от сервера',
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (result is Failure) {
|
||||||
|
String errorMessage = 'Ошибка при добавлении карты';
|
||||||
|
|
||||||
|
emit(state.copyWith(
|
||||||
|
status: PaymentMethodsStatus.failure,
|
||||||
|
errorMessage: errorMessage,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
emit(state.copyWith(
|
||||||
|
status: PaymentMethodsStatus.failure,
|
||||||
|
errorMessage: e.toString(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import 'package:be_happy/core/result.dart';
|
import 'package:be_happy/core/result.dart';
|
||||||
|
import 'package:be_happy/domain/entities/payment_card.dart';
|
||||||
import 'package:be_happy/domain/entities/subscription.dart';
|
import 'package:be_happy/domain/entities/subscription.dart';
|
||||||
import 'package:be_happy/domain/usecase/activate_subscription_usecase.dart';
|
import 'package:be_happy/domain/usecase/activate_subscription_usecase.dart';
|
||||||
|
import 'package:be_happy/domain/usecase/get_payment_cards_usecase.dart';
|
||||||
import 'package:be_happy/domain/usecase/get_subscription_by_id_usecase.dart';
|
import 'package:be_happy/domain/usecase/get_subscription_by_id_usecase.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
@@ -12,27 +14,35 @@ import '../state/susbcription_details_state.dart';
|
|||||||
class SubscriptionDetailsBloc
|
class SubscriptionDetailsBloc
|
||||||
extends Bloc<SubscriptionDetailsEvent, SubscriptionDetailsState> {
|
extends Bloc<SubscriptionDetailsEvent, SubscriptionDetailsState> {
|
||||||
final GetSubscriptionByIdUsecase getSubscriptionByIdUsecase;
|
final GetSubscriptionByIdUsecase getSubscriptionByIdUsecase;
|
||||||
|
final GetPaymentCardsUsecase getPaymentCardsUsecase;
|
||||||
final ActivateSubscriptionUsecase activateSubscriptionUsecase;
|
final ActivateSubscriptionUsecase activateSubscriptionUsecase;
|
||||||
final GetClientSubscriptionsUsecase getClientSubscriptionsUsecase;
|
final GetClientSubscriptionsUsecase getClientSubscriptionsUsecase;
|
||||||
|
|
||||||
SubscriptionDetailsBloc(
|
SubscriptionDetailsBloc(
|
||||||
this.getSubscriptionByIdUsecase,
|
this.getSubscriptionByIdUsecase,
|
||||||
|
this.getPaymentCardsUsecase,
|
||||||
this.activateSubscriptionUsecase,
|
this.activateSubscriptionUsecase,
|
||||||
this.getClientSubscriptionsUsecase,
|
this.getClientSubscriptionsUsecase,
|
||||||
) : super(DetailsLoading()) {
|
) : super(DetailsLoading()) {
|
||||||
on<LoadDetailsEvent>((event, emit) async {
|
on<LoadDetailsEvent>((event, emit) async {
|
||||||
emit(DetailsLoading());
|
emit(DetailsLoading());
|
||||||
try {
|
try {
|
||||||
final result = await getSubscriptionByIdUsecase(event.subscriptionId);
|
final cardsResult = await getPaymentCardsUsecase();
|
||||||
|
|
||||||
|
final subsResult = await getSubscriptionByIdUsecase(
|
||||||
|
event.subscriptionId,
|
||||||
|
);
|
||||||
|
|
||||||
final clientSubsResult = await getClientSubscriptionsUsecase();
|
final clientSubsResult = await getClientSubscriptionsUsecase();
|
||||||
bool isPurchased = false;
|
bool isPurchased = false;
|
||||||
|
|
||||||
switch (clientSubsResult) {
|
switch (clientSubsResult) {
|
||||||
case Success<List<ClientSubscription>>():
|
case Success<List<ClientSubscription>>():
|
||||||
isPurchased = clientSubsResult.data?.any(
|
isPurchased =
|
||||||
|
clientSubsResult.data?.any(
|
||||||
(element) => element.subscriptionId == event.subscriptionId,
|
(element) => element.subscriptionId == event.subscriptionId,
|
||||||
) ?? false;
|
) ??
|
||||||
|
false;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Failure<List<ClientSubscription>>():
|
case Failure<List<ClientSubscription>>():
|
||||||
@@ -40,16 +50,30 @@ class SubscriptionDetailsBloc
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (result) {
|
switch (subsResult) {
|
||||||
case Success<Subscription>():
|
case Success<Subscription>():
|
||||||
final sub = result.data;
|
final sub = subsResult.data;
|
||||||
if (sub == null) return;
|
if (sub == null) return;
|
||||||
|
|
||||||
|
List<PaymentCard> loadedCards = [];
|
||||||
|
PaymentCard? defaultCard;
|
||||||
|
|
||||||
|
if (cardsResult is Success<List<PaymentCard>>) {
|
||||||
|
loadedCards = cardsResult.data ?? [];
|
||||||
|
if (loadedCards.isNotEmpty) {
|
||||||
|
defaultCard = loadedCards.firstWhere(
|
||||||
|
(element) => element.isMain,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
emit(
|
emit(
|
||||||
DetailsContentState(
|
DetailsContentState(
|
||||||
subscription: sub,
|
subscription: sub,
|
||||||
selectedPeriod: sub.options.first,
|
selectedPeriod: sub.options.first,
|
||||||
isAlreadyPurchased: isPurchased,
|
isAlreadyPurchased: isPurchased,
|
||||||
|
cards: loadedCards,
|
||||||
|
selectedCard: defaultCard ?? loadedCards.first,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
@@ -70,6 +94,12 @@ class SubscriptionDetailsBloc
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
on<SelectCardEvent>((event, emit) {
|
||||||
|
if (state is DetailsContentState) {
|
||||||
|
emit((state as DetailsContentState).copyWith(selectedCard: event.card));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
on<ToggleAgreementEvent>((event, emit) {
|
on<ToggleAgreementEvent>((event, emit) {
|
||||||
if (state is DetailsContentState) {
|
if (state is DetailsContentState) {
|
||||||
emit((state as DetailsContentState).copyWith(isAgreed: event.value));
|
emit((state as DetailsContentState).copyWith(isAgreed: event.value));
|
||||||
@@ -79,16 +109,18 @@ class SubscriptionDetailsBloc
|
|||||||
on<ActivateSubscriptionPressed>((event, emit) async {
|
on<ActivateSubscriptionPressed>((event, emit) async {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case DetailsContentState contentState:
|
case DetailsContentState contentState:
|
||||||
if (!contentState.isAgreed) {
|
if (!contentState.isAgreed || contentState.selectedCard == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final result = await activateSubscriptionUsecase(contentState.selectedPeriod.id);
|
final result = await activateSubscriptionUsecase(
|
||||||
|
contentState.selectedPeriod.id,
|
||||||
|
contentState.selectedCard.id,
|
||||||
|
);
|
||||||
|
|
||||||
if (result is Success) {
|
if (result is Success) {
|
||||||
emit(contentState.copyWith(isSuccess: true));
|
emit(contentState.copyWith(isSuccess: true));
|
||||||
} else {
|
} else {}
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ class TopUpBloc extends Bloc<TopUpEvent, TopUpState> {
|
|||||||
on<LoadTopUpData>((event, emit) async {
|
on<LoadTopUpData>((event, emit) async {
|
||||||
emit(state.copyWith(isLoading: true));
|
emit(state.copyWith(isLoading: true));
|
||||||
|
|
||||||
// Запускаем оба запроса параллельно
|
|
||||||
final results = await Future.wait([
|
final results = await Future.wait([
|
||||||
getCertificatesUsecase(),
|
getCertificatesUsecase(),
|
||||||
getUserCards(),
|
getUserCards(),
|
||||||
@@ -77,5 +76,21 @@ class TopUpBloc extends Bloc<TopUpEvent, TopUpState> {
|
|||||||
on<ToggleAgreement>(
|
on<ToggleAgreement>(
|
||||||
(event, emit) => emit(state.copyWith(isAgreed: event.value)),
|
(event, emit) => emit(state.copyWith(isAgreed: event.value)),
|
||||||
);
|
);
|
||||||
|
on<TopUpButtonPressed>((event, emit) async {
|
||||||
|
emit(state.copyWith(isLoading: true));
|
||||||
|
|
||||||
|
if (state.isAgreed) {
|
||||||
|
final result = await purchaseCertificateUsecase(
|
||||||
|
certificateId: state.selectedTariff!.id,
|
||||||
|
cardId:state.selectedCard!.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result is Success) {
|
||||||
|
emit(state.copyWith(isLoading: false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,12 +8,26 @@ import '../../domain/usecase/verify_code_usecase.dart';
|
|||||||
import '../event/verify_code_event.dart';
|
import '../event/verify_code_event.dart';
|
||||||
import '../state/verify_code_state.dart';
|
import '../state/verify_code_state.dart';
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:be_happy/core/failures.dart';
|
||||||
|
import 'package:be_happy/core/result.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
import '../../domain/usecase/verify_code_usecase.dart';
|
||||||
|
import '../../domain/usecase/login_usecase.dart'; // 🔹 Импортируем LoginUseCase
|
||||||
|
import '../event/verify_code_event.dart';
|
||||||
|
import '../state/verify_code_state.dart';
|
||||||
|
|
||||||
class VerifyCodeBloc extends Bloc<VerifyCodeEvent, VerifyCodeState> {
|
class VerifyCodeBloc extends Bloc<VerifyCodeEvent, VerifyCodeState> {
|
||||||
final VerifyCodeUseCase _verifyCodeUseCase;
|
final VerifyCodeUseCase _verifyCodeUseCase;
|
||||||
|
final LoginUseCase _loginUseCase; // 🔹 Добавляем поле для юзкейса логина
|
||||||
Timer? _timer;
|
Timer? _timer;
|
||||||
|
|
||||||
VerifyCodeBloc(this._verifyCodeUseCase)
|
// 🔹 Обновляем конструктор
|
||||||
|
VerifyCodeBloc(this._verifyCodeUseCase, this._loginUseCase)
|
||||||
: super(VerifyCodeState.initial()) {
|
: super(VerifyCodeState.initial()) {
|
||||||
|
|
||||||
on<VerifyCodeStarted>((event, emit) {
|
on<VerifyCodeStarted>((event, emit) {
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
phoneNumber: event.phoneNumber,
|
phoneNumber: event.phoneNumber,
|
||||||
@@ -24,56 +38,89 @@ class VerifyCodeBloc extends Bloc<VerifyCodeEvent, VerifyCodeState> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
on<CodeChanged>((event, emit) {
|
on<CodeChanged>((event, emit) {
|
||||||
emit(state.copyWith(code: event.code));
|
emit(state.copyWith(
|
||||||
|
code: event.code,
|
||||||
|
error: null,
|
||||||
|
));
|
||||||
});
|
});
|
||||||
|
|
||||||
on<ResendCodePressed>((event, emit) {
|
on<ResendCodePressed>((event, emit) async {
|
||||||
if (state.secondsLeft == 0) {
|
if (state.secondsLeft == 0 && !state.isSubmitting) {
|
||||||
emit(state.copyWith(secondsLeft: 60));
|
emit(state.copyWith(isSubmitting: true, error: null));
|
||||||
_startTimer();
|
|
||||||
|
try {
|
||||||
|
final newTempToken = await _loginUseCase.execute("+${state.phoneNumber}");
|
||||||
|
|
||||||
|
emit(state.copyWith(
|
||||||
|
isSubmitting: false,
|
||||||
|
tempToken: newTempToken,
|
||||||
|
secondsLeft: 60,
|
||||||
|
code: '',
|
||||||
|
error: null,
|
||||||
|
isBlocked: false,
|
||||||
|
));
|
||||||
|
|
||||||
|
_startTimer();
|
||||||
|
} catch (e) {
|
||||||
|
emit(state.copyWith(
|
||||||
|
isSubmitting: false,
|
||||||
|
error: 'Не удалось повторно отправить код. Попробуйте позже.',
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
on<TimerTicked>((event, emit) {
|
||||||
|
emit(state.copyWith(secondsLeft: event.secondsLeft));
|
||||||
|
});
|
||||||
|
|
||||||
on<VerifyCodeSubmitted>(_onVerifyCodeSubmitted);
|
on<VerifyCodeSubmitted>(_onVerifyCodeSubmitted);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _startTimer() {
|
void _startTimer() {
|
||||||
_timer?.cancel();
|
_timer?.cancel();
|
||||||
|
|
||||||
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
||||||
if (state.secondsLeft <= 1) {
|
final currentSeconds = state.secondsLeft;
|
||||||
|
if (currentSeconds <= 1) {
|
||||||
timer.cancel();
|
timer.cancel();
|
||||||
emit(state.copyWith(secondsLeft: 0));
|
add(TimerTicked(0));
|
||||||
} else {
|
} else {
|
||||||
emit(state.copyWith(secondsLeft: state.secondsLeft - 1));
|
add(TimerTicked(currentSeconds - 1));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onVerifyCodeSubmitted(VerifyCodeSubmitted event,
|
Future<void> _onVerifyCodeSubmitted(VerifyCodeSubmitted event,
|
||||||
Emitter<VerifyCodeState> emit,) async {
|
Emitter<VerifyCodeState> emit,) async {
|
||||||
if (state.isSubmitting || state.code.length != 6) return;
|
if (state.isSubmitting || state.code.length != 6 || state.secondsLeft == 0) return;
|
||||||
|
|
||||||
emit(state.copyWith(isSubmitting: true, error: null));
|
emit(state.copyWith(isSubmitting: true, error: null));
|
||||||
|
|
||||||
|
// 🔹 Здесь автоматически подставится обновленный из стейта tempToken
|
||||||
final result = await _verifyCodeUseCase.execute(state.code, state.tempToken);
|
final result = await _verifyCodeUseCase.execute(state.code, state.tempToken);
|
||||||
|
|
||||||
final newState = switch (result) {
|
final newState = switch (result) {
|
||||||
Success() => state.copyWith(
|
Success() => (() {
|
||||||
isSubmitting: false,
|
_timer?.cancel();
|
||||||
isSuccess: true,
|
return state.copyWith(
|
||||||
),
|
isSubmitting: false,
|
||||||
|
isSuccess: true,
|
||||||
|
);
|
||||||
|
})(),
|
||||||
Failure(failure: final f) => switch (f) {
|
Failure(failure: final f) => switch (f) {
|
||||||
AuthFailure(attemptsLeft: final count) => state.copyWith(
|
AuthFailure(attemptsLeft: final count) => state.copyWith(
|
||||||
isSubmitting: false,
|
isSubmitting: false,
|
||||||
attemptsLeft: count,
|
attemptsLeft: count,
|
||||||
error: 'Неверный код. Осталось попыток: $count',
|
error: 'Неверный код. Осталось попыток: $count',
|
||||||
),
|
),
|
||||||
AuthBlockFailure() => state.copyWith(
|
AuthBlockFailure() => (() {
|
||||||
isSubmitting: false,
|
_timer?.cancel();
|
||||||
isBlocked: true,
|
return state.copyWith(
|
||||||
error: 'Вы заблокированы за слишком частые попытки',
|
isSubmitting: false,
|
||||||
),
|
isBlocked: true,
|
||||||
|
error: 'Вы заблокированы за слишком частые попытки',
|
||||||
|
);
|
||||||
|
})(),
|
||||||
_ => state.copyWith(
|
_ => state.copyWith(
|
||||||
isSubmitting: false,
|
isSubmitting: false,
|
||||||
error: 'Произошла ошибка. Попробуйте позже',
|
error: 'Произошла ошибка. Попробуйте позже',
|
||||||
|
|||||||
38
pubspec.lock
38
pubspec.lock
@@ -61,10 +61,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: characters
|
name: characters
|
||||||
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
|
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.0"
|
version: "1.4.1"
|
||||||
checked_yaml:
|
checked_yaml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -524,26 +524,26 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: leak_tracker
|
name: leak_tracker
|
||||||
sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0"
|
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "10.0.9"
|
version: "11.0.2"
|
||||||
leak_tracker_flutter_testing:
|
leak_tracker_flutter_testing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: leak_tracker_flutter_testing
|
name: leak_tracker_flutter_testing
|
||||||
sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
|
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.9"
|
version: "3.0.10"
|
||||||
leak_tracker_testing:
|
leak_tracker_testing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: leak_tracker_testing
|
name: leak_tracker_testing
|
||||||
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
|
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.1"
|
version: "3.0.2"
|
||||||
lints:
|
lints:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -572,26 +572,26 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: matcher
|
name: matcher
|
||||||
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.12.17"
|
version: "0.12.19"
|
||||||
material_color_utilities:
|
material_color_utilities:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: material_color_utilities
|
name: material_color_utilities
|
||||||
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.11.1"
|
version: "0.13.0"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: meta
|
name: meta
|
||||||
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
|
sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.16.0"
|
version: "1.18.0"
|
||||||
mime:
|
mime:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -817,10 +817,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
|
sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.4"
|
version: "0.7.11"
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -905,10 +905,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: vector_math
|
name: vector_math
|
||||||
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
|
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.4"
|
version: "2.2.0"
|
||||||
vm_service:
|
vm_service:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -974,5 +974,5 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "4.2.1"
|
version: "4.2.1"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.8.1 <4.0.0"
|
dart: ">=3.10.0-0 <4.0.0"
|
||||||
flutter: ">=3.32.0"
|
flutter: ">=3.32.0"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ name: be_happy
|
|||||||
description: ""
|
description: ""
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
|
|
||||||
version: 1.0.0+12
|
version: 1.0.0+13
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.8.1
|
sdk: ^3.8.1
|
||||||
|
|||||||
Reference in New Issue
Block a user