Files
worker/lib/features/promotions/presentation/pages/promotion_detail_page.dart
2025-10-24 14:42:14 +07:00

502 lines
15 KiB
Dart

/// Promotion Detail Page
///
/// Displays full details of a selected promotion including:
/// - Banner image
/// - Title and date range
/// - Program content
/// - Terms and conditions
/// - Contact information
library;
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:worker/core/router/app_router.dart';
import 'package:worker/core/theme/colors.dart';
import 'package:worker/features/home/domain/entities/promotion.dart';
import 'package:worker/features/home/presentation/providers/promotions_provider.dart';
import 'package:worker/features/promotions/presentation/widgets/highlight_box.dart';
import 'package:worker/features/promotions/presentation/widgets/promotion_section.dart';
/// Promotion Detail Page
///
/// Full-screen detail view of a promotion with scrollable content
/// and fixed bottom action bar.
class PromotionDetailPage extends ConsumerStatefulWidget {
const PromotionDetailPage({
this.promotionId,
super.key,
});
/// Promotion ID
final String? promotionId;
@override
ConsumerState<PromotionDetailPage> createState() =>
_PromotionDetailPageState();
}
class _PromotionDetailPageState extends ConsumerState<PromotionDetailPage> {
bool _isBookmarked = false;
@override
Widget build(BuildContext context) {
// Watch promotions provider
final promotionsAsync = ref.watch(promotionsProvider);
return promotionsAsync.when(
data: (promotions) {
// Find promotion by ID
final promotion = promotions.firstWhere(
(p) => p.id == widget.promotionId,
orElse: () => promotions.first,
);
return _buildDetailContent(promotion);
},
loading: () => Scaffold(
appBar: AppBar(
title: const Text('Chi tiết khuyến mãi'),
backgroundColor: Colors.white,
foregroundColor: AppColors.primaryBlue,
elevation: 1,
),
body: const Center(
child: CircularProgressIndicator(),
),
),
error: (error, stack) => Scaffold(
appBar: AppBar(
title: const Text('Chi tiết khuyến mãi'),
backgroundColor: Colors.white,
foregroundColor: AppColors.primaryBlue,
elevation: 1,
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icons.error_outline,
size: 64,
color: AppColors.danger,
),
const SizedBox(height: 16),
const Text(
'Không thể tải thông tin khuyến mãi',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 8),
Text(
error.toString(),
style: const TextStyle(
fontSize: 14,
color: AppColors.grey500,
),
textAlign: TextAlign.center,
),
],
),
),
),
);
}
Widget _buildDetailContent(Promotion promotion) {
return Scaffold(
backgroundColor: Colors.white,
body: Stack(
children: [
// Scrollable Content
CustomScrollView(
slivers: [
// App Bar
SliverAppBar(
pinned: true,
// backgroundColor: Colors.white,
foregroundColor: AppColors.primaryBlue,
elevation: 1,
shadowColor: Colors.black.withValues(alpha: 0.1),
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () => context.pop(),
),
title: const Text(
'Chi tiết khuyến mãi',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
actions: [
// Share Button
IconButton(
icon: const Icon(Icons.share),
color: const Color(0xFF64748B),
onPressed: _handleShare,
),
// Bookmark Button
IconButton(
icon: Icon(
_isBookmarked ? Icons.bookmark : Icons.bookmark_border,
),
color: const Color(0xFF64748B),
onPressed: _handleBookmark,
),
],
),
// Content
SliverToBoxAdapter(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Banner Image
_buildBannerImage(promotion),
// Promotion Header
_buildPromotionHeader(promotion),
// Program Content Section
_buildProgramContentSection(),
// Terms & Conditions Section
_buildTermsSection(),
// Contact Info Section
_buildContactSection(),
// Bottom padding for action bar
const SizedBox(height: 100),
],
),
),
],
),
// Fixed Bottom Action Bar
Positioned(
left: 0,
right: 0,
bottom: 0,
child: _buildActionBar(),
),
],
),
);
}
/// Build banner image section
Widget _buildBannerImage(Promotion promotion) {
return CachedNetworkImage(
imageUrl: promotion.imageUrl,
width: double.infinity,
height: 200,
fit: BoxFit.cover,
placeholder: (context, url) => Container(
height: 200,
color: AppColors.grey100,
child: const Center(
child: CircularProgressIndicator(),
),
),
errorWidget: (context, url, error) => Container(
height: 200,
color: AppColors.grey100,
child: const Center(
child: Icon(
Icons.image_not_supported,
size: 64,
color: AppColors.grey500,
),
),
),
);
}
/// Build promotion header with title and date
Widget _buildPromotionHeader(Promotion promotion) {
return Container(
padding: const EdgeInsets.fromLTRB(16, 24, 16, 16),
decoration: const BoxDecoration(
border: Border(
bottom: BorderSide(
color: Color(0xFFE2E8F0),
width: 1,
),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Title
Text(
promotion.title,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.w700,
color: Color(0xFF1E293B),
height: 1.3,
),
),
const SizedBox(height: 12),
// Date Range and Status
Wrap(
spacing: 8,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
// Clock icon and date
const Icon(
Icons.access_time,
size: 18,
color: Color(0xFFF59E0B), // warning color
),
Text(
_formatDateRange(promotion),
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Color(0xFFF59E0B),
),
),
// Status Badge
Container(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 4,
),
decoration: BoxDecoration(
color: const Color(0xFF10B981), // success color
borderRadius: BorderRadius.circular(16),
),
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.local_fire_department,
size: 14,
color: Colors.white,
),
SizedBox(width: 4),
Text(
'Đang diễn ra',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.white,
),
),
],
),
),
],
),
],
),
);
}
/// Build program content section
Widget _buildProgramContentSection() {
return const PromotionSection(
title: 'Nội dung chương trình',
icon: Icons.card_giftcard,
content: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
PromotionContentText(
'Chương trình khuyến mãi đặc biệt dành cho các công trình xây dựng với mức giảm giá hấp dẫn nhất trong năm.',
),
// Highlight Box
HighlightBox(
emoji: '🎉',
text: 'Giảm giá lên đến 30% cho tất cả sản phẩm gạch men cao cấp',
),
// Discount Details
PromotionContentText(
'Ưu đãi chi tiết:',
isBold: true,
),
PromotionBulletList(
items: [
'Gạch men 60x60cm: Giảm 25% - 30%',
'Gạch men 80x80cm: Giảm 20% - 25%',
'Gạch men 120x60cm: Giảm 15% - 20%',
'Gạch granite 60x60cm: Giảm 20% - 25%',
'Gạch ốp tường: Giảm 15% - 20%',
],
),
SizedBox(height: 16),
// Additional Benefits
PromotionContentText(
'Ưu đãi bổ sung:',
isBold: true,
),
PromotionBulletList(
items: [
'Miễn phí vận chuyển cho đơn hàng từ 500m²',
'Tặng keo dán gạch cho đơn hàng từ 200m²',
'Hỗ trợ thiết kế 3D miễn phí',
'Bảo hành sản phẩm lên đến 15 năm',
],
),
],
),
);
}
/// Build terms and conditions section
Widget _buildTermsSection() {
return const PromotionSection(
title: 'Điều kiện áp dụng',
icon: Icons.description,
content: PromotionBulletList(
items: [
'Áp dụng cho tất cả khách hàng là thợ xây dựng đã đăng ký tài khoản',
'Đơn hàng tối thiểu: 50m² sản phẩm gạch men',
'Thanh toán tối thiểu 50% giá trị đơn hàng khi đặt',
'Không áp dụng đồng thời với các chương trình khuyến mãi khác',
'Giá đã bao gồm VAT, chưa bao gồm phí vận chuyển',
'Sản phẩm không áp dụng đổi trả sau khi đã cắt, gia công',
'Thời gian giao hàng: 3-7 ngày làm việc tùy theo khu vực',
'Khuyến mãi có thể kết thúc sớm nếu hết hàng',
],
),
);
}
/// Build contact information section
Widget _buildContactSection() {
return const PromotionSection(
title: 'Thông tin liên hệ',
icon: Icons.phone,
isLast: true,
content: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ContactInfo(
label: 'Hotline',
value: '1900-xxxx (8:00 - 18:00 hàng ngày)',
),
ContactInfo(
label: 'Email',
value: 'promotion@company.com',
),
ContactInfo(
label: 'Zalo',
value: '0123.456.789',
),
],
),
);
}
/// Build fixed bottom action bar
Widget _buildActionBar() {
return Container(
decoration: BoxDecoration(
color: Colors.white,
border: const Border(
top: BorderSide(
color: Color(0xFFE2E8F0),
width: 1,
),
),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.1),
blurRadius: 10,
offset: const Offset(0, -2),
),
],
),
padding: const EdgeInsets.all(16),
child: SafeArea(
top: false,
child: ElevatedButton(
onPressed: _handleViewProducts,
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryBlue,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
elevation: 0,
),
child: const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.visibility, size: 20),
SizedBox(width: 8),
Text(
'Xem sản phẩm áp dụng',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
],
),
),
),
);
}
/// Format date range for display
String _formatDateRange(Promotion promotion) {
final startDay = promotion.startDate.day.toString().padLeft(2, '0');
final startMonth = promotion.startDate.month.toString().padLeft(2, '0');
final startYear = promotion.startDate.year;
final endDay = promotion.endDate.day.toString().padLeft(2, '0');
final endMonth = promotion.endDate.month.toString().padLeft(2, '0');
final endYear = promotion.endDate.year;
return '$startDay/$startMonth/$startYear - $endDay/$endMonth/$endYear';
}
/// Handle share button press
void _handleShare() {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Tính năng chia sẻ đang phát triển'),
duration: Duration(seconds: 2),
),
);
}
/// Handle bookmark button press
void _handleBookmark() {
setState(() {
_isBookmarked = !_isBookmarked;
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
_isBookmarked ? 'Đã lưu khuyến mãi' : 'Đã bỏ lưu khuyến mãi',
),
duration: const Duration(seconds: 2),
),
);
}
/// Handle view products button press
void _handleViewProducts() {
// Navigate to products page
context.push(RouteNames.products);
}
}