mystery box
This commit is contained in:
@@ -14,36 +14,50 @@ class HomeFeatureSection extends StatelessWidget {
|
||||
builder: (context, state) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
HomeFeatureCard(
|
||||
icon: Icons.card_giftcard,
|
||||
title: 'Reward',
|
||||
iconColor: const Color(0xFF1976D2),
|
||||
onTap: () => context.router.push(RewardRoute()),
|
||||
),
|
||||
HomeFeatureCard(
|
||||
icon: Icons.casino,
|
||||
title: 'Undian',
|
||||
iconColor: const Color(0xFF7B1FA2),
|
||||
onTap: () => context.router.push(DrawRoute()),
|
||||
),
|
||||
HomeFeatureCard(
|
||||
icon: Icons.store,
|
||||
title: 'Merchant',
|
||||
iconColor: const Color(0xFF388E3C),
|
||||
onTap: () => context.router.push(MerchantRoute()),
|
||||
),
|
||||
HomeFeatureCard(
|
||||
icon: Icons.blur_circular,
|
||||
title: 'Wheels',
|
||||
iconColor: const Color(0xFF388E3C),
|
||||
onTap: () => state.isAuthenticated
|
||||
? context.router.push(FerrisWheelRoute())
|
||||
: context.router.push(OnboardingRoute()),
|
||||
),
|
||||
],
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
children: [
|
||||
HomeFeatureCard(
|
||||
icon: Icons.card_giftcard,
|
||||
title: 'Reward',
|
||||
iconColor: const Color(0xFF1976D2),
|
||||
onTap: () => context.router.push(RewardRoute()),
|
||||
),
|
||||
SizedBox(width: 12),
|
||||
HomeFeatureCard(
|
||||
icon: Icons.casino,
|
||||
title: 'Undian',
|
||||
iconColor: const Color(0xFF7B1FA2),
|
||||
onTap: () => context.router.push(DrawRoute()),
|
||||
),
|
||||
SizedBox(width: 12),
|
||||
HomeFeatureCard(
|
||||
icon: Icons.store,
|
||||
title: 'Merchant',
|
||||
iconColor: const Color(0xFF388E3C),
|
||||
onTap: () => context.router.push(MerchantRoute()),
|
||||
),
|
||||
SizedBox(width: 12),
|
||||
HomeFeatureCard(
|
||||
icon: Icons.blur_circular,
|
||||
title: 'Wheels',
|
||||
iconColor: const Color(0xFF388E3C),
|
||||
onTap: () => state.isAuthenticated
|
||||
? context.router.push(FerrisWheelRoute())
|
||||
: context.router.push(OnboardingRoute()),
|
||||
),
|
||||
SizedBox(width: 12),
|
||||
HomeFeatureCard(
|
||||
icon: Icons.storage_outlined,
|
||||
title: 'Mistery Box',
|
||||
iconColor: const Color(0xFF388E3C),
|
||||
onTap: () => state.isAuthenticated
|
||||
? context.router.push(MisteryBoxRoute())
|
||||
: context.router.push(OnboardingRoute()),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -0,0 +1,880 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../common/theme/theme.dart';
|
||||
|
||||
@RoutePage()
|
||||
class MisteryBoxPage extends StatefulWidget {
|
||||
const MisteryBoxPage({super.key});
|
||||
|
||||
@override
|
||||
State<MisteryBoxPage> createState() => _MisteryBoxPageState();
|
||||
}
|
||||
|
||||
class _MisteryBoxPageState extends State<MisteryBoxPage>
|
||||
with TickerProviderStateMixin {
|
||||
int coins = 100;
|
||||
int totalWins = 0;
|
||||
List<Prize> prizes = [
|
||||
Prize(
|
||||
name: 'Voucher Rp 100K',
|
||||
type: 'Voucher',
|
||||
value: 100000,
|
||||
rarity: 'Legendary',
|
||||
chance: 5,
|
||||
icon: '🎁',
|
||||
),
|
||||
Prize(
|
||||
name: 'Cashback 50%',
|
||||
type: 'Cashback',
|
||||
value: 50,
|
||||
rarity: 'Epic',
|
||||
chance: 10,
|
||||
icon: '💰',
|
||||
),
|
||||
Prize(
|
||||
name: '500 Poin Reward',
|
||||
type: 'Poin',
|
||||
value: 500,
|
||||
rarity: 'Rare',
|
||||
chance: 15,
|
||||
icon: '🏆',
|
||||
),
|
||||
Prize(
|
||||
name: 'Voucher Rp 25K',
|
||||
type: 'Voucher',
|
||||
value: 25000,
|
||||
rarity: 'Uncommon',
|
||||
chance: 25,
|
||||
icon: '🎫',
|
||||
),
|
||||
Prize(
|
||||
name: '100 Poin Reward',
|
||||
type: 'Poin',
|
||||
value: 100,
|
||||
rarity: 'Common',
|
||||
chance: 30,
|
||||
icon: '⭐',
|
||||
),
|
||||
Prize(
|
||||
name: 'Diskon 10%',
|
||||
type: 'Diskon',
|
||||
value: 10,
|
||||
rarity: 'Common',
|
||||
chance: 15,
|
||||
icon: '🍕',
|
||||
),
|
||||
];
|
||||
|
||||
bool isOpening = false;
|
||||
late AnimationController _shakeController;
|
||||
late AnimationController _rotateController;
|
||||
late Animation<double> _shakeAnimation;
|
||||
late Animation<double> _rotateAnimation;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_shakeController = AnimationController(
|
||||
duration: const Duration(milliseconds: 600),
|
||||
vsync: this,
|
||||
);
|
||||
_rotateController = AnimationController(
|
||||
duration: const Duration(milliseconds: 600),
|
||||
vsync: this,
|
||||
);
|
||||
|
||||
_shakeAnimation = Tween<double>(begin: 0, end: 15).animate(
|
||||
CurvedAnimation(parent: _shakeController, curve: Curves.elasticIn),
|
||||
);
|
||||
_rotateAnimation = Tween<double>(begin: 0, end: 2 * pi).animate(
|
||||
CurvedAnimation(parent: _rotateController, curve: Curves.easeInOut),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_shakeController.dispose();
|
||||
_rotateController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void openBox() async {
|
||||
if (coins < 10 || isOpening) return;
|
||||
|
||||
setState(() {
|
||||
isOpening = true;
|
||||
coins -= 10;
|
||||
});
|
||||
|
||||
// Animasi shake dan rotate
|
||||
_shakeController.repeat(reverse: true);
|
||||
_rotateController.forward();
|
||||
await Future.delayed(const Duration(milliseconds: 1200));
|
||||
_shakeController.stop();
|
||||
_rotateController.reset();
|
||||
|
||||
// Generate prize
|
||||
Prize prize = _generatePrize();
|
||||
|
||||
setState(() {
|
||||
totalWins++;
|
||||
isOpening = false;
|
||||
});
|
||||
|
||||
// Show dialog
|
||||
if (mounted) {
|
||||
_showPrizeDialog(prize);
|
||||
}
|
||||
}
|
||||
|
||||
Prize _generatePrize() {
|
||||
int random = Random().nextInt(100);
|
||||
int cumulativeChance = 0;
|
||||
|
||||
for (var prize in prizes) {
|
||||
cumulativeChance += prize.chance;
|
||||
if (random < cumulativeChance) {
|
||||
return prize;
|
||||
}
|
||||
}
|
||||
return prizes.last;
|
||||
}
|
||||
|
||||
void _showPrizeDialog(Prize prize) {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (context) => PrizeDialog(prize: prize),
|
||||
);
|
||||
}
|
||||
|
||||
Color _getRarityColor(String rarity) {
|
||||
switch (rarity) {
|
||||
case 'Legendary':
|
||||
return const Color(0xFFFFD700);
|
||||
case 'Epic':
|
||||
return const Color(0xFFB429F9);
|
||||
case 'Rare':
|
||||
return const Color(0xFF3B82F6);
|
||||
case 'Uncommon':
|
||||
return const Color(0xFF10B981);
|
||||
default:
|
||||
return const Color(0xFF9CA3AF);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [
|
||||
AppColor.primary,
|
||||
AppColor.primaryDark,
|
||||
const Color(0xFF4A0000),
|
||||
],
|
||||
),
|
||||
),
|
||||
child: SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
_buildHeader(),
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
_buildMysteryBox(),
|
||||
const SizedBox(height: 40),
|
||||
_buildOpenButton(),
|
||||
const SizedBox(height: 24),
|
||||
_buildPrizeListButton(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHeader() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'🎁 Mystery Box',
|
||||
style: AppStyle.h5.copyWith(
|
||||
color: AppColor.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'Win Amazing Rewards!',
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: AppColor.white.withOpacity(0.8),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
_buildStatCard('🪙', coins.toString()),
|
||||
const SizedBox(width: 8),
|
||||
_buildStatCard('🏆', totalWins.toString()),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatCard(String icon, String value) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.white.withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: AppColor.white.withOpacity(0.3), width: 1.5),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(icon, style: const TextStyle(fontSize: 16)),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
value,
|
||||
style: AppStyle.md.copyWith(
|
||||
color: AppColor.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMysteryBox() {
|
||||
return AnimatedBuilder(
|
||||
animation: Listenable.merge([_shakeAnimation, _rotateAnimation]),
|
||||
builder: (context, child) {
|
||||
return Transform.translate(
|
||||
offset: Offset(
|
||||
sin(_shakeAnimation.value) * 10,
|
||||
cos(_shakeAnimation.value) * 5,
|
||||
),
|
||||
child: Transform.rotate(
|
||||
angle: _rotateAnimation.value,
|
||||
child: Container(
|
||||
width: 200,
|
||||
height: 200,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.white,
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.3),
|
||||
blurRadius: 30,
|
||||
offset: const Offset(0, 15),
|
||||
),
|
||||
BoxShadow(
|
||||
color: AppColor.warning.withOpacity(0.5),
|
||||
blurRadius: 50,
|
||||
spreadRadius: isOpening ? 8 : 0,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
top: 15,
|
||||
left: 15,
|
||||
child: Container(
|
||||
width: 30,
|
||||
height: 30,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.warning.withOpacity(0.2),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 20,
|
||||
right: 20,
|
||||
child: Container(
|
||||
width: 25,
|
||||
height: 25,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.success.withOpacity(0.2),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
),
|
||||
Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Text('🎁', style: TextStyle(fontSize: 70)),
|
||||
const SizedBox(height: 8),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 4,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: AppColor.primaryGradient,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Text(
|
||||
'Mystery Box',
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: AppColor.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (isOpening) ...[
|
||||
const Positioned(
|
||||
top: 25,
|
||||
right: 25,
|
||||
child: Text('✨', style: TextStyle(fontSize: 24)),
|
||||
),
|
||||
const Positioned(
|
||||
bottom: 30,
|
||||
left: 25,
|
||||
child: Text('✨', style: TextStyle(fontSize: 24)),
|
||||
),
|
||||
const Positioned(
|
||||
top: 50,
|
||||
left: 30,
|
||||
child: Text('⭐', style: TextStyle(fontSize: 20)),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildOpenButton() {
|
||||
bool canOpen = coins >= 10 && !isOpening;
|
||||
|
||||
return GestureDetector(
|
||||
onTap: canOpen ? openBox : null,
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: canOpen ? AppColor.white : AppColor.white.withOpacity(0.3),
|
||||
borderRadius: BorderRadius.circular(25),
|
||||
boxShadow: canOpen
|
||||
? [
|
||||
BoxShadow(
|
||||
color: AppColor.white.withOpacity(0.3),
|
||||
blurRadius: 15,
|
||||
offset: const Offset(0, 6),
|
||||
),
|
||||
]
|
||||
: [],
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
isOpening ? Icons.refresh : Icons.card_giftcard,
|
||||
color: canOpen ? AppColor.primary : AppColor.textLight,
|
||||
size: 24,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Text(
|
||||
isOpening ? 'Membuka...' : 'Buka Box',
|
||||
style: AppStyle.lg.copyWith(
|
||||
color: canOpen ? AppColor.primary : AppColor.textLight,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
|
||||
decoration: BoxDecoration(
|
||||
color: canOpen
|
||||
? AppColor.warning.withOpacity(0.2)
|
||||
: AppColor.textLight.withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Text(
|
||||
'10 🪙',
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: canOpen ? AppColor.warning : AppColor.textLight,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPrizeListButton() {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
backgroundColor: Colors.transparent,
|
||||
isScrollControlled: true,
|
||||
builder: (context) => _buildPrizeListModal(),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.white.withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(color: AppColor.white.withOpacity(0.3), width: 2),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.emoji_events, color: AppColor.white, size: 20),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'Lihat Hadiah Tersedia',
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: AppColor.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
const Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
color: AppColor.white,
|
||||
size: 14,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPrizeListModal() {
|
||||
return Container(
|
||||
height: MediaQuery.of(context).size.height * 0.7,
|
||||
decoration: const BoxDecoration(
|
||||
color: AppColor.white,
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
// Handle bar
|
||||
Container(
|
||||
margin: const EdgeInsets.only(top: 12),
|
||||
width: 40,
|
||||
height: 4,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.textLight,
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
),
|
||||
),
|
||||
// Header
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Hadiah Tersedia',
|
||||
style: AppStyle.h5.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColor.textPrimary,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10,
|
||||
vertical: 4,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.primary.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Text(
|
||||
'${prizes.length} Items',
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: AppColor.primary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// Prize Grid
|
||||
Expanded(
|
||||
child: GridView.builder(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 2,
|
||||
childAspectRatio: 0.70,
|
||||
crossAxisSpacing: 12,
|
||||
mainAxisSpacing: 12,
|
||||
),
|
||||
itemCount: prizes.length,
|
||||
itemBuilder: (context, index) {
|
||||
final prize = prizes[index];
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [
|
||||
_getRarityColor(prize.rarity).withOpacity(0.1),
|
||||
_getRarityColor(prize.rarity).withOpacity(0.05),
|
||||
],
|
||||
),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(
|
||||
color: _getRarityColor(prize.rarity).withOpacity(0.4),
|
||||
width: 2,
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(prize.icon, style: const TextStyle(fontSize: 40)),
|
||||
const SizedBox(height: 12),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10,
|
||||
vertical: 4,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: _getRarityColor(prize.rarity),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Text(
|
||||
prize.rarity,
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: AppColor.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 9,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
prize.name,
|
||||
style: AppStyle.sm.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColor.textPrimary,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 3,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.primary.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Text(
|
||||
prize.type,
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: AppColor.primary,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 10,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 3,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: _getRarityColor(
|
||||
prize.rarity,
|
||||
).withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.percent,
|
||||
size: 10,
|
||||
color: _getRarityColor(prize.rarity),
|
||||
),
|
||||
const SizedBox(width: 2),
|
||||
Text(
|
||||
'${prize.chance}%',
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: _getRarityColor(prize.rarity),
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 10,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class PrizeDialog extends StatefulWidget {
|
||||
final Prize prize;
|
||||
|
||||
const PrizeDialog({Key? key, required this.prize}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<PrizeDialog> createState() => _PrizeDialogState();
|
||||
}
|
||||
|
||||
class _PrizeDialogState extends State<PrizeDialog>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late AnimationController _controller;
|
||||
late Animation<double> _scaleAnimation;
|
||||
late Animation<double> _fadeAnimation;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = AnimationController(
|
||||
duration: const Duration(milliseconds: 500),
|
||||
vsync: this,
|
||||
);
|
||||
|
||||
_scaleAnimation = Tween<double>(
|
||||
begin: 0.5,
|
||||
end: 1.0,
|
||||
).animate(CurvedAnimation(parent: _controller, curve: Curves.elasticOut));
|
||||
|
||||
_fadeAnimation = Tween<double>(
|
||||
begin: 0.0,
|
||||
end: 1.0,
|
||||
).animate(CurvedAnimation(parent: _controller, curve: Curves.easeIn));
|
||||
|
||||
_controller.forward();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Color _getRarityColor(String rarity) {
|
||||
switch (rarity) {
|
||||
case 'Legendary':
|
||||
return const Color(0xFFFFD700);
|
||||
case 'Epic':
|
||||
return const Color(0xFFB429F9);
|
||||
case 'Rare':
|
||||
return const Color(0xFF3B82F6);
|
||||
case 'Uncommon':
|
||||
return const Color(0xFF10B981);
|
||||
default:
|
||||
return const Color(0xFF9CA3AF);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FadeTransition(
|
||||
opacity: _fadeAnimation,
|
||||
child: Dialog(
|
||||
backgroundColor: Colors.transparent,
|
||||
child: ScaleTransition(
|
||||
scale: _scaleAnimation,
|
||||
child: Container(
|
||||
constraints: const BoxConstraints(maxWidth: 320),
|
||||
padding: const EdgeInsets.all(24),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.white,
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
border: Border.all(
|
||||
color: _getRarityColor(widget.prize.rarity),
|
||||
width: 4,
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: _getRarityColor(widget.prize.rarity).withOpacity(0.5),
|
||||
blurRadius: 30,
|
||||
offset: const Offset(0, 10),
|
||||
spreadRadius: 2,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text('🎉', style: const TextStyle(fontSize: 50)),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
'Selamat!',
|
||||
style: AppStyle.h4.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColor.textPrimary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Kamu mendapatkan',
|
||||
style: AppStyle.sm.copyWith(color: AppColor.textSecondary),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(widget.prize.icon, style: const TextStyle(fontSize: 60)),
|
||||
const SizedBox(height: 12),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 14,
|
||||
vertical: 6,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: _getRarityColor(widget.prize.rarity),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: _getRarityColor(
|
||||
widget.prize.rarity,
|
||||
).withOpacity(0.4),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Text(
|
||||
widget.prize.rarity.toUpperCase(),
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: AppColor.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
letterSpacing: 1.2,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
widget.prize.name,
|
||||
style: AppStyle.h5.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColor.textPrimary,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 4,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.primary.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Text(
|
||||
widget.prize.type,
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: AppColor.primary,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
GestureDetector(
|
||||
onTap: () => Navigator.pop(context),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: AppColor.primaryGradient,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppColor.primary.withOpacity(0.4),
|
||||
blurRadius: 12,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Text(
|
||||
'Tutup',
|
||||
style: AppStyle.md.copyWith(
|
||||
color: AppColor.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Prize {
|
||||
final String name;
|
||||
final String type;
|
||||
final int value;
|
||||
final String rarity;
|
||||
final int chance;
|
||||
final String icon;
|
||||
|
||||
Prize({
|
||||
required this.name,
|
||||
required this.type,
|
||||
required this.value,
|
||||
required this.rarity,
|
||||
required this.chance,
|
||||
required this.icon,
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user