EnakPoint & EnakCoin wallet, PIN, outlet, order, community, reward
- Wallet EnakPoint & EnakCoin: beranda, riwayat, saldo kedaluwarsa, kode bayar, tukar EnakCoin, transfer - PIN (buat, ganti, lupa), outlet, riwayat order, game spin - Halaman community, webview, coming soon, dan ikon menu beranda baru - Redesign Reward Saya: kartu tiket, filter chip, detail kode + QR - Hapus customer point loader, menu, dan checkout lama - Base URL ke enaklo-pos-api.altru.id Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
5954510b93
commit
f5e3075205
@@ -5,12 +5,20 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_spinkit/flutter_spinkit.dart';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:dartz/dartz.dart' hide State;
|
||||
|
||||
import '../../../../application/auth/auth_bloc.dart';
|
||||
import '../../../../application/game/ferris_wheel_loader/ferris_wheel_loader_bloc.dart';
|
||||
import '../../../../application/game/game_spin/game_spin_bloc.dart';
|
||||
import '../../../../application/wallet/wallet_loader/wallet_loader_bloc.dart';
|
||||
import '../../../../common/extension/extension.dart';
|
||||
import '../../../../common/theme/theme.dart';
|
||||
import '../../../../common/painter/wheel_painter.dart';
|
||||
import '../../../../injection.dart';
|
||||
import '../../../../domain/game/game.dart';
|
||||
import '../../../../domain/wallet/wallet.dart';
|
||||
import '../../../components/toast/flushbar.dart';
|
||||
import '../../wallet/wallet_ui.dart';
|
||||
|
||||
// Simple models for UI state
|
||||
class PrizeHistory {
|
||||
@@ -39,10 +47,15 @@ class FerrisWheelPage extends StatefulWidget implements AutoRouteWrapper {
|
||||
State<FerrisWheelPage> createState() => _FerrisWheelPageState();
|
||||
|
||||
@override
|
||||
Widget wrappedRoute(BuildContext context) => BlocProvider(
|
||||
create: (context) =>
|
||||
getIt<FerrisWheelLoaderBloc>()
|
||||
..add(const FerrisWheelLoaderEvent.fetched()),
|
||||
Widget wrappedRoute(BuildContext context) => MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider(
|
||||
create: (context) =>
|
||||
getIt<FerrisWheelLoaderBloc>()
|
||||
..add(const FerrisWheelLoaderEvent.fetched()),
|
||||
),
|
||||
BlocProvider(create: (context) => getIt<GameSpinBloc>()),
|
||||
],
|
||||
child: this,
|
||||
);
|
||||
}
|
||||
@@ -69,7 +82,6 @@ class _FerrisWheelPageState extends State<FerrisWheelPage>
|
||||
bool _isMusicEnabled = true;
|
||||
|
||||
// Game State
|
||||
int tokens = 3;
|
||||
bool isSpinning = false;
|
||||
String resultText = 'Belum pernah spin';
|
||||
double currentRotation = 0.0;
|
||||
@@ -84,6 +96,7 @@ class _FerrisWheelPageState extends State<FerrisWheelPage>
|
||||
super.initState();
|
||||
_initializeAudio();
|
||||
_initializeAnimations();
|
||||
context.read<WalletLoaderBloc>().add(const WalletLoaderEvent.fetched());
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -167,39 +180,11 @@ class _FerrisWheelPageState extends State<FerrisWheelPage>
|
||||
_idleRotationController.repeat();
|
||||
}
|
||||
|
||||
int _selectPrizeWithWeight() {
|
||||
if (gamePrizes.isEmpty) return 0;
|
||||
int get _coinBalance =>
|
||||
context.read<WalletLoaderBloc>().state.summary.coinBalance;
|
||||
|
||||
// Jika GamePrize memiliki properti weight, gunakan ini:
|
||||
// List<int> weights = gamePrizes.map((prize) => prize.weight ?? 1).toList();
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// Hitung total bobot
|
||||
int totalWeight = weights.reduce((a, b) => a + b);
|
||||
|
||||
// Generate random number
|
||||
int randomNum = math.Random().nextInt(totalWeight);
|
||||
|
||||
// Tentukan hadiah berdasarkan bobot
|
||||
int currentWeight = 0;
|
||||
for (int i = 0; i < gamePrizes.length; i++) {
|
||||
currentWeight += weights[i];
|
||||
if (randomNum < currentWeight) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback (seharusnya tidak pernah terjadi)
|
||||
return gamePrizes.length - 1;
|
||||
}
|
||||
int get _coinCost =>
|
||||
context.read<FerrisWheelLoaderBloc>().state.ferrisWheel.coinCost;
|
||||
|
||||
Color _getPrizeColor(GamePrize prize, int index) {
|
||||
final colors = [
|
||||
@@ -214,22 +199,70 @@ class _FerrisWheelPageState extends State<FerrisWheelPage>
|
||||
return colors[index % colors.length];
|
||||
}
|
||||
|
||||
/// Hadiah ditentukan server; roda hanya dianimasikan ke hadiah itu.
|
||||
void _spinWheel() {
|
||||
if (isSpinning || tokens <= 0 || gamePrizes.isEmpty) return;
|
||||
if (isSpinning || gamePrizes.isEmpty) return;
|
||||
|
||||
final game = context.read<FerrisWheelLoaderBloc>().state.ferrisWheel;
|
||||
if (_coinBalance < _coinCost) {
|
||||
AppFlushbar.showError(
|
||||
context,
|
||||
'EnakCoin kamu kurang. Butuh $_coinCost EnakCoin untuk main.',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
_playWheelSpin();
|
||||
_playTokenSound();
|
||||
|
||||
setState(() {
|
||||
isSpinning = true;
|
||||
tokens--;
|
||||
resultText = 'Sedang berputar...';
|
||||
});
|
||||
|
||||
context.read<GameSpinBloc>().add(GameSpinEvent.spun(game.id));
|
||||
}
|
||||
|
||||
void _onSpinResult(Either<GameFailure, SpinResult> either) {
|
||||
either.fold(
|
||||
(f) {
|
||||
final message = f.map(
|
||||
serverError: (value) => value.failure.toStringFormatted(context),
|
||||
unexpectedError: (_) => 'Terjadi kesalahan, coba lagi',
|
||||
dynamicErrorMessage: (value) => value.erroMessage,
|
||||
);
|
||||
setState(() {
|
||||
isSpinning = false;
|
||||
resultText = 'Belum berhasil, silakan coba lagi';
|
||||
});
|
||||
AppFlushbar.showError(context, message);
|
||||
},
|
||||
(result) {
|
||||
context.read<WalletLoaderBloc>().add(
|
||||
WalletLoaderEvent.coinBalanceUpdated(result.coinsRemaining),
|
||||
);
|
||||
|
||||
var index = gamePrizes.indexWhere((p) => p.id == result.prizeId);
|
||||
if (index < 0) {
|
||||
index = gamePrizes.indexWhere((p) => p.name == result.prizeName);
|
||||
}
|
||||
_animateToPrize(
|
||||
index < 0 ? 0 : index,
|
||||
index < 0
|
||||
? GamePrize.empty().copyWith(
|
||||
id: result.prizeId,
|
||||
name: result.prizeName,
|
||||
)
|
||||
: gamePrizes[index],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _animateToPrize(int selectedPrizeIndex, GamePrize wonPrize) {
|
||||
_playWheelSpin();
|
||||
|
||||
_idleRotationController.stop();
|
||||
_idleRotationController.reset();
|
||||
|
||||
int selectedPrizeIndex = _selectPrizeWithWeight();
|
||||
double sectionAngle = (2 * math.pi) / gamePrizes.length;
|
||||
double currentPos = currentRotation;
|
||||
|
||||
@@ -254,7 +287,7 @@ class _FerrisWheelPageState extends State<FerrisWheelPage>
|
||||
|
||||
_rotationController.reset();
|
||||
_rotationController.animateTo(1.0).then((_) {
|
||||
final wonPrize = gamePrizes[selectedPrizeIndex];
|
||||
if (!mounted) return;
|
||||
_playWinSound();
|
||||
|
||||
setState(() {
|
||||
@@ -373,6 +406,15 @@ class _FerrisWheelPageState extends State<FerrisWheelPage>
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocListener<GameSpinBloc, GameSpinState>(
|
||||
listenWhen: (p, c) => p.failureOrResultOption != c.failureOrResultOption,
|
||||
listener: (context, state) =>
|
||||
state.failureOrResultOption.fold(() {}, _onSpinResult),
|
||||
child: _buildPage(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPage() {
|
||||
return BlocBuilder<FerrisWheelLoaderBloc, FerrisWheelLoaderState>(
|
||||
builder: (context, state) {
|
||||
if (state.isFetching) {
|
||||
@@ -599,21 +641,15 @@ class _FerrisWheelPageState extends State<FerrisWheelPage>
|
||||
),
|
||||
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 WalletCurrencyIcon(currency: WalletCurrency.coin, size: 20),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'Token: $tokens',
|
||||
style: AppStyle.lg.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColor.textPrimary,
|
||||
BlocBuilder<WalletLoaderBloc, WalletLoaderState>(
|
||||
builder: (context, wallet) => Text(
|
||||
'${wallet.summary.coinBalance.thousandFormat} EnakCoin',
|
||||
style: AppStyle.lg.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColor.textPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -689,6 +725,16 @@ class _FerrisWheelPageState extends State<FerrisWheelPage>
|
||||
}
|
||||
|
||||
Widget _buildSpinButton() {
|
||||
return BlocSelector<WalletLoaderBloc, WalletLoaderState, int>(
|
||||
selector: (state) => state.summary.coinBalance,
|
||||
builder: (context, coinBalance) => Opacity(
|
||||
opacity: coinBalance >= _coinCost ? 1 : 0.5,
|
||||
child: _buildSpinButtonBody(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSpinButtonBody() {
|
||||
return AnimatedBuilder(
|
||||
animation: _pulseAnimation ?? const AlwaysStoppedAnimation(1.0),
|
||||
builder: (context, child) => Transform.scale(
|
||||
@@ -737,21 +783,31 @@ class _FerrisWheelPageState extends State<FerrisWheelPage>
|
||||
}
|
||||
|
||||
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,
|
||||
),
|
||||
),
|
||||
return BlocSelector<WalletLoaderBloc, WalletLoaderState, int>(
|
||||
selector: (state) => state.summary.coinBalance,
|
||||
builder: (context, coinBalance) {
|
||||
final cost = _coinCost;
|
||||
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(
|
||||
coinBalance >= cost
|
||||
? 'Sekali main: $cost EnakCoin'
|
||||
: 'Sekali main: $cost EnakCoin. EnakCoin kamu belum cukup.',
|
||||
textAlign: TextAlign.center,
|
||||
style: AppStyle.lg.copyWith(
|
||||
color: AppColor.textWhite,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user