You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
631 lines
22 KiB
Dart
631 lines
22 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter_svg/flutter_svg.dart';
|
|
import 'package:mohem_flutter_app/api/offers_and_discounts_api_client.dart';
|
|
import 'package:mohem_flutter_app/app_state/app_state.dart';
|
|
import 'package:mohem_flutter_app/classes/consts.dart';
|
|
import 'package:mohem_flutter_app/config/routes.dart';
|
|
import 'package:mohem_flutter_app/extensions/int_extensions.dart';
|
|
import 'package:mohem_flutter_app/models/offers_and_discounts/wala_plus_retailer_model.dart';
|
|
import 'package:mohem_flutter_app/widgets/app_bar_widget.dart';
|
|
import 'package:shimmer/shimmer.dart';
|
|
import 'package:url_launcher/url_launcher.dart';
|
|
|
|
class WalaPlusRetailerDetailsScreen extends StatefulWidget {
|
|
final WalaPlusRetailerModel? retailer;
|
|
final String? retailerId;
|
|
|
|
const WalaPlusRetailerDetailsScreen({
|
|
Key? key,
|
|
this.retailer,
|
|
this.retailerId,
|
|
}) : super(key: key);
|
|
|
|
@override
|
|
State<WalaPlusRetailerDetailsScreen> createState() => _WalaPlusRetailerDetailsScreenState();
|
|
}
|
|
|
|
class _WalaPlusRetailerDetailsScreenState extends State<WalaPlusRetailerDetailsScreen> {
|
|
WalaPlusRetailerModel? _retailer;
|
|
bool _isLoading = false;
|
|
String? _effectiveRetailerId;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_retailer = widget.retailer;
|
|
_effectiveRetailerId = widget.retailerId ?? widget.retailer?.id;
|
|
}
|
|
|
|
@override
|
|
void didChangeDependencies() {
|
|
super.didChangeDependencies();
|
|
if (_retailer == null && _effectiveRetailerId == null) {
|
|
Object? args = ModalRoute.of(context)?.settings.arguments;
|
|
if (args is WalaPlusRetailerModel) {
|
|
_retailer = args;
|
|
_effectiveRetailerId = args.id;
|
|
} else if (args is String) {
|
|
_effectiveRetailerId = args;
|
|
}
|
|
}
|
|
|
|
if (_retailer == null && _effectiveRetailerId != null && !_isLoading) {
|
|
_fetchRetailerDetails();
|
|
}
|
|
}
|
|
|
|
Future<void> _fetchRetailerDetails() async {
|
|
if (_effectiveRetailerId == null) return;
|
|
setState(() {
|
|
_isLoading = true;
|
|
});
|
|
|
|
try {
|
|
WalaPlusRetailerModel? result = await OffersAndDiscountsApiClient().getWalaPlusRetailerById(
|
|
retailerId: _effectiveRetailerId!,
|
|
context: context,
|
|
);
|
|
if (result != null && mounted) {
|
|
setState(() {
|
|
_retailer = result;
|
|
});
|
|
}
|
|
} catch (e) {
|
|
debugPrint("Error fetching WalaPlus retailer by ID: $e");
|
|
} finally {
|
|
if (mounted) {
|
|
setState(() {
|
|
_isLoading = false;
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
void _onUseOffer() async {
|
|
String? website = _retailer?.resolvedWebsite;
|
|
if (website != null && website.isNotEmpty) {
|
|
try {
|
|
Uri uri = Uri.parse(website);
|
|
if (!uri.hasScheme) {
|
|
uri = Uri.parse("https://$website");
|
|
}
|
|
await launchUrl(uri, mode: LaunchMode.externalApplication);
|
|
return;
|
|
} catch (e) {
|
|
debugPrint("Error launching website: $e");
|
|
}
|
|
}
|
|
|
|
// If promo pin exists, display a dialog with the promo code to copy
|
|
if (_retailer?.pin != null && _retailer!.pin!.isNotEmpty) {
|
|
_showPromoCodeDialog(_retailer!.pin!);
|
|
return;
|
|
}
|
|
|
|
// Otherwise show terms/usage dialog
|
|
_showTermsDialog();
|
|
}
|
|
|
|
void _showPromoCodeDialog(String pin) {
|
|
bool isAr = AppState().isArabic(context);
|
|
showDialog(
|
|
context: context,
|
|
builder: (cxt) => AlertDialog(
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
|
title: Text(
|
|
isAr ? "كود الخصم" : "Promo Code",
|
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
|
),
|
|
content: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xFFF3F4F6),
|
|
borderRadius: BorderRadius.circular(10),
|
|
border: Border.all(color: const Color(0xFF20A4A8), width: 1.2),
|
|
),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text(
|
|
pin,
|
|
style: const TextStyle(
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.bold,
|
|
letterSpacing: 2,
|
|
color: Color(0xFF1E293B),
|
|
),
|
|
),
|
|
IconButton(
|
|
icon: const Icon(Icons.copy_rounded, color: Color(0xFF20A4A8)),
|
|
onPressed: () {
|
|
Clipboard.setData(ClipboardData(text: pin));
|
|
Navigator.pop(cxt);
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Text(isAr ? "تم نسخ الكود بنجاح" : "Promo code copied"),
|
|
duration: const Duration(seconds: 2),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(cxt),
|
|
child: Text(isAr ? "إغلاق" : "Close", style: const TextStyle(color: Color(0xFF20A4A8))),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
void _showTermsDialog() {
|
|
bool isAr = AppState().isArabic(context);
|
|
String content = _retailer?.howToUse ??
|
|
_retailer?.arHowToUse ??
|
|
_retailer?.enHowToUse ??
|
|
_retailer?.usage?.toString() ??
|
|
(isAr ? "تطبق الشروط والأحكام الخاصة بالعرض." : "Terms and conditions apply for this offer.");
|
|
|
|
// Clean html tags if simple
|
|
content = content.replaceAll(RegExp(r'<[^>]*>|&[^;]+;'), ' ').trim();
|
|
|
|
showModalBottomSheet(
|
|
context: context,
|
|
shape: const RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
|
|
),
|
|
builder: (cxt) => SafeArea(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(20.0),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text(
|
|
isAr ? "الشروط والأحكام" : "Terms and Conditions",
|
|
style: const TextStyle(
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.bold,
|
|
color: Color(0xFF1E293B),
|
|
),
|
|
),
|
|
IconButton(
|
|
icon: const Icon(Icons.close),
|
|
onPressed: () => Navigator.pop(cxt),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 12),
|
|
Text(
|
|
content,
|
|
style: const TextStyle(fontSize: 14, color: Color(0xFF4B5563), height: 1.5),
|
|
),
|
|
const SizedBox(height: 20),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
bool isAr = AppState().isArabic(context);
|
|
String title = _retailer?.name ?? (isAr ? "تفاصيل العرض" : "Offer Details");
|
|
|
|
return Scaffold(
|
|
backgroundColor: const Color(0xFFF8F9FA),
|
|
appBar: AppBarWidget(
|
|
context,
|
|
title: title,
|
|
showHomeButton: true,
|
|
onHomeTapped: () {
|
|
if (ApiConsts.isDependetLogin) {
|
|
Navigator.popUntil(context, ModalRoute.withName(AppRoutes.dashboardScreenForDependet));
|
|
} else {
|
|
Navigator.popUntil(context, ModalRoute.withName(AppRoutes.dashboard));
|
|
}
|
|
},
|
|
),
|
|
body: _isLoading && _retailer == null
|
|
? _buildLoadingSkeleton()
|
|
: Column(
|
|
children: [
|
|
Expanded(
|
|
child: SingleChildScrollView(
|
|
physics: const BouncingScrollPhysics(),
|
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
|
child: Container(
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(22),
|
|
border: Border.all(color: const Color(0xFFF1F5F9), width: 1.2),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.04),
|
|
blurRadius: 12,
|
|
offset: const Offset(0, 4),
|
|
),
|
|
],
|
|
),
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// 1. Top Banner Image with rounded corners
|
|
ClipRRect(
|
|
borderRadius: BorderRadius.circular(16),
|
|
child: SizedBox(
|
|
height: 190,
|
|
width: double.infinity,
|
|
child: (_retailer?.image != null && _retailer!.image!.isNotEmpty)
|
|
? Image.network(
|
|
_retailer!.image!,
|
|
fit: BoxFit.cover,
|
|
errorBuilder: (_, __, ___) => Container(
|
|
color: const Color(0xFFF3F4F6),
|
|
child: const Icon(Icons.broken_image, size: 48, color: Color(0xFF9CA3AF)),
|
|
),
|
|
)
|
|
: Container(
|
|
color: const Color(0xFFF3F4F6),
|
|
child: const Icon(Icons.image_outlined, size: 48, color: Color(0xFF9CA3AF)),
|
|
),
|
|
),
|
|
),
|
|
14.height,
|
|
|
|
// 2. Brand Strip (Human Resources - Retailer Logo - Dr Sulaiman Al Habib)
|
|
_buildBrandStrip(isAr),
|
|
18.height,
|
|
|
|
// 3. Retailer Name (Centered, Bold)
|
|
Center(
|
|
child: Text(
|
|
_retailer?.name ?? "",
|
|
textAlign: TextAlign.center,
|
|
style: const TextStyle(
|
|
fontSize: 22,
|
|
fontWeight: FontWeight.bold,
|
|
color: Color(0xFF1E293B),
|
|
),
|
|
),
|
|
),
|
|
18.height,
|
|
|
|
// 4. 3 Info Badges Row (Store Type, Validity, City)
|
|
_buildInfoBadgesRow(isAr),
|
|
22.height,
|
|
|
|
// 5. Description Section
|
|
Text(
|
|
isAr ? "الوصف:" : "description:",
|
|
style: const TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.bold,
|
|
color: Color(0xFF1E293B),
|
|
),
|
|
),
|
|
8.height,
|
|
Text(
|
|
_getCleanDescription(),
|
|
style: const TextStyle(
|
|
fontSize: 14,
|
|
fontWeight: FontWeight.w400,
|
|
color: Color(0xFF4B5563),
|
|
height: 1.45,
|
|
),
|
|
),
|
|
16.height,
|
|
|
|
// 6. Terms and Conditions Link (Right aligned)
|
|
Align(
|
|
alignment: isAr ? Alignment.centerLeft : Alignment.centerRight,
|
|
child: InkWell(
|
|
onTap: _showTermsDialog,
|
|
child: Text(
|
|
isAr ? "الشروط والأحكام" : "terms and conditions",
|
|
style: const TextStyle(
|
|
fontSize: 14,
|
|
fontWeight: FontWeight.w600,
|
|
color: Color(0xFF20A4A8),
|
|
decoration: TextDecoration.underline,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
|
|
// 7. Bottom Action Button
|
|
Container(
|
|
padding: const EdgeInsets.fromLTRB(16, 10, 16, 20),
|
|
color: Colors.white,
|
|
child: SizedBox(
|
|
width: double.infinity,
|
|
height: 50,
|
|
child: ElevatedButton(
|
|
onPressed: _onUseOffer,
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: const Color(0xFF20A4A8),
|
|
foregroundColor: Colors.white,
|
|
elevation: 0,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
),
|
|
child: Text(
|
|
isAr ? "استخدام العرض" : "Use Offer",
|
|
style: const TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.w700,
|
|
color: Colors.white,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildBrandStrip(bool isAr) {
|
|
return Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
children: [
|
|
// Left: Human Resources branding
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: const [
|
|
Text(
|
|
"الموارد البشرية",
|
|
style: TextStyle(
|
|
color: Color(0xFFED1C2B),
|
|
fontSize: 10,
|
|
fontWeight: FontWeight.w700,
|
|
),
|
|
),
|
|
Text(
|
|
"Human Resources",
|
|
style: TextStyle(
|
|
color: Color(0xFFED1C2B),
|
|
fontSize: 8.5,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
|
|
// Center: Retailer Logo
|
|
if (_retailer?.logo != null && _retailer!.logo!.isNotEmpty)
|
|
Image.network(
|
|
_retailer!.logo!,
|
|
height: 36,
|
|
width: 36,
|
|
fit: BoxFit.contain,
|
|
errorBuilder: (_, __, ___) => const SizedBox(width: 36, height: 36),
|
|
)
|
|
else
|
|
const SizedBox(width: 36, height: 36),
|
|
|
|
// Right: Dr Sulaiman Al Habib branding with red cross
|
|
Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.end,
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: const [
|
|
Text(
|
|
"سليمان الحبيب",
|
|
style: TextStyle(
|
|
color: Color(0xFF4B5563),
|
|
fontSize: 9,
|
|
fontWeight: FontWeight.w700,
|
|
),
|
|
),
|
|
Text(
|
|
"DR SULAIMAN AL HABIB",
|
|
style: TextStyle(
|
|
color: Color(0xFF4B5563),
|
|
fontSize: 8,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(width: 5),
|
|
SvgPicture.asset(
|
|
"assets/icons/mazaya_brand.svg",
|
|
height: 22,
|
|
fit: BoxFit.contain,
|
|
),
|
|
],
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildInfoBadgesRow(bool isAr) {
|
|
String storeTypeText = (_retailer?.type?.toLowerCase() == 'online')
|
|
? (isAr ? "عبر الإنترنت" : "online")
|
|
: (isAr ? "في المتجر" : "in store");
|
|
|
|
IconData storeIcon = (_retailer?.type?.toLowerCase() == 'online')
|
|
? Icons.language_rounded
|
|
: Icons.storefront_rounded;
|
|
|
|
String cityText = _retailer?.displayCity ?? "";
|
|
if (cityText.isEmpty) {
|
|
cityText = isAr ? "الرياض" : "Riyadh";
|
|
}
|
|
|
|
String validityText = isAr ? "٣ أشهر" : "3 Month";
|
|
|
|
return Row(
|
|
children: [
|
|
Expanded(
|
|
child: _buildInfoCard(
|
|
icon: storeIcon,
|
|
label: storeTypeText,
|
|
),
|
|
),
|
|
12.width,
|
|
Expanded(
|
|
child: _buildInfoCard(
|
|
icon: Icons.check_circle_outline_rounded,
|
|
label: validityText,
|
|
),
|
|
),
|
|
12.width,
|
|
Expanded(
|
|
child: _buildInfoCard(
|
|
icon: Icons.location_on_rounded,
|
|
label: cityText,
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildInfoCard({required IconData icon, required String label}) {
|
|
return Container(
|
|
padding: const EdgeInsets.symmetric(vertical: 14, horizontal: 8),
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xFFF4F6F9),
|
|
borderRadius: BorderRadius.circular(14),
|
|
),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Icon(icon, color: const Color(0xFF20A4A8), size: 26),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
label,
|
|
textAlign: TextAlign.center,
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
style: const TextStyle(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w600,
|
|
color: Color(0xFF334155),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
String _getCleanDescription() {
|
|
String desc = _retailer?.description ?? _retailer?.info ?? _retailer?.offerTitle ?? "";
|
|
if (desc.isEmpty) {
|
|
return AppState().isArabic(context) ? "لا توجد تفاصيل إضافية متاحة." : "No additional details available.";
|
|
}
|
|
// Remove HTML tags if string contains them
|
|
return desc.replaceAll(RegExp(r'<[^>]*>|&[^;]+;'), ' ').trim();
|
|
}
|
|
|
|
Widget _buildLoadingSkeleton() {
|
|
return Shimmer.fromColors(
|
|
baseColor: const Color(0xFFE2E8F0),
|
|
highlightColor: const Color(0xFFF8FAFC),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: Container(
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(22),
|
|
),
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Container(
|
|
height: 190,
|
|
width: double.infinity,
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(16),
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
Center(
|
|
child: Container(
|
|
height: 24,
|
|
width: 160,
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(6),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 20),
|
|
Row(
|
|
children: List.generate(
|
|
3,
|
|
(index) => Expanded(
|
|
child: Container(
|
|
height: 75,
|
|
margin: EdgeInsets.symmetric(horizontal: index == 1 ? 8 : 0),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(14),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 24),
|
|
Container(
|
|
height: 16,
|
|
width: 100,
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(4),
|
|
),
|
|
),
|
|
const SizedBox(height: 10),
|
|
Container(
|
|
height: 14,
|
|
width: double.infinity,
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(4),
|
|
),
|
|
),
|
|
const SizedBox(height: 6),
|
|
Container(
|
|
height: 14,
|
|
width: 220,
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(4),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|