Ferish Wheel and Music
This commit is contained in:
@@ -6,12 +6,30 @@ import 'package:flutter_spinkit/flutter_spinkit.dart';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import '../../../../application/auth/auth_bloc.dart';
|
||||
import '../../../../application/game/game_price_loader/game_prize_loader_bloc.dart';
|
||||
import '../../../../application/game/ferris_wheel_loader/ferris_wheel_loader_bloc.dart';
|
||||
import '../../../../common/theme/theme.dart';
|
||||
import '../../../../common/painter/wheel_painter.dart';
|
||||
import '../../../../injection.dart';
|
||||
import '../../../../domain/game/game.dart';
|
||||
import 'data/model.dart';
|
||||
|
||||
// Simple models for UI state
|
||||
class PrizeHistory {
|
||||
final String prize;
|
||||
final DateTime dateTime;
|
||||
final int value;
|
||||
final Color color;
|
||||
final IconData icon;
|
||||
final String? gamePrizeId;
|
||||
|
||||
PrizeHistory({
|
||||
required this.prize,
|
||||
required this.dateTime,
|
||||
required this.value,
|
||||
required this.color,
|
||||
required this.icon,
|
||||
this.gamePrizeId,
|
||||
});
|
||||
}
|
||||
|
||||
@RoutePage()
|
||||
class FerrisWheelPage extends StatefulWidget implements AutoRouteWrapper {
|
||||
@@ -22,12 +40,9 @@ class FerrisWheelPage extends StatefulWidget implements AutoRouteWrapper {
|
||||
|
||||
@override
|
||||
Widget wrappedRoute(BuildContext context) => BlocProvider(
|
||||
create: (context) => getIt<GamePrizeLoaderBloc>()
|
||||
..add(
|
||||
const GamePrizeLoaderEvent.fetched(
|
||||
'28d5aed3-4c1b-4b7b-bad5-67cdd4919dc2',
|
||||
),
|
||||
),
|
||||
create: (context) =>
|
||||
getIt<FerrisWheelLoaderBloc>()
|
||||
..add(const FerrisWheelLoaderEvent.fetched()),
|
||||
child: this,
|
||||
);
|
||||
}
|
||||
@@ -84,12 +99,10 @@ class _FerrisWheelPageState extends State<FerrisWheelPage>
|
||||
|
||||
void _initializeAudio() async {
|
||||
try {
|
||||
await _bgmPlayer.setSource(
|
||||
AssetSource('audio/carnival/bgm/carnival_main_theme.mp3'),
|
||||
);
|
||||
await _bgmPlayer.setSource(AssetSource('audio/carnaval_main_theme.mp3'));
|
||||
await _bgmPlayer.setReleaseMode(ReleaseMode.loop);
|
||||
await _bgmPlayer.setVolume(0.5);
|
||||
if (_isMusicEnabled) _bgmPlayer.resume();
|
||||
if (_isMusicEnabled) await _bgmPlayer.resume();
|
||||
} catch (e) {
|
||||
print('Error initializing audio: $e');
|
||||
}
|
||||
@@ -107,23 +120,10 @@ class _FerrisWheelPageState extends State<FerrisWheelPage>
|
||||
}
|
||||
}
|
||||
|
||||
void _playButtonTap() =>
|
||||
_playSound('audio/carnival/sfx/button_tap.mp3', volume: 0.3);
|
||||
void _playTokenSound() =>
|
||||
_playSound('audio/carnival/sfx/token_sound.mp3', volume: 0.5);
|
||||
void _playWheelSpin() =>
|
||||
_playSound('audio/carnival/sfx/wheel_spin.mp3', volume: 0.8);
|
||||
|
||||
void _playWinSound(GamePrize prize) {
|
||||
int prizeValue = prize.metadata['value'] ?? prize.weight;
|
||||
if (prizeValue >= 1000000) {
|
||||
_playSound('audio/carnival/sfx/win_big.mp3', volume: 0.9);
|
||||
} else if (prizeValue >= 5000) {
|
||||
_playSound('audio/carnival/sfx/win_medium.mp3', volume: 0.8);
|
||||
} else {
|
||||
_playSound('audio/carnival/sfx/win_small.mp3', volume: 0.7);
|
||||
}
|
||||
}
|
||||
void _playButtonTap() => _playSound('audio/button_tap.mp3', volume: 0.3);
|
||||
void _playTokenSound() => _playSound('audio/token_sound.mp3', volume: 0.5);
|
||||
void _playWheelSpin() => _playSound('audio/wheel_spin.mp3', volume: 0.8);
|
||||
void _playWinSound() => _playSound('audio/win_medium.mp3', volume: 0.8);
|
||||
|
||||
void _toggleSound() {
|
||||
setState(() => _isSoundEnabled = !_isSoundEnabled);
|
||||
@@ -170,66 +170,48 @@ class _FerrisWheelPageState extends State<FerrisWheelPage>
|
||||
int _selectPrizeWithWeight() {
|
||||
if (gamePrizes.isEmpty) return 0;
|
||||
|
||||
final availablePrizes = gamePrizes
|
||||
.asMap()
|
||||
.entries
|
||||
.where((entry) => entry.value.stock > 0)
|
||||
.toList();
|
||||
// Jika GamePrize memiliki properti weight, gunakan ini:
|
||||
// List<int> weights = gamePrizes.map((prize) => prize.weight ?? 1).toList();
|
||||
|
||||
if (availablePrizes.isEmpty) {
|
||||
return math.Random().nextInt(gamePrizes.length);
|
||||
// Untuk sementara, gunakan bobot default yang berbeda
|
||||
// Hadiah langka (index awal) memiliki bobot lebih kecil
|
||||
List<int> weights = [];
|
||||
for (int i = 0; i < gamePrizes.length; i++) {
|
||||
// Bobot menurun: hadiah pertama langka, hadiah terakhir mudah didapat
|
||||
int weight = gamePrizes.length - i;
|
||||
weights.add(weight);
|
||||
}
|
||||
|
||||
int totalWeight = availablePrizes
|
||||
.map((entry) => entry.value.weight)
|
||||
.reduce((a, b) => a + b);
|
||||
// Hitung total bobot
|
||||
int totalWeight = weights.reduce((a, b) => a + b);
|
||||
|
||||
if (totalWeight <= 0) {
|
||||
final randomEntry =
|
||||
availablePrizes[math.Random().nextInt(availablePrizes.length)];
|
||||
return randomEntry.key;
|
||||
}
|
||||
// Generate random number
|
||||
int randomNum = math.Random().nextInt(totalWeight);
|
||||
|
||||
int randomWeight = math.Random().nextInt(totalWeight);
|
||||
// Tentukan hadiah berdasarkan bobot
|
||||
int currentWeight = 0;
|
||||
|
||||
for (final entry in availablePrizes) {
|
||||
currentWeight += entry.value.weight;
|
||||
if (randomWeight < currentWeight) {
|
||||
return entry.key;
|
||||
for (int i = 0; i < gamePrizes.length; i++) {
|
||||
currentWeight += weights[i];
|
||||
if (randomNum < currentWeight) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return availablePrizes.last.key;
|
||||
// Fallback (seharusnya tidak pernah terjadi)
|
||||
return gamePrizes.length - 1;
|
||||
}
|
||||
|
||||
Color _getPrizeColor(GamePrize prize, int index) {
|
||||
final colorName = prize.metadata['color'] as String?;
|
||||
switch (colorName?.toLowerCase()) {
|
||||
case 'primary':
|
||||
return AppColor.primary;
|
||||
case 'info':
|
||||
return AppColor.info;
|
||||
case 'warning':
|
||||
return AppColor.warning;
|
||||
case 'success':
|
||||
return AppColor.success;
|
||||
case 'error':
|
||||
return AppColor.error;
|
||||
case 'secondary':
|
||||
return AppColor.secondary;
|
||||
default:
|
||||
final colors = [
|
||||
AppColor.primary,
|
||||
AppColor.info,
|
||||
AppColor.warning,
|
||||
AppColor.success,
|
||||
AppColor.primaryDark,
|
||||
AppColor.secondary,
|
||||
AppColor.error,
|
||||
];
|
||||
return colors[index % colors.length];
|
||||
}
|
||||
final colors = [
|
||||
AppColor.primary,
|
||||
AppColor.info,
|
||||
AppColor.warning,
|
||||
AppColor.success,
|
||||
AppColor.primaryDark,
|
||||
AppColor.secondary,
|
||||
AppColor.error,
|
||||
];
|
||||
return colors[index % colors.length];
|
||||
}
|
||||
|
||||
void _spinWheel() {
|
||||
@@ -245,23 +227,25 @@ class _FerrisWheelPageState extends State<FerrisWheelPage>
|
||||
});
|
||||
|
||||
_idleRotationController.stop();
|
||||
_idleRotationController.reset();
|
||||
|
||||
int targetSection = _selectPrizeWithWeight();
|
||||
int selectedPrizeIndex = _selectPrizeWithWeight();
|
||||
double sectionAngle = (2 * math.pi) / gamePrizes.length;
|
||||
double targetAngle = (targetSection * sectionAngle) + (sectionAngle / 2);
|
||||
double baseRotations = 4 + math.Random().nextDouble() * 3;
|
||||
double currentIdleRotation = _idleRotationAnimation?.value ?? 0.0;
|
||||
double finalRotation =
|
||||
currentRotation +
|
||||
currentIdleRotation +
|
||||
(baseRotations * 2 * math.pi) +
|
||||
targetAngle;
|
||||
double currentPos = currentRotation;
|
||||
|
||||
_spinAnimation =
|
||||
Tween<double>(
|
||||
begin: currentRotation + currentIdleRotation,
|
||||
end: finalRotation,
|
||||
).animate(
|
||||
// TAMBAH offset ke tengah section (bukan garis)
|
||||
double targetForSelectedSection =
|
||||
-(selectedPrizeIndex * sectionAngle) - (sectionAngle / 2);
|
||||
|
||||
double spins = 6 * 2 * math.pi;
|
||||
double finalRotation = currentPos + spins + targetForSelectedSection;
|
||||
|
||||
while (finalRotation <= currentPos + spins) {
|
||||
finalRotation += 2 * math.pi;
|
||||
}
|
||||
|
||||
_spinAnimation = Tween<double>(begin: currentPos, end: finalRotation)
|
||||
.animate(
|
||||
CurvedAnimation(
|
||||
parent: _rotationController,
|
||||
curve: Curves.easeOutCubic,
|
||||
@@ -270,27 +254,20 @@ class _FerrisWheelPageState extends State<FerrisWheelPage>
|
||||
|
||||
_rotationController.reset();
|
||||
_rotationController.animateTo(1.0).then((_) {
|
||||
final wonPrize = gamePrizes[targetSection];
|
||||
_playWinSound(wonPrize);
|
||||
final wonPrize = gamePrizes[selectedPrizeIndex];
|
||||
_playWinSound();
|
||||
|
||||
setState(() {
|
||||
currentRotation = finalRotation;
|
||||
isSpinning = false;
|
||||
resultText = 'Selamat! Anda mendapat ${wonPrize.name}!';
|
||||
|
||||
if (wonPrize.stock > 0) {
|
||||
gamePrizes[targetSection] = wonPrize.copyWith(
|
||||
stock: wonPrize.stock - 1,
|
||||
);
|
||||
}
|
||||
|
||||
prizeHistory.insert(
|
||||
0,
|
||||
PrizeHistory(
|
||||
prize: wonPrize.name,
|
||||
dateTime: DateTime.now(),
|
||||
value: wonPrize.metadata['value'] ?? wonPrize.weight,
|
||||
color: _getPrizeColor(wonPrize, targetSection),
|
||||
value: 100,
|
||||
color: _getPrizeColor(wonPrize, selectedPrizeIndex),
|
||||
icon: Icons.card_giftcard,
|
||||
gamePrizeId: wonPrize.id,
|
||||
),
|
||||
@@ -298,7 +275,6 @@ class _FerrisWheelPageState extends State<FerrisWheelPage>
|
||||
});
|
||||
|
||||
_showWinDialog(wonPrize);
|
||||
_idleRotationController.reset();
|
||||
_idleRotationController.repeat();
|
||||
});
|
||||
}
|
||||
@@ -363,15 +339,6 @@ class _FerrisWheelPageState extends State<FerrisWheelPage>
|
||||
color: AppColor.textWhite,
|
||||
),
|
||||
),
|
||||
if (wonPrize.stock >= 0) ...[
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Stok tersisa: ${wonPrize.stock}',
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: AppColor.textWhite.withOpacity(0.8),
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 20),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
@@ -406,7 +373,7 @@ class _FerrisWheelPageState extends State<FerrisWheelPage>
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<GamePrizeLoaderBloc, GamePrizeLoaderState>(
|
||||
return BlocBuilder<FerrisWheelLoaderBloc, FerrisWheelLoaderState>(
|
||||
builder: (context, state) {
|
||||
if (state.isFetching) {
|
||||
return Scaffold(
|
||||
@@ -425,10 +392,10 @@ class _FerrisWheelPageState extends State<FerrisWheelPage>
|
||||
);
|
||||
}
|
||||
|
||||
if (gamePrizes.isEmpty && state.gamePrize.isNotEmpty) {
|
||||
if (gamePrizes.isEmpty && state.ferrisWheel.prizes.isNotEmpty) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
setState(() {
|
||||
gamePrizes = state.gamePrize;
|
||||
gamePrizes = state.ferrisWheel.prizes;
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -445,100 +412,9 @@ class _FerrisWheelPageState extends State<FerrisWheelPage>
|
||||
child: SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
// Header
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
_playButtonTap();
|
||||
context.router.back();
|
||||
},
|
||||
icon: Icon(
|
||||
Icons.close,
|
||||
color: AppColor.textWhite,
|
||||
size: 28,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'SPIN & WIN',
|
||||
style: AppStyle.h6.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColor.textWhite,
|
||||
letterSpacing: 2,
|
||||
),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: _toggleMusic,
|
||||
icon: Icon(
|
||||
_isMusicEnabled
|
||||
? Icons.volume_up
|
||||
: Icons.volume_off,
|
||||
color: AppColor.textWhite,
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: _toggleSound,
|
||||
icon: Icon(
|
||||
_isSoundEnabled
|
||||
? Icons.graphic_eq
|
||||
: Icons.volume_mute,
|
||||
color: AppColor.textWhite,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Tab Selector
|
||||
Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 20),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.white.withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(25),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
for (int i = 0; i < 3; i++)
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
_playButtonTap();
|
||||
setState(() => currentTabIndex = i);
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 12,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: currentTabIndex == i
|
||||
? AppColor.white
|
||||
: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(25),
|
||||
),
|
||||
child: Text(
|
||||
['Spin Wheel', 'Daftar Hadiah', 'Riwayat'][i],
|
||||
textAlign: TextAlign.center,
|
||||
style: AppStyle.md.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: currentTabIndex == i
|
||||
? AppColor.primary
|
||||
: AppColor.textWhite,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
_buildHeader(),
|
||||
_buildTabSelector(),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Content Area
|
||||
Expanded(
|
||||
child: currentTabIndex == 0
|
||||
? _buildMainContent()
|
||||
@@ -555,254 +431,256 @@ class _FerrisWheelPageState extends State<FerrisWheelPage>
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHeader() {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
_playButtonTap();
|
||||
context.router.back();
|
||||
},
|
||||
icon: Icon(Icons.close, color: AppColor.textWhite, size: 28),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'SPIN & WIN',
|
||||
style: AppStyle.h6.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColor.textWhite,
|
||||
letterSpacing: 2,
|
||||
),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: _toggleMusic,
|
||||
icon: Icon(
|
||||
_isMusicEnabled ? Icons.volume_up : Icons.volume_off,
|
||||
color: AppColor.textWhite,
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: _toggleSound,
|
||||
icon: Icon(
|
||||
_isSoundEnabled ? Icons.graphic_eq : Icons.volume_mute,
|
||||
color: AppColor.textWhite,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTabSelector() {
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 20),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.white.withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(25),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
for (int i = 0; i < 3; i++)
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
_playButtonTap();
|
||||
setState(() => currentTabIndex = i);
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: currentTabIndex == i
|
||||
? AppColor.white
|
||||
: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(25),
|
||||
),
|
||||
child: Text(
|
||||
['Spin Wheel', 'Daftar Hadiah', 'Riwayat'][i],
|
||||
textAlign: TextAlign.center,
|
||||
style: AppStyle.md.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: currentTabIndex == i
|
||||
? AppColor.primary
|
||||
: AppColor.textWhite,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMainContent() {
|
||||
return Column(
|
||||
children: [
|
||||
// User Info Card
|
||||
Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.white,
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppColor.black.withOpacity(0.1),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
BlocBuilder<AuthBloc, AuthState>(
|
||||
builder: (context, auth) {
|
||||
return Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.primary,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
'G',
|
||||
style: AppStyle.lg.copyWith(
|
||||
color: AppColor.textWhite,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
auth.user.name,
|
||||
style: AppStyle.lg.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColor.textPrimary,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
auth.user.phoneNumber,
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: AppColor.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 20,
|
||||
height: 20,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.warning,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
Icons.circle,
|
||||
size: 12,
|
||||
color: AppColor.textWhite,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'Token: $tokens',
|
||||
style: AppStyle.lg.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColor.textPrimary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
_buildUserInfoCard(),
|
||||
Text(
|
||||
resultText,
|
||||
style: AppStyle.md.copyWith(color: AppColor.textWhite),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Wheel Section
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: gamePrizes.isEmpty
|
||||
? SpinKitFadingCircle(color: AppColor.textWhite, size: 36)
|
||||
: Stack(
|
||||
alignment: Alignment.center,
|
||||
: _buildWheelSection(),
|
||||
),
|
||||
),
|
||||
_buildBottomBanner(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildUserInfoCard() {
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.white,
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppColor.black.withOpacity(0.1),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
BlocBuilder<AuthBloc, AuthState>(
|
||||
builder: (context, auth) {
|
||||
return Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.primary,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
'G',
|
||||
style: AppStyle.lg.copyWith(
|
||||
color: AppColor.textWhite,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Glow Effect
|
||||
AnimatedBuilder(
|
||||
animation: _glowController,
|
||||
builder: (context, child) => Container(
|
||||
width: 340,
|
||||
height: 340,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppColor.white.withOpacity(
|
||||
0.3 + 0.2 * _glowController.value,
|
||||
),
|
||||
blurRadius: 40,
|
||||
spreadRadius: 10,
|
||||
),
|
||||
],
|
||||
),
|
||||
Text(
|
||||
auth.user.name,
|
||||
style: AppStyle.lg.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColor.textPrimary,
|
||||
),
|
||||
),
|
||||
|
||||
// Spinning Wheel
|
||||
AnimatedBuilder(
|
||||
animation: isSpinning
|
||||
? _rotationController
|
||||
: _idleRotationController,
|
||||
builder: (context, child) {
|
||||
double rotationAngle = isSpinning
|
||||
? (_spinAnimation?.value ?? currentRotation)
|
||||
: (currentRotation +
|
||||
(_idleRotationAnimation?.value ?? 0.0));
|
||||
|
||||
return Transform.rotate(
|
||||
angle: rotationAngle,
|
||||
child: CustomPaint(
|
||||
size: const Size(320, 320),
|
||||
painter: WheelPainter(
|
||||
gamePrizes: gamePrizes,
|
||||
getPrizeColor: _getPrizeColor,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
// Spin Button
|
||||
AnimatedBuilder(
|
||||
animation:
|
||||
_pulseAnimation ??
|
||||
const AlwaysStoppedAnimation(1.0),
|
||||
builder: (context, child) => Transform.scale(
|
||||
scale: _pulseAnimation?.value ?? 1.0,
|
||||
child: Container(
|
||||
width: 100,
|
||||
height: 100,
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [AppColor.warning, AppColor.warning],
|
||||
),
|
||||
shape: BoxShape.circle,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppColor.black.withOpacity(0.3),
|
||||
blurRadius: 15,
|
||||
offset: const Offset(0, 6),
|
||||
),
|
||||
BoxShadow(
|
||||
color: AppColor.warning.withOpacity(0.5),
|
||||
blurRadius: 20,
|
||||
spreadRadius:
|
||||
(_pulseAnimation?.value ?? 1.0) * 5,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(50),
|
||||
onTap: _spinWheel,
|
||||
child: Center(
|
||||
child: Text(
|
||||
'SPIN',
|
||||
style: AppStyle.lg.copyWith(
|
||||
color: AppColor.textWhite,
|
||||
fontWeight: FontWeight.bold,
|
||||
letterSpacing: 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Pointer
|
||||
Positioned(
|
||||
top: 30,
|
||||
child: Container(
|
||||
width: 0,
|
||||
height: 0,
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
left: BorderSide(
|
||||
width: 15,
|
||||
color: Colors.transparent,
|
||||
),
|
||||
right: BorderSide(
|
||||
width: 15,
|
||||
color: Colors.transparent,
|
||||
),
|
||||
bottom: BorderSide(
|
||||
width: 30,
|
||||
color: AppColor.error,
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
auth.user.phoneNumber,
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: AppColor.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 20,
|
||||
height: 20,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.warning,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(Icons.circle, size: 12, color: AppColor.textWhite),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'Token: $tokens',
|
||||
style: AppStyle.lg.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColor.textPrimary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildWheelSection() {
|
||||
return Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
// Glow Effect
|
||||
AnimatedBuilder(
|
||||
animation: _glowController,
|
||||
builder: (context, child) => Container(
|
||||
width: 340,
|
||||
height: 340,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppColor.white.withOpacity(
|
||||
0.3 + 0.2 * _glowController.value,
|
||||
),
|
||||
blurRadius: 40,
|
||||
spreadRadius: 10,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
// Spinning Wheel
|
||||
AnimatedBuilder(
|
||||
animation: isSpinning ? _rotationController : _idleRotationController,
|
||||
builder: (context, child) {
|
||||
double rotationAngle = isSpinning
|
||||
? (_spinAnimation?.value ?? currentRotation)
|
||||
: (currentRotation + (_idleRotationAnimation?.value ?? 0.0));
|
||||
|
||||
// Bottom Banner
|
||||
Container(
|
||||
margin: const EdgeInsets.all(20),
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [AppColor.warning, AppColor.warning],
|
||||
),
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
),
|
||||
child: Text(
|
||||
'Spin 30x lagi buat mainin spesial spin',
|
||||
textAlign: TextAlign.center,
|
||||
style: AppStyle.lg.copyWith(
|
||||
color: AppColor.textWhite,
|
||||
fontWeight: FontWeight.bold,
|
||||
return Transform.rotate(
|
||||
angle: rotationAngle,
|
||||
child: CustomPaint(
|
||||
size: const Size(320, 320),
|
||||
painter: WheelPainter(
|
||||
gamePrizes: gamePrizes,
|
||||
getPrizeColor: _getPrizeColor,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
// Spin Button
|
||||
_buildSpinButton(),
|
||||
// Pointer
|
||||
Positioned(
|
||||
top: 30,
|
||||
child: Container(
|
||||
width: 0,
|
||||
height: 0,
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
left: BorderSide(width: 15, color: Colors.transparent),
|
||||
right: BorderSide(width: 15, color: Colors.transparent),
|
||||
bottom: BorderSide(width: 30, color: AppColor.error),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -810,6 +688,73 @@ class _FerrisWheelPageState extends State<FerrisWheelPage>
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSpinButton() {
|
||||
return AnimatedBuilder(
|
||||
animation: _pulseAnimation ?? const AlwaysStoppedAnimation(1.0),
|
||||
builder: (context, child) => Transform.scale(
|
||||
scale: _pulseAnimation?.value ?? 1.0,
|
||||
child: Container(
|
||||
width: 100,
|
||||
height: 100,
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [AppColor.warning, AppColor.warning],
|
||||
),
|
||||
shape: BoxShape.circle,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppColor.black.withOpacity(0.3),
|
||||
blurRadius: 15,
|
||||
offset: const Offset(0, 6),
|
||||
),
|
||||
BoxShadow(
|
||||
color: AppColor.warning.withOpacity(0.5),
|
||||
blurRadius: 20,
|
||||
spreadRadius: (_pulseAnimation?.value ?? 1.0) * 5,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(50),
|
||||
onTap: _spinWheel,
|
||||
child: Center(
|
||||
child: Text(
|
||||
'SPIN',
|
||||
style: AppStyle.lg.copyWith(
|
||||
color: AppColor.textWhite,
|
||||
fontWeight: FontWeight.bold,
|
||||
letterSpacing: 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBottomBanner() {
|
||||
return Container(
|
||||
margin: const EdgeInsets.all(20),
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(colors: [AppColor.warning, AppColor.warning]),
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
),
|
||||
child: Text(
|
||||
'Spin 30x lagi buat mainin spesial spin',
|
||||
textAlign: TextAlign.center,
|
||||
style: AppStyle.lg.copyWith(
|
||||
color: AppColor.textWhite,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPrizeListContent() {
|
||||
return Container(
|
||||
margin: const EdgeInsets.all(20),
|
||||
@@ -892,17 +837,15 @@ class _FerrisWheelPageState extends State<FerrisWheelPage>
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'Nilai: ${prize.metadata['value'] ?? prize.weight}',
|
||||
'ID: ${prize.id}',
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: AppColor.textSecondary,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'Stok: ${prize.stock}/${prize.maxStock}',
|
||||
'Game ID: ${prize.gameId}',
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: prize.stock > 0
|
||||
? AppColor.success
|
||||
: AppColor.error,
|
||||
color: AppColor.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -921,7 +864,7 @@ class _FerrisWheelPageState extends State<FerrisWheelPage>
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
),
|
||||
child: Text(
|
||||
'Weight: ${prize.weight}',
|
||||
'Hadiah',
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: _getPrizeColor(prize, index),
|
||||
fontWeight: FontWeight.bold,
|
||||
|
||||
Reference in New Issue
Block a user