176 lines
6.0 KiB
Dart
176 lines
6.0 KiB
Dart
/// Reward Card Widget
|
|
///
|
|
/// Displays a gift catalog item with redemption button.
|
|
library;
|
|
|
|
import 'package:cached_network_image/cached_network_image.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:intl/intl.dart';
|
|
import 'package:worker/core/theme/colors.dart';
|
|
import 'package:worker/features/loyalty/domain/entities/gift_catalog.dart';
|
|
import 'package:worker/features/loyalty/presentation/providers/loyalty_points_provider.dart';
|
|
|
|
/// Reward Card Widget
|
|
///
|
|
/// Shows gift information and redemption button.
|
|
/// Button state changes based on whether user has enough points.
|
|
class RewardCard extends ConsumerWidget {
|
|
/// Gift to display
|
|
final GiftCatalog gift;
|
|
|
|
/// Callback when redeem button is pressed
|
|
final VoidCallback onRedeem;
|
|
|
|
const RewardCard({required this.gift, required this.onRedeem, super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
final hasEnoughPoints = ref.watch(hasEnoughPointsProvider(gift.pointsCost));
|
|
final numberFormat = NumberFormat('#,###', 'vi_VN');
|
|
|
|
return Card(
|
|
elevation: 2,
|
|
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 8),
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
|
clipBehavior: Clip.antiAlias,
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
// Gift Image
|
|
_buildImage(),
|
|
|
|
// Gift Info
|
|
Expanded(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(12),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// Gift Name
|
|
Text(
|
|
gift.name,
|
|
style: const TextStyle(
|
|
fontSize: 14,
|
|
fontWeight: FontWeight.w600,
|
|
color: AppColors.grey900,
|
|
height: 1.3,
|
|
),
|
|
maxLines: 2,
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
const SizedBox(height: 4),
|
|
|
|
// Gift Description
|
|
if (gift.description != null && gift.description!.isNotEmpty)
|
|
Text(
|
|
gift.description!,
|
|
style: const TextStyle(
|
|
fontSize: 12,
|
|
color: AppColors.grey500,
|
|
height: 1.2,
|
|
),
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
|
|
// Spacer to push points and button to bottom
|
|
const Spacer(),
|
|
|
|
// Points Cost (at bottom)
|
|
Text(
|
|
'${numberFormat.format(gift.pointsCost)} điểm',
|
|
style: const TextStyle(
|
|
fontSize: 14,
|
|
fontWeight: FontWeight.w700,
|
|
color: AppColors.primaryBlue,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
|
|
// Redeem Button (at bottom)
|
|
SizedBox(
|
|
width: double.infinity,
|
|
height: 36,
|
|
child: ElevatedButton.icon(
|
|
onPressed: hasEnoughPoints && gift.isAvailable
|
|
? onRedeem
|
|
: null,
|
|
icon: Icon(
|
|
Icons.card_giftcard,
|
|
size: 16,
|
|
color: hasEnoughPoints && gift.isAvailable
|
|
? Colors.white
|
|
: AppColors.grey500,
|
|
),
|
|
label: Text(
|
|
hasEnoughPoints && gift.isAvailable
|
|
? 'Đổi quà'
|
|
: 'Không đủ điểm',
|
|
style: const TextStyle(
|
|
fontSize: 13,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: hasEnoughPoints && gift.isAvailable
|
|
? AppColors.primaryBlue
|
|
: AppColors.grey100,
|
|
foregroundColor: hasEnoughPoints && gift.isAvailable
|
|
? Colors.white
|
|
: AppColors.grey500,
|
|
elevation: 0,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 12,
|
|
vertical: 8,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
/// Build gift image
|
|
Widget _buildImage() {
|
|
return SizedBox(
|
|
height: 120,
|
|
child: gift.imageUrl != null && gift.imageUrl!.isNotEmpty
|
|
? CachedNetworkImage(
|
|
imageUrl: gift.imageUrl!,
|
|
fit: BoxFit.cover,
|
|
placeholder: (context, url) => Container(
|
|
color: AppColors.grey100,
|
|
child: const Center(
|
|
child: CircularProgressIndicator(strokeWidth: 2),
|
|
),
|
|
),
|
|
errorWidget: (context, url, error) => Container(
|
|
color: AppColors.grey100,
|
|
child: const Icon(
|
|
Icons.card_giftcard,
|
|
size: 48,
|
|
color: AppColors.grey500,
|
|
),
|
|
),
|
|
)
|
|
: Container(
|
|
color: AppColors.grey100,
|
|
child: const Icon(
|
|
Icons.card_giftcard,
|
|
size: 48,
|
|
color: AppColors.grey500,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|