create promocode page
This commit is contained in:
@@ -44,7 +44,7 @@ android {
|
|||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||||
applicationId = "com.sparkit.be_happy"
|
applicationId = "com.sparkit.behappy"
|
||||||
// You can update the following values to match your application needs.
|
// You can update the following values to match your application needs.
|
||||||
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
||||||
minSdk = 26
|
minSdk = 26
|
||||||
|
|||||||
25
lib/data/models/promo_code_response_dto.dart
Normal file
25
lib/data/models/promo_code_response_dto.dart
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import '../../domain/entities/promo_code_result.dart';
|
||||||
|
|
||||||
|
class PromoCodeResponseDto {
|
||||||
|
final bool success;
|
||||||
|
final double balance;
|
||||||
|
|
||||||
|
PromoCodeResponseDto({
|
||||||
|
required this.success,
|
||||||
|
required this.balance,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory PromoCodeResponseDto.fromJson(Map<String, dynamic> json) {
|
||||||
|
return PromoCodeResponseDto(
|
||||||
|
success: json['success'] as bool,
|
||||||
|
balance: (json['balance'] as num).toDouble(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
PromoCodeResult toEntity() {
|
||||||
|
return PromoCodeResult(
|
||||||
|
success: success,
|
||||||
|
balance: balance,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1150,4 +1150,31 @@ class ApiService {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<Map<String, dynamic>> applyPromoCode(String code) async {
|
||||||
|
final token = await _securityService.getAccessToken();
|
||||||
|
if (token == null) throw Exception('No access token');
|
||||||
|
|
||||||
|
final response = await _dio.post(
|
||||||
|
'$baseUrl/promocode/apply',
|
||||||
|
data: {'code': code},
|
||||||
|
options: Options(
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': 'Bearer $token',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.statusCode == 201 || response.statusCode == 200) {
|
||||||
|
return response.data;
|
||||||
|
} else if (response.statusCode == 404) {
|
||||||
|
throw PromoCodeNotFoundException();
|
||||||
|
} else {
|
||||||
|
throw Exception('API Error: ${response.statusCode}');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Добавь исключение (можно в отдельный файл)
|
||||||
|
class PromoCodeNotFoundException implements Exception {}
|
||||||
|
|||||||
23
lib/data/repositories/promo_code_repository_impl.dart
Normal file
23
lib/data/repositories/promo_code_repository_impl.dart
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import '../../domain/entities/promo_code_result.dart';
|
||||||
|
import '../../domain/repositories/promo_code_repository.dart';
|
||||||
|
import '../models/promo_code_response_dto.dart';
|
||||||
|
import '../network/api_service.dart';
|
||||||
|
|
||||||
|
class PromoCodeRepositoryImpl implements PromoCodeRepository {
|
||||||
|
final ApiService apiService;
|
||||||
|
|
||||||
|
PromoCodeRepositoryImpl(this.apiService);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<PromoCodeResult> applyPromoCode(String code) async {
|
||||||
|
try {
|
||||||
|
final data = await apiService.applyPromoCode(code);
|
||||||
|
final dto = PromoCodeResponseDto.fromJson(data);
|
||||||
|
return dto.toEntity();
|
||||||
|
} on PromoCodeNotFoundException {
|
||||||
|
throw Exception('Промокод не найден');
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Ошибка активации промокода: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -85,11 +85,14 @@ import '../data/repositories/auth_repository_impl.dart';
|
|||||||
import '../data/repositories/news_repository_impl.dart';
|
import '../data/repositories/news_repository_impl.dart';
|
||||||
import '../data/repositories/pin_repository_impl.dart';
|
import '../data/repositories/pin_repository_impl.dart';
|
||||||
import '../data/repositories/profile_repository_impl.dart';
|
import '../data/repositories/profile_repository_impl.dart';
|
||||||
|
import '../data/repositories/promo_code_repository_impl.dart';
|
||||||
import '../data/service/news_api_service.dart';
|
import '../data/service/news_api_service.dart';
|
||||||
import '../domain/repositories/auth_repository.dart';
|
import '../domain/repositories/auth_repository.dart';
|
||||||
import '../domain/repositories/news_repository.dart';
|
import '../domain/repositories/news_repository.dart';
|
||||||
|
import '../domain/repositories/promo_code_repository.dart';
|
||||||
import '../domain/service/device_info_service.dart';
|
import '../domain/service/device_info_service.dart';
|
||||||
import '../domain/usecase/activate_subscription_usecase.dart';
|
import '../domain/usecase/activate_subscription_usecase.dart';
|
||||||
|
import '../domain/usecase/apply_promo_code_usecase.dart';
|
||||||
import '../domain/usecase/get_client_subscriptions_usecase.dart';
|
import '../domain/usecase/get_client_subscriptions_usecase.dart';
|
||||||
import '../domain/usecase/get_news_by_id_usecase.dart';
|
import '../domain/usecase/get_news_by_id_usecase.dart';
|
||||||
import '../domain/usecase/get_notifications_usecase.dart';
|
import '../domain/usecase/get_notifications_usecase.dart';
|
||||||
@@ -103,6 +106,7 @@ import '../presentation/viewmodel/map_bloc.dart';
|
|||||||
import '../presentation/viewmodel/news_bloc.dart';
|
import '../presentation/viewmodel/news_bloc.dart';
|
||||||
import '../presentation/viewmodel/notifications_bloc.dart';
|
import '../presentation/viewmodel/notifications_bloc.dart';
|
||||||
import '../presentation/viewmodel/order_history_bloc.dart';
|
import '../presentation/viewmodel/order_history_bloc.dart';
|
||||||
|
import '../presentation/viewmodel/promo_code_bloc.dart';
|
||||||
import '../presentation/viewmodel/scooter_detail_modal_bloc.dart';
|
import '../presentation/viewmodel/scooter_detail_modal_bloc.dart';
|
||||||
import '../presentation/viewmodel/subscription_list_bloc.dart';
|
import '../presentation/viewmodel/subscription_list_bloc.dart';
|
||||||
import '../presentation/viewmodel/verify_code_bloc.dart';
|
import '../presentation/viewmodel/verify_code_bloc.dart';
|
||||||
@@ -383,4 +387,20 @@ Future<void> setupDependencies() async {
|
|||||||
getIt.registerFactory<ScooterCodeBloc>(
|
getIt.registerFactory<ScooterCodeBloc>(
|
||||||
() => ScooterCodeBloc(getScooterByTitleUsecase: getIt<GetScooterByTitleUsecase>()),
|
() => ScooterCodeBloc(getScooterByTitleUsecase: getIt<GetScooterByTitleUsecase>()),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
// Repository
|
||||||
|
getIt.registerSingleton<PromoCodeRepository>(
|
||||||
|
PromoCodeRepositoryImpl(getIt<ApiService>()),
|
||||||
|
);
|
||||||
|
|
||||||
|
// UseCase
|
||||||
|
getIt.registerSingleton<ApplyPromoCodeUsecase>(
|
||||||
|
ApplyPromoCodeUsecase(getIt<PromoCodeRepository>()),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Bloc (factory, т.к. экран создаёт новый экземпляр)
|
||||||
|
getIt.registerFactory<PromoCodeBloc>(
|
||||||
|
() => PromoCodeBloc(getIt<ApplyPromoCodeUsecase>()),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
9
lib/domain/entities/promo_code_result.dart
Normal file
9
lib/domain/entities/promo_code_result.dart
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
class PromoCodeResult {
|
||||||
|
final bool success;
|
||||||
|
final double balance;
|
||||||
|
|
||||||
|
PromoCodeResult({
|
||||||
|
required this.success,
|
||||||
|
required this.balance,
|
||||||
|
});
|
||||||
|
}
|
||||||
5
lib/domain/repositories/promo_code_repository.dart
Normal file
5
lib/domain/repositories/promo_code_repository.dart
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import '../entities/promo_code_result.dart';
|
||||||
|
|
||||||
|
abstract class PromoCodeRepository {
|
||||||
|
Future<PromoCodeResult> applyPromoCode(String code);
|
||||||
|
}
|
||||||
12
lib/domain/usecase/apply_promo_code_usecase.dart
Normal file
12
lib/domain/usecase/apply_promo_code_usecase.dart
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import '../entities/promo_code_result.dart';
|
||||||
|
import '../repositories/promo_code_repository.dart';
|
||||||
|
|
||||||
|
class ApplyPromoCodeUsecase {
|
||||||
|
final PromoCodeRepository repository;
|
||||||
|
|
||||||
|
ApplyPromoCodeUsecase(this.repository);
|
||||||
|
|
||||||
|
Future<PromoCodeResult> call(String code) {
|
||||||
|
return repository.applyPromoCode(code);
|
||||||
|
}
|
||||||
|
}
|
||||||
8
lib/presentation/event/promo_code_event.dart
Normal file
8
lib/presentation/event/promo_code_event.dart
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
sealed class PromoCodeEvent {}
|
||||||
|
|
||||||
|
class PromoCodeApplyRequested extends PromoCodeEvent {
|
||||||
|
final String code;
|
||||||
|
PromoCodeApplyRequested(this.code);
|
||||||
|
}
|
||||||
|
|
||||||
|
class PromoCodeReset extends PromoCodeEvent {}
|
||||||
@@ -1,40 +1,45 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import '../../core/app_colors.dart';
|
import '../../core/app_colors.dart';
|
||||||
|
import '../../di/service_locator.dart';
|
||||||
|
import '../../domain/usecase/apply_promo_code_usecase.dart';
|
||||||
import '../components/custom_app_bar.dart';
|
import '../components/custom_app_bar.dart';
|
||||||
|
import '../event/promo_code_event.dart';
|
||||||
|
import '../state/promo_code_state.dart';
|
||||||
|
import '../viewmodel/promo_code_bloc.dart';
|
||||||
|
|
||||||
class PromoCodeScreen extends StatefulWidget {
|
class PromoCodeScreen extends StatelessWidget {
|
||||||
const PromoCodeScreen({super.key});
|
const PromoCodeScreen({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<PromoCodeScreen> createState() => _PromoCodeScreenState();
|
Widget build(BuildContext context) {
|
||||||
}
|
return BlocProvider(
|
||||||
|
create: (context) => PromoCodeBloc(getIt<ApplyPromoCodeUsecase>()),
|
||||||
class _PromoCodeScreenState extends State<PromoCodeScreen> {
|
child: const _PromoCodeScreenContent(),
|
||||||
final TextEditingController promoController = TextEditingController();
|
|
||||||
bool isError = false;
|
|
||||||
|
|
||||||
void _activatePromo() {
|
|
||||||
if (promoController.text == 'G17N160') {
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
const SnackBar(content: Text('Промокод активирован!')),
|
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
setState(() {
|
|
||||||
isError = true;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _retry() {
|
class _PromoCodeScreenContent extends StatefulWidget {
|
||||||
setState(() {
|
const _PromoCodeScreenContent();
|
||||||
isError = false;
|
|
||||||
promoController.clear();
|
@override
|
||||||
});
|
State<_PromoCodeScreenContent> createState() => _PromoCodeScreenContentState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PromoCodeScreenContentState extends State<_PromoCodeScreenContent> {
|
||||||
|
final TextEditingController promoController = TextEditingController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
promoController.dispose();
|
||||||
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
resizeToAvoidBottomInset: false,
|
||||||
body: Container(
|
body: Container(
|
||||||
decoration: const BoxDecoration(gradient: AppColors.phoneScreenBg),
|
decoration: const BoxDecoration(gradient: AppColors.phoneScreenBg),
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
@@ -49,10 +54,42 @@ class _PromoCodeScreenState extends State<PromoCodeScreen> {
|
|||||||
CustomAppBar(title: 'Промокоды'),
|
CustomAppBar(title: 'Промокоды'),
|
||||||
const SizedBox(height: 32),
|
const SizedBox(height: 32),
|
||||||
|
|
||||||
Container(
|
// 🔹 LISTENER: Только сайд-эффекты (SnackBar, навигация)
|
||||||
|
BlocListener<PromoCodeBloc, PromoCodeState>(
|
||||||
|
listener: (context, state) {
|
||||||
|
if (state.status == PromoCodeStatus.success) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(
|
||||||
|
'Промокод активирован! Баланс: ${state.newBalance?.toStringAsFixed(2)} BYN',
|
||||||
|
),
|
||||||
|
backgroundColor: Colors.green,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
// Очищаем поле визуально
|
||||||
|
promoController.clear();
|
||||||
|
// Ждем чуть меньше, чтобы пользователь увидел успех
|
||||||
|
Future.delayed(const Duration(milliseconds: 1200), () {
|
||||||
|
if (mounted) Navigator.pop(context);
|
||||||
|
});
|
||||||
|
} else if (state.status == PromoCodeStatus.failure) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(state.errorMessage ?? 'Ошибка активации'),
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: BlocBuilder<PromoCodeBloc, PromoCodeState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
final bool hasError = state.status == PromoCodeStatus.failure;
|
||||||
|
final bool isLoading = state.status == PromoCodeStatus.loading;
|
||||||
|
|
||||||
|
return Container(
|
||||||
padding: const EdgeInsets.all(25),
|
padding: const EdgeInsets.all(25),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Color(0xFF141530),
|
color: const Color(0xFF141530),
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -60,26 +97,19 @@ class _PromoCodeScreenState extends State<PromoCodeScreen> {
|
|||||||
children: [
|
children: [
|
||||||
const Text(
|
const Text(
|
||||||
'У вас есть промокод?',
|
'У вас есть промокод?',
|
||||||
style: TextStyle(
|
style: TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.w600),
|
||||||
color: Colors.white,
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
const Text(
|
const Text(
|
||||||
'Введите промокод и получите скидку на поездку',
|
'Введите промокод и получите скидку на поездку',
|
||||||
style: TextStyle(
|
style: TextStyle(color: AppColors.white70, fontSize: 16),
|
||||||
color: AppColors.white70,
|
|
||||||
fontSize: 16,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
TextField(
|
TextField(
|
||||||
controller: promoController,
|
controller: promoController,
|
||||||
style: TextStyle(
|
enabled: !isLoading,
|
||||||
color: isError ? Colors.red : Colors.white,
|
style: TextStyle(color: hasError ? Colors.red : Colors.white),
|
||||||
),
|
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: 'Введите промокод',
|
hintText: 'Введите промокод',
|
||||||
hintStyle: const TextStyle(color: AppColors.white70),
|
hintStyle: const TextStyle(color: AppColors.white70),
|
||||||
@@ -95,60 +125,64 @@ class _PromoCodeScreenState extends State<PromoCodeScreen> {
|
|||||||
),
|
),
|
||||||
focusedBorder: OutlineInputBorder(
|
focusedBorder: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(24),
|
borderRadius: BorderRadius.circular(24),
|
||||||
borderSide: BorderSide(color: AppColors.smsDigit, width: 1.5),
|
borderSide: BorderSide(
|
||||||
|
color: hasError ? Colors.red : AppColors.smsDigit,
|
||||||
|
width: 1.5,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
// ✅ Явно убираем ошибку при успехе
|
||||||
|
errorText: hasError ? 'Неверный промокод' : null,
|
||||||
|
),
|
||||||
|
onSubmitted: (_) => _submit(context),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: OutlinedButton(
|
child: OutlinedButton(
|
||||||
onPressed: () => Navigator.pop(context),
|
onPressed: isLoading ? null : () => Navigator.pop(context),
|
||||||
style: OutlinedButton.styleFrom(
|
style: OutlinedButton.styleFrom(
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)),
|
||||||
borderRadius: BorderRadius.circular(24),
|
|
||||||
),
|
|
||||||
side: BorderSide(color: AppColors.white70.withOpacity(0.4)),
|
side: BorderSide(color: AppColors.white70.withOpacity(0.4)),
|
||||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
),
|
),
|
||||||
child: const Text(
|
child: const Text('Отмена', style: TextStyle(color: AppColors.white70)),
|
||||||
'Отмена',
|
|
||||||
style: TextStyle(color: AppColors.white70),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 22),
|
const SizedBox(width: 22),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
onPressed: _activatePromo,
|
onPressed: isLoading ? null : () => _submit(context),
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)),
|
||||||
borderRadius: BorderRadius.circular(24),
|
|
||||||
),
|
|
||||||
backgroundColor: AppColors.activeButtonGradient.colors[0],
|
backgroundColor: AppColors.activeButtonGradient.colors[0],
|
||||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
),
|
),
|
||||||
child: const Text(
|
child: isLoading
|
||||||
'Активировать',
|
? const SizedBox(
|
||||||
style: TextStyle(color: AppColors.activeButtonText),
|
width: 20,
|
||||||
),
|
height: 20,
|
||||||
|
child: CircularProgressIndicator(color: AppColors.activeButtonText, strokeWidth: 2),
|
||||||
|
)
|
||||||
|
: const Text('Активировать', style: TextStyle(color: AppColors.activeButtonText)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
Image.asset(
|
||||||
|
'assets/promo_bottom.png',
|
||||||
Image.asset('assets/promo_bottom.png',
|
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
fit: BoxFit.contain,
|
fit: BoxFit.contain,
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
@@ -160,4 +194,10 @@ class _PromoCodeScreenState extends State<PromoCodeScreen> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _submit(BuildContext context) {
|
||||||
|
final code = promoController.text.trim();
|
||||||
|
if (code.isEmpty) return;
|
||||||
|
context.read<PromoCodeBloc>().add(PromoCodeApplyRequested(code));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
25
lib/presentation/state/promo_code_state.dart
Normal file
25
lib/presentation/state/promo_code_state.dart
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
enum PromoCodeStatus { initial, loading, success, failure }
|
||||||
|
|
||||||
|
class PromoCodeState {
|
||||||
|
final PromoCodeStatus status;
|
||||||
|
final double? newBalance;
|
||||||
|
final String? errorMessage;
|
||||||
|
|
||||||
|
const PromoCodeState({
|
||||||
|
this.status = PromoCodeStatus.initial,
|
||||||
|
this.newBalance,
|
||||||
|
this.errorMessage,
|
||||||
|
});
|
||||||
|
|
||||||
|
PromoCodeState copyWith({
|
||||||
|
PromoCodeStatus? status,
|
||||||
|
double? newBalance,
|
||||||
|
String? errorMessage,
|
||||||
|
}) {
|
||||||
|
return PromoCodeState(
|
||||||
|
status: status ?? this.status,
|
||||||
|
newBalance: newBalance ?? this.newBalance,
|
||||||
|
errorMessage: errorMessage ?? this.errorMessage,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
37
lib/presentation/viewmodel/promo_code_bloc.dart
Normal file
37
lib/presentation/viewmodel/promo_code_bloc.dart
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import '../../domain/usecase/apply_promo_code_usecase.dart';
|
||||||
|
import '../event/promo_code_event.dart';
|
||||||
|
import '../state/promo_code_state.dart';
|
||||||
|
|
||||||
|
class PromoCodeBloc extends Bloc<PromoCodeEvent, PromoCodeState> {
|
||||||
|
final ApplyPromoCodeUsecase _applyPromoCodeUsecase;
|
||||||
|
|
||||||
|
PromoCodeBloc(this._applyPromoCodeUsecase) : super(const PromoCodeState()) {
|
||||||
|
on<PromoCodeApplyRequested>(_onApplyRequested);
|
||||||
|
on<PromoCodeReset>(_onReset);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onApplyRequested(
|
||||||
|
PromoCodeApplyRequested event,
|
||||||
|
Emitter<PromoCodeState> emit,
|
||||||
|
) async {
|
||||||
|
emit(state.copyWith(status: PromoCodeStatus.loading));
|
||||||
|
|
||||||
|
try {
|
||||||
|
final result = await _applyPromoCodeUsecase(event.code);
|
||||||
|
emit(state.copyWith(
|
||||||
|
status: PromoCodeStatus.success,
|
||||||
|
newBalance: result.balance,
|
||||||
|
));
|
||||||
|
} catch (e) {
|
||||||
|
emit(state.copyWith(
|
||||||
|
status: PromoCodeStatus.failure,
|
||||||
|
errorMessage: e.toString(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onReset(PromoCodeReset event, Emitter<PromoCodeState> emit) {
|
||||||
|
emit(const PromoCodeState());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ name: be_happy
|
|||||||
description: ""
|
description: ""
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
|
|
||||||
version: 1.0.0+11
|
version: 1.0.0+12
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.8.1
|
sdk: ^3.8.1
|
||||||
|
|||||||
Reference in New Issue
Block a user