From 909c312af0f179a67bdb6e8e42cdf2188847f983 Mon Sep 17 00:00:00 2001 From: efrilm Date: Thu, 18 Sep 2025 14:53:39 +0700 Subject: [PATCH] Ferish Wheel and Music --- assets/audio/{carnaval/sfx => }/bell_ding.mp3 | Bin assets/audio/{carnaval/sfx => }/big_win.mp3 | Bin .../audio/{carnaval/sfx => }/button_tap.mp3 | Bin .../bgm => }/carnaval_main_theme.mp3 | Bin .../audio/{carnaval/sfx => }/token_sound.mp3 | Bin .../audio/{carnaval/sfx => }/wheel_spin.mp3 | Bin .../ferris_wheel_loader_bloc.dart | 42 + .../ferris_wheel_loader_bloc.freezed.dart | 388 +++++++++ .../ferris_wheel_loader_event.dart | 6 + .../ferris_wheel_loader_state.dart | 15 + .../game_prize_loader_bloc.dart | 40 - .../game_prize_loader_bloc.freezed.dart | 442 ---------- .../game_prize_loader_event.dart | 6 - .../game_prize_loader_state.dart | 13 - lib/common/painter/wheel_painter.dart | 197 +---- lib/common/url/api_path.dart | 2 +- lib/domain/game/entity/game_entity.dart | 2 + lib/domain/game/entity/game_prize_entity.dart | 14 +- lib/domain/game/game.freezed.dart | 156 +--- .../game/repositories/i_game_repository.dart | 4 +- .../datasources/remote_data_provider.dart | 13 +- lib/infrastructure/game/dto/game_dto.dart | 4 +- .../game/dto/game_prize_dto.dart | 13 +- .../game/game_dtos.freezed.dart | 178 +--- lib/infrastructure/game/game_dtos.g.dart | 16 +- .../game/repositories/game_repository.dart | 10 +- lib/injection.config.dart | 8 +- .../ferris_wheel/ferris_wheel_page.dart | 823 ++++++++---------- 28 files changed, 970 insertions(+), 1422 deletions(-) rename assets/audio/{carnaval/sfx => }/bell_ding.mp3 (100%) rename assets/audio/{carnaval/sfx => }/big_win.mp3 (100%) rename assets/audio/{carnaval/sfx => }/button_tap.mp3 (100%) rename assets/audio/{carnaval/bgm => }/carnaval_main_theme.mp3 (100%) rename assets/audio/{carnaval/sfx => }/token_sound.mp3 (100%) rename assets/audio/{carnaval/sfx => }/wheel_spin.mp3 (100%) create mode 100644 lib/application/game/ferris_wheel_loader/ferris_wheel_loader_bloc.dart create mode 100644 lib/application/game/ferris_wheel_loader/ferris_wheel_loader_bloc.freezed.dart create mode 100644 lib/application/game/ferris_wheel_loader/ferris_wheel_loader_event.dart create mode 100644 lib/application/game/ferris_wheel_loader/ferris_wheel_loader_state.dart delete mode 100644 lib/application/game/game_price_loader/game_prize_loader_bloc.dart delete mode 100644 lib/application/game/game_price_loader/game_prize_loader_bloc.freezed.dart delete mode 100644 lib/application/game/game_price_loader/game_prize_loader_event.dart delete mode 100644 lib/application/game/game_price_loader/game_prize_loader_state.dart diff --git a/assets/audio/carnaval/sfx/bell_ding.mp3 b/assets/audio/bell_ding.mp3 similarity index 100% rename from assets/audio/carnaval/sfx/bell_ding.mp3 rename to assets/audio/bell_ding.mp3 diff --git a/assets/audio/carnaval/sfx/big_win.mp3 b/assets/audio/big_win.mp3 similarity index 100% rename from assets/audio/carnaval/sfx/big_win.mp3 rename to assets/audio/big_win.mp3 diff --git a/assets/audio/carnaval/sfx/button_tap.mp3 b/assets/audio/button_tap.mp3 similarity index 100% rename from assets/audio/carnaval/sfx/button_tap.mp3 rename to assets/audio/button_tap.mp3 diff --git a/assets/audio/carnaval/bgm/carnaval_main_theme.mp3 b/assets/audio/carnaval_main_theme.mp3 similarity index 100% rename from assets/audio/carnaval/bgm/carnaval_main_theme.mp3 rename to assets/audio/carnaval_main_theme.mp3 diff --git a/assets/audio/carnaval/sfx/token_sound.mp3 b/assets/audio/token_sound.mp3 similarity index 100% rename from assets/audio/carnaval/sfx/token_sound.mp3 rename to assets/audio/token_sound.mp3 diff --git a/assets/audio/carnaval/sfx/wheel_spin.mp3 b/assets/audio/wheel_spin.mp3 similarity index 100% rename from assets/audio/carnaval/sfx/wheel_spin.mp3 rename to assets/audio/wheel_spin.mp3 diff --git a/lib/application/game/ferris_wheel_loader/ferris_wheel_loader_bloc.dart b/lib/application/game/ferris_wheel_loader/ferris_wheel_loader_bloc.dart new file mode 100644 index 0000000..3cf713f --- /dev/null +++ b/lib/application/game/ferris_wheel_loader/ferris_wheel_loader_bloc.dart @@ -0,0 +1,42 @@ +import 'package:bloc/bloc.dart'; +import 'package:dartz/dartz.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:injectable/injectable.dart'; + +import '../../../domain/game/game.dart'; + +part 'ferris_wheel_loader_event.dart'; +part 'ferris_wheel_loader_state.dart'; +part 'ferris_wheel_loader_bloc.freezed.dart'; + +@injectable +class FerrisWheelLoaderBloc + extends Bloc { + final IGameRepository _repository; + FerrisWheelLoaderBloc(this._repository) + : super(FerrisWheelLoaderState.initial()) { + on(_onFerrisWheelLoaderEvent); + } + + Future _onFerrisWheelLoaderEvent( + FerrisWheelLoaderEvent event, + Emitter emit, + ) { + return event.map( + fetched: (e) async { + emit( + state.copyWith(isFetching: true, failureOptionFerrisWheel: none()), + ); + + final result = await _repository.ferrisWheel(); + + var data = result.fold( + (f) => state.copyWith(failureOptionFerrisWheel: optionOf(f)), + (ferrisWheel) => state.copyWith(ferrisWheel: ferrisWheel), + ); + + emit(data.copyWith(isFetching: false)); + }, + ); + } +} diff --git a/lib/application/game/ferris_wheel_loader/ferris_wheel_loader_bloc.freezed.dart b/lib/application/game/ferris_wheel_loader/ferris_wheel_loader_bloc.freezed.dart new file mode 100644 index 0000000..0979be9 --- /dev/null +++ b/lib/application/game/ferris_wheel_loader/ferris_wheel_loader_bloc.freezed.dart @@ -0,0 +1,388 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'ferris_wheel_loader_bloc.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +/// @nodoc +mixin _$FerrisWheelLoaderEvent { + @optionalTypeArgs + TResult when({ + required TResult Function() fetched, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? fetched, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? fetched, + required TResult orElse(), + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_Fetched value) fetched, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Fetched value)? fetched, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Fetched value)? fetched, + required TResult orElse(), + }) => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $FerrisWheelLoaderEventCopyWith<$Res> { + factory $FerrisWheelLoaderEventCopyWith( + FerrisWheelLoaderEvent value, + $Res Function(FerrisWheelLoaderEvent) then, + ) = _$FerrisWheelLoaderEventCopyWithImpl<$Res, FerrisWheelLoaderEvent>; +} + +/// @nodoc +class _$FerrisWheelLoaderEventCopyWithImpl< + $Res, + $Val extends FerrisWheelLoaderEvent +> + implements $FerrisWheelLoaderEventCopyWith<$Res> { + _$FerrisWheelLoaderEventCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of FerrisWheelLoaderEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc +abstract class _$$FetchedImplCopyWith<$Res> { + factory _$$FetchedImplCopyWith( + _$FetchedImpl value, + $Res Function(_$FetchedImpl) then, + ) = __$$FetchedImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$FetchedImplCopyWithImpl<$Res> + extends _$FerrisWheelLoaderEventCopyWithImpl<$Res, _$FetchedImpl> + implements _$$FetchedImplCopyWith<$Res> { + __$$FetchedImplCopyWithImpl( + _$FetchedImpl _value, + $Res Function(_$FetchedImpl) _then, + ) : super(_value, _then); + + /// Create a copy of FerrisWheelLoaderEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$FetchedImpl implements _Fetched { + const _$FetchedImpl(); + + @override + String toString() { + return 'FerrisWheelLoaderEvent.fetched()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$FetchedImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({required TResult Function() fetched}) { + return fetched(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({TResult? Function()? fetched}) { + return fetched?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? fetched, + required TResult orElse(), + }) { + if (fetched != null) { + return fetched(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Fetched value) fetched, + }) { + return fetched(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Fetched value)? fetched, + }) { + return fetched?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Fetched value)? fetched, + required TResult orElse(), + }) { + if (fetched != null) { + return fetched(this); + } + return orElse(); + } +} + +abstract class _Fetched implements FerrisWheelLoaderEvent { + const factory _Fetched() = _$FetchedImpl; +} + +/// @nodoc +mixin _$FerrisWheelLoaderState { + Game get ferrisWheel => throw _privateConstructorUsedError; + Option get failureOptionFerrisWheel => + throw _privateConstructorUsedError; + bool get isFetching => throw _privateConstructorUsedError; + + /// Create a copy of FerrisWheelLoaderState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $FerrisWheelLoaderStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $FerrisWheelLoaderStateCopyWith<$Res> { + factory $FerrisWheelLoaderStateCopyWith( + FerrisWheelLoaderState value, + $Res Function(FerrisWheelLoaderState) then, + ) = _$FerrisWheelLoaderStateCopyWithImpl<$Res, FerrisWheelLoaderState>; + @useResult + $Res call({ + Game ferrisWheel, + Option failureOptionFerrisWheel, + bool isFetching, + }); + + $GameCopyWith<$Res> get ferrisWheel; +} + +/// @nodoc +class _$FerrisWheelLoaderStateCopyWithImpl< + $Res, + $Val extends FerrisWheelLoaderState +> + implements $FerrisWheelLoaderStateCopyWith<$Res> { + _$FerrisWheelLoaderStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of FerrisWheelLoaderState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? ferrisWheel = null, + Object? failureOptionFerrisWheel = null, + Object? isFetching = null, + }) { + return _then( + _value.copyWith( + ferrisWheel: null == ferrisWheel + ? _value.ferrisWheel + : ferrisWheel // ignore: cast_nullable_to_non_nullable + as Game, + failureOptionFerrisWheel: null == failureOptionFerrisWheel + ? _value.failureOptionFerrisWheel + : failureOptionFerrisWheel // ignore: cast_nullable_to_non_nullable + as Option, + isFetching: null == isFetching + ? _value.isFetching + : isFetching // ignore: cast_nullable_to_non_nullable + as bool, + ) + as $Val, + ); + } + + /// Create a copy of FerrisWheelLoaderState + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $GameCopyWith<$Res> get ferrisWheel { + return $GameCopyWith<$Res>(_value.ferrisWheel, (value) { + return _then(_value.copyWith(ferrisWheel: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$FerrisWheelLoaderStateImplCopyWith<$Res> + implements $FerrisWheelLoaderStateCopyWith<$Res> { + factory _$$FerrisWheelLoaderStateImplCopyWith( + _$FerrisWheelLoaderStateImpl value, + $Res Function(_$FerrisWheelLoaderStateImpl) then, + ) = __$$FerrisWheelLoaderStateImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + Game ferrisWheel, + Option failureOptionFerrisWheel, + bool isFetching, + }); + + @override + $GameCopyWith<$Res> get ferrisWheel; +} + +/// @nodoc +class __$$FerrisWheelLoaderStateImplCopyWithImpl<$Res> + extends + _$FerrisWheelLoaderStateCopyWithImpl<$Res, _$FerrisWheelLoaderStateImpl> + implements _$$FerrisWheelLoaderStateImplCopyWith<$Res> { + __$$FerrisWheelLoaderStateImplCopyWithImpl( + _$FerrisWheelLoaderStateImpl _value, + $Res Function(_$FerrisWheelLoaderStateImpl) _then, + ) : super(_value, _then); + + /// Create a copy of FerrisWheelLoaderState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? ferrisWheel = null, + Object? failureOptionFerrisWheel = null, + Object? isFetching = null, + }) { + return _then( + _$FerrisWheelLoaderStateImpl( + ferrisWheel: null == ferrisWheel + ? _value.ferrisWheel + : ferrisWheel // ignore: cast_nullable_to_non_nullable + as Game, + failureOptionFerrisWheel: null == failureOptionFerrisWheel + ? _value.failureOptionFerrisWheel + : failureOptionFerrisWheel // ignore: cast_nullable_to_non_nullable + as Option, + isFetching: null == isFetching + ? _value.isFetching + : isFetching // ignore: cast_nullable_to_non_nullable + as bool, + ), + ); + } +} + +/// @nodoc + +class _$FerrisWheelLoaderStateImpl implements _FerrisWheelLoaderState { + const _$FerrisWheelLoaderStateImpl({ + required this.ferrisWheel, + required this.failureOptionFerrisWheel, + this.isFetching = false, + }); + + @override + final Game ferrisWheel; + @override + final Option failureOptionFerrisWheel; + @override + @JsonKey() + final bool isFetching; + + @override + String toString() { + return 'FerrisWheelLoaderState(ferrisWheel: $ferrisWheel, failureOptionFerrisWheel: $failureOptionFerrisWheel, isFetching: $isFetching)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$FerrisWheelLoaderStateImpl && + (identical(other.ferrisWheel, ferrisWheel) || + other.ferrisWheel == ferrisWheel) && + (identical( + other.failureOptionFerrisWheel, + failureOptionFerrisWheel, + ) || + other.failureOptionFerrisWheel == failureOptionFerrisWheel) && + (identical(other.isFetching, isFetching) || + other.isFetching == isFetching)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + ferrisWheel, + failureOptionFerrisWheel, + isFetching, + ); + + /// Create a copy of FerrisWheelLoaderState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$FerrisWheelLoaderStateImplCopyWith<_$FerrisWheelLoaderStateImpl> + get copyWith => + __$$FerrisWheelLoaderStateImplCopyWithImpl<_$FerrisWheelLoaderStateImpl>( + this, + _$identity, + ); +} + +abstract class _FerrisWheelLoaderState implements FerrisWheelLoaderState { + const factory _FerrisWheelLoaderState({ + required final Game ferrisWheel, + required final Option failureOptionFerrisWheel, + final bool isFetching, + }) = _$FerrisWheelLoaderStateImpl; + + @override + Game get ferrisWheel; + @override + Option get failureOptionFerrisWheel; + @override + bool get isFetching; + + /// Create a copy of FerrisWheelLoaderState + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$FerrisWheelLoaderStateImplCopyWith<_$FerrisWheelLoaderStateImpl> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/lib/application/game/ferris_wheel_loader/ferris_wheel_loader_event.dart b/lib/application/game/ferris_wheel_loader/ferris_wheel_loader_event.dart new file mode 100644 index 0000000..c250a80 --- /dev/null +++ b/lib/application/game/ferris_wheel_loader/ferris_wheel_loader_event.dart @@ -0,0 +1,6 @@ +part of 'ferris_wheel_loader_bloc.dart'; + +@freezed +class FerrisWheelLoaderEvent with _$FerrisWheelLoaderEvent { + const factory FerrisWheelLoaderEvent.fetched() = _Fetched; +} diff --git a/lib/application/game/ferris_wheel_loader/ferris_wheel_loader_state.dart b/lib/application/game/ferris_wheel_loader/ferris_wheel_loader_state.dart new file mode 100644 index 0000000..795a34c --- /dev/null +++ b/lib/application/game/ferris_wheel_loader/ferris_wheel_loader_state.dart @@ -0,0 +1,15 @@ +part of 'ferris_wheel_loader_bloc.dart'; + +@freezed +class FerrisWheelLoaderState with _$FerrisWheelLoaderState { + const factory FerrisWheelLoaderState({ + required Game ferrisWheel, + required Option failureOptionFerrisWheel, + @Default(false) bool isFetching, + }) = _FerrisWheelLoaderState; + + factory FerrisWheelLoaderState.initial() => FerrisWheelLoaderState( + ferrisWheel: Game.empty(), + failureOptionFerrisWheel: none(), + ); +} diff --git a/lib/application/game/game_price_loader/game_prize_loader_bloc.dart b/lib/application/game/game_price_loader/game_prize_loader_bloc.dart deleted file mode 100644 index bb1ad73..0000000 --- a/lib/application/game/game_price_loader/game_prize_loader_bloc.dart +++ /dev/null @@ -1,40 +0,0 @@ -import 'package:bloc/bloc.dart'; -import 'package:dartz/dartz.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:injectable/injectable.dart'; - -import '../../../domain/game/game.dart'; - -part 'game_prize_loader_event.dart'; -part 'game_prize_loader_state.dart'; -part 'game_prize_loader_bloc.freezed.dart'; - -@injectable -class GamePrizeLoaderBloc - extends Bloc { - final IGameRepository _repository; - GamePrizeLoaderBloc(this._repository) - : super(GamePrizeLoaderState.initial()) { - on(_onGamePrizeLoaderEvent); - } - - Future _onGamePrizeLoaderEvent( - GamePrizeLoaderEvent event, - Emitter emit, - ) { - return event.map( - fetched: (e) async { - emit(state.copyWith(isFetching: true, failureOptionGamePrize: none())); - - final result = await _repository.gamePrizeByGameId(id: e.id); - - var data = result.fold( - (f) => state.copyWith(failureOptionGamePrize: optionOf(f)), - (gamePrize) => state.copyWith(gamePrize: gamePrize), - ); - - emit(data.copyWith(isFetching: false)); - }, - ); - } -} diff --git a/lib/application/game/game_price_loader/game_prize_loader_bloc.freezed.dart b/lib/application/game/game_price_loader/game_prize_loader_bloc.freezed.dart deleted file mode 100644 index 8c22ad9..0000000 --- a/lib/application/game/game_price_loader/game_prize_loader_bloc.freezed.dart +++ /dev/null @@ -1,442 +0,0 @@ -// coverage:ignore-file -// GENERATED CODE - DO NOT MODIFY BY HAND -// ignore_for_file: type=lint -// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark - -part of 'game_prize_loader_bloc.dart'; - -// ************************************************************************** -// FreezedGenerator -// ************************************************************************** - -T _$identity(T value) => value; - -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', -); - -/// @nodoc -mixin _$GamePrizeLoaderEvent { - String get id => throw _privateConstructorUsedError; - @optionalTypeArgs - TResult when({ - required TResult Function(String id) fetched, - }) => throw _privateConstructorUsedError; - @optionalTypeArgs - TResult? whenOrNull({ - TResult? Function(String id)? fetched, - }) => throw _privateConstructorUsedError; - @optionalTypeArgs - TResult maybeWhen({ - TResult Function(String id)? fetched, - required TResult orElse(), - }) => throw _privateConstructorUsedError; - @optionalTypeArgs - TResult map({ - required TResult Function(_Fetched value) fetched, - }) => throw _privateConstructorUsedError; - @optionalTypeArgs - TResult? mapOrNull({ - TResult? Function(_Fetched value)? fetched, - }) => throw _privateConstructorUsedError; - @optionalTypeArgs - TResult maybeMap({ - TResult Function(_Fetched value)? fetched, - required TResult orElse(), - }) => throw _privateConstructorUsedError; - - /// Create a copy of GamePrizeLoaderEvent - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - $GamePrizeLoaderEventCopyWith get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $GamePrizeLoaderEventCopyWith<$Res> { - factory $GamePrizeLoaderEventCopyWith( - GamePrizeLoaderEvent value, - $Res Function(GamePrizeLoaderEvent) then, - ) = _$GamePrizeLoaderEventCopyWithImpl<$Res, GamePrizeLoaderEvent>; - @useResult - $Res call({String id}); -} - -/// @nodoc -class _$GamePrizeLoaderEventCopyWithImpl< - $Res, - $Val extends GamePrizeLoaderEvent -> - implements $GamePrizeLoaderEventCopyWith<$Res> { - _$GamePrizeLoaderEventCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - /// Create a copy of GamePrizeLoaderEvent - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({Object? id = null}) { - return _then( - _value.copyWith( - id: null == id - ? _value.id - : id // ignore: cast_nullable_to_non_nullable - as String, - ) - as $Val, - ); - } -} - -/// @nodoc -abstract class _$$FetchedImplCopyWith<$Res> - implements $GamePrizeLoaderEventCopyWith<$Res> { - factory _$$FetchedImplCopyWith( - _$FetchedImpl value, - $Res Function(_$FetchedImpl) then, - ) = __$$FetchedImplCopyWithImpl<$Res>; - @override - @useResult - $Res call({String id}); -} - -/// @nodoc -class __$$FetchedImplCopyWithImpl<$Res> - extends _$GamePrizeLoaderEventCopyWithImpl<$Res, _$FetchedImpl> - implements _$$FetchedImplCopyWith<$Res> { - __$$FetchedImplCopyWithImpl( - _$FetchedImpl _value, - $Res Function(_$FetchedImpl) _then, - ) : super(_value, _then); - - /// Create a copy of GamePrizeLoaderEvent - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({Object? id = null}) { - return _then( - _$FetchedImpl( - null == id - ? _value.id - : id // ignore: cast_nullable_to_non_nullable - as String, - ), - ); - } -} - -/// @nodoc - -class _$FetchedImpl implements _Fetched { - const _$FetchedImpl(this.id); - - @override - final String id; - - @override - String toString() { - return 'GamePrizeLoaderEvent.fetched(id: $id)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$FetchedImpl && - (identical(other.id, id) || other.id == id)); - } - - @override - int get hashCode => Object.hash(runtimeType, id); - - /// Create a copy of GamePrizeLoaderEvent - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - @override - @pragma('vm:prefer-inline') - _$$FetchedImplCopyWith<_$FetchedImpl> get copyWith => - __$$FetchedImplCopyWithImpl<_$FetchedImpl>(this, _$identity); - - @override - @optionalTypeArgs - TResult when({ - required TResult Function(String id) fetched, - }) { - return fetched(id); - } - - @override - @optionalTypeArgs - TResult? whenOrNull({ - TResult? Function(String id)? fetched, - }) { - return fetched?.call(id); - } - - @override - @optionalTypeArgs - TResult maybeWhen({ - TResult Function(String id)? fetched, - required TResult orElse(), - }) { - if (fetched != null) { - return fetched(id); - } - return orElse(); - } - - @override - @optionalTypeArgs - TResult map({ - required TResult Function(_Fetched value) fetched, - }) { - return fetched(this); - } - - @override - @optionalTypeArgs - TResult? mapOrNull({ - TResult? Function(_Fetched value)? fetched, - }) { - return fetched?.call(this); - } - - @override - @optionalTypeArgs - TResult maybeMap({ - TResult Function(_Fetched value)? fetched, - required TResult orElse(), - }) { - if (fetched != null) { - return fetched(this); - } - return orElse(); - } -} - -abstract class _Fetched implements GamePrizeLoaderEvent { - const factory _Fetched(final String id) = _$FetchedImpl; - - @override - String get id; - - /// Create a copy of GamePrizeLoaderEvent - /// with the given fields replaced by the non-null parameter values. - @override - @JsonKey(includeFromJson: false, includeToJson: false) - _$$FetchedImplCopyWith<_$FetchedImpl> get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -mixin _$GamePrizeLoaderState { - List get gamePrize => throw _privateConstructorUsedError; - Option get failureOptionGamePrize => - throw _privateConstructorUsedError; - bool get isFetching => throw _privateConstructorUsedError; - - /// Create a copy of GamePrizeLoaderState - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - $GamePrizeLoaderStateCopyWith get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $GamePrizeLoaderStateCopyWith<$Res> { - factory $GamePrizeLoaderStateCopyWith( - GamePrizeLoaderState value, - $Res Function(GamePrizeLoaderState) then, - ) = _$GamePrizeLoaderStateCopyWithImpl<$Res, GamePrizeLoaderState>; - @useResult - $Res call({ - List gamePrize, - Option failureOptionGamePrize, - bool isFetching, - }); -} - -/// @nodoc -class _$GamePrizeLoaderStateCopyWithImpl< - $Res, - $Val extends GamePrizeLoaderState -> - implements $GamePrizeLoaderStateCopyWith<$Res> { - _$GamePrizeLoaderStateCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - /// Create a copy of GamePrizeLoaderState - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? gamePrize = null, - Object? failureOptionGamePrize = null, - Object? isFetching = null, - }) { - return _then( - _value.copyWith( - gamePrize: null == gamePrize - ? _value.gamePrize - : gamePrize // ignore: cast_nullable_to_non_nullable - as List, - failureOptionGamePrize: null == failureOptionGamePrize - ? _value.failureOptionGamePrize - : failureOptionGamePrize // ignore: cast_nullable_to_non_nullable - as Option, - isFetching: null == isFetching - ? _value.isFetching - : isFetching // ignore: cast_nullable_to_non_nullable - as bool, - ) - as $Val, - ); - } -} - -/// @nodoc -abstract class _$$GamePrizeLoaderStateImplCopyWith<$Res> - implements $GamePrizeLoaderStateCopyWith<$Res> { - factory _$$GamePrizeLoaderStateImplCopyWith( - _$GamePrizeLoaderStateImpl value, - $Res Function(_$GamePrizeLoaderStateImpl) then, - ) = __$$GamePrizeLoaderStateImplCopyWithImpl<$Res>; - @override - @useResult - $Res call({ - List gamePrize, - Option failureOptionGamePrize, - bool isFetching, - }); -} - -/// @nodoc -class __$$GamePrizeLoaderStateImplCopyWithImpl<$Res> - extends _$GamePrizeLoaderStateCopyWithImpl<$Res, _$GamePrizeLoaderStateImpl> - implements _$$GamePrizeLoaderStateImplCopyWith<$Res> { - __$$GamePrizeLoaderStateImplCopyWithImpl( - _$GamePrizeLoaderStateImpl _value, - $Res Function(_$GamePrizeLoaderStateImpl) _then, - ) : super(_value, _then); - - /// Create a copy of GamePrizeLoaderState - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? gamePrize = null, - Object? failureOptionGamePrize = null, - Object? isFetching = null, - }) { - return _then( - _$GamePrizeLoaderStateImpl( - gamePrize: null == gamePrize - ? _value._gamePrize - : gamePrize // ignore: cast_nullable_to_non_nullable - as List, - failureOptionGamePrize: null == failureOptionGamePrize - ? _value.failureOptionGamePrize - : failureOptionGamePrize // ignore: cast_nullable_to_non_nullable - as Option, - isFetching: null == isFetching - ? _value.isFetching - : isFetching // ignore: cast_nullable_to_non_nullable - as bool, - ), - ); - } -} - -/// @nodoc - -class _$GamePrizeLoaderStateImpl implements _GamePrizeLoaderState { - const _$GamePrizeLoaderStateImpl({ - required final List gamePrize, - required this.failureOptionGamePrize, - this.isFetching = false, - }) : _gamePrize = gamePrize; - - final List _gamePrize; - @override - List get gamePrize { - if (_gamePrize is EqualUnmodifiableListView) return _gamePrize; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_gamePrize); - } - - @override - final Option failureOptionGamePrize; - @override - @JsonKey() - final bool isFetching; - - @override - String toString() { - return 'GamePrizeLoaderState(gamePrize: $gamePrize, failureOptionGamePrize: $failureOptionGamePrize, isFetching: $isFetching)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$GamePrizeLoaderStateImpl && - const DeepCollectionEquality().equals( - other._gamePrize, - _gamePrize, - ) && - (identical(other.failureOptionGamePrize, failureOptionGamePrize) || - other.failureOptionGamePrize == failureOptionGamePrize) && - (identical(other.isFetching, isFetching) || - other.isFetching == isFetching)); - } - - @override - int get hashCode => Object.hash( - runtimeType, - const DeepCollectionEquality().hash(_gamePrize), - failureOptionGamePrize, - isFetching, - ); - - /// Create a copy of GamePrizeLoaderState - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - @override - @pragma('vm:prefer-inline') - _$$GamePrizeLoaderStateImplCopyWith<_$GamePrizeLoaderStateImpl> - get copyWith => - __$$GamePrizeLoaderStateImplCopyWithImpl<_$GamePrizeLoaderStateImpl>( - this, - _$identity, - ); -} - -abstract class _GamePrizeLoaderState implements GamePrizeLoaderState { - const factory _GamePrizeLoaderState({ - required final List gamePrize, - required final Option failureOptionGamePrize, - final bool isFetching, - }) = _$GamePrizeLoaderStateImpl; - - @override - List get gamePrize; - @override - Option get failureOptionGamePrize; - @override - bool get isFetching; - - /// Create a copy of GamePrizeLoaderState - /// with the given fields replaced by the non-null parameter values. - @override - @JsonKey(includeFromJson: false, includeToJson: false) - _$$GamePrizeLoaderStateImplCopyWith<_$GamePrizeLoaderStateImpl> - get copyWith => throw _privateConstructorUsedError; -} diff --git a/lib/application/game/game_price_loader/game_prize_loader_event.dart b/lib/application/game/game_price_loader/game_prize_loader_event.dart deleted file mode 100644 index ad7b58a..0000000 --- a/lib/application/game/game_price_loader/game_prize_loader_event.dart +++ /dev/null @@ -1,6 +0,0 @@ -part of 'game_prize_loader_bloc.dart'; - -@freezed -class GamePrizeLoaderEvent with _$GamePrizeLoaderEvent { - const factory GamePrizeLoaderEvent.fetched(String id) = _Fetched; -} diff --git a/lib/application/game/game_price_loader/game_prize_loader_state.dart b/lib/application/game/game_price_loader/game_prize_loader_state.dart deleted file mode 100644 index 50258c5..0000000 --- a/lib/application/game/game_price_loader/game_prize_loader_state.dart +++ /dev/null @@ -1,13 +0,0 @@ -part of 'game_prize_loader_bloc.dart'; - -@freezed -class GamePrizeLoaderState with _$GamePrizeLoaderState { - const factory GamePrizeLoaderState({ - required List gamePrize, - required Option failureOptionGamePrize, - @Default(false) bool isFetching, - }) = _GamePrizeLoaderState; - - factory GamePrizeLoaderState.initial() => - GamePrizeLoaderState(gamePrize: [], failureOptionGamePrize: none()); -} diff --git a/lib/common/painter/wheel_painter.dart b/lib/common/painter/wheel_painter.dart index c0b72e8..2415de4 100644 --- a/lib/common/painter/wheel_painter.dart +++ b/lib/common/painter/wheel_painter.dart @@ -1,8 +1,7 @@ +// wheel_painter.dart - Fixed implementation with consistent positioning import 'dart:math' as math; import 'package:flutter/material.dart'; - import '../../domain/game/game.dart'; -import '../theme/theme.dart'; class WheelPainter extends CustomPainter { final List gamePrizes; @@ -36,10 +35,11 @@ class WheelPainter extends CustomPainter { ..style = PaintingStyle.fill; canvas.drawCircle(center, radius - 20, innerWhitePaint); - // Draw sections + // Draw sections - KONSISTEN dengan logic spin for (int i = 0; i < gamePrizes.length; i++) { final prize = gamePrizes[i]; - final startAngle = i * sectionAngle - math.pi / 2; + // Section 0 di top (-π/2), section 1 di kanan atas, dst (clockwise) + final startAngle = (-math.pi / 2) + (i * sectionAngle); // Section background final sectionPaint = Paint() @@ -54,54 +54,13 @@ class WheelPainter extends CustomPainter { sectionPaint, ); - // Draw icon in each section - final iconAngle = startAngle + sectionAngle / 2; - final iconPosition = Offset( - center.dx + (radius - 60) * math.cos(iconAngle), - center.dy + (radius - 60) * math.sin(iconAngle), - ); - - // Draw icon background circle - final iconBgPaint = Paint() - ..color = Colors.white - ..style = PaintingStyle.fill; - canvas.drawCircle(iconPosition, 16, iconBgPaint); - - // Save canvas state for icon drawing - canvas.save(); - canvas.translate(iconPosition.dx, iconPosition.dy); - - // Get icon from metadata or use default - final iconData = _getIconFromMetadata(prize.metadata); - final iconText = _getIconText(iconData); - final iconTextPainter = TextPainter( - text: TextSpan( - text: iconText, - style: TextStyle( - fontFamily: 'MaterialIcons', - fontSize: 20, - color: getPrizeColor(prize, i), - fontWeight: FontWeight.normal, - ), - ), - textDirection: TextDirection.ltr, - ); - iconTextPainter.layout(); - iconTextPainter.paint( - canvas, - Offset(-iconTextPainter.width / 2, -iconTextPainter.height / 2), - ); - - canvas.restore(); - // Draw prize text final textAngle = startAngle + sectionAngle / 2; final textPosition = Offset( - center.dx + (radius - 100) * math.cos(textAngle), - center.dy + (radius - 100) * math.sin(textAngle), + center.dx + (radius - 80) * math.cos(textAngle), + center.dy + (radius - 80) * math.sin(textAngle), ); - // Save canvas state for text rotation canvas.save(); canvas.translate(textPosition.dx, textPosition.dy); canvas.rotate(textAngle + math.pi / 2); @@ -111,62 +70,51 @@ class WheelPainter extends CustomPainter { text: prize.name, style: const TextStyle( color: Colors.white, - fontSize: 10, + fontSize: 12, + fontWeight: FontWeight.bold, + ), + ), + textDirection: TextDirection.ltr, + textAlign: TextAlign.center, + ); + textPainter.layout(maxWidth: 100); + textPainter.paint( + canvas, + Offset(-textPainter.width / 2, -textPainter.height / 2), + ); + canvas.restore(); + + // DEBUG: Draw section number + final numberPosition = Offset( + center.dx + (radius - 50) * math.cos(textAngle), + center.dy + (radius - 50) * math.sin(textAngle), + ); + canvas.drawCircle(numberPosition, 15, Paint()..color = Colors.white); + final numberPainter = TextPainter( + text: TextSpan( + text: i.toString(), + style: const TextStyle( + color: Colors.black, + fontSize: 14, fontWeight: FontWeight.bold, ), ), textDirection: TextDirection.ltr, ); - textPainter.layout(); - textPainter.paint( + numberPainter.layout(); + numberPainter.paint( canvas, - Offset(-textPainter.width / 2, -textPainter.height / 2), + Offset( + numberPosition.dx - numberPainter.width / 2, + numberPosition.dy - numberPainter.height / 2, + ), ); - - canvas.restore(); - - // Draw stock indicator if stock is low - if (prize.stock <= 5 && prize.stock > 0) { - final stockAngle = startAngle + sectionAngle / 2; - final stockPosition = Offset( - center.dx + (radius - 35) * math.cos(stockAngle), - center.dy + (radius - 35) * math.sin(stockAngle), - ); - - final stockPaint = Paint() - ..color = AppColor.error - ..style = PaintingStyle.fill; - canvas.drawCircle(stockPosition, 8, stockPaint); - - canvas.save(); - canvas.translate(stockPosition.dx, stockPosition.dy); - - final stockTextPainter = TextPainter( - text: TextSpan( - text: '!', - style: const TextStyle( - color: Colors.white, - fontSize: 10, - fontWeight: FontWeight.bold, - ), - ), - textDirection: TextDirection.ltr, - ); - stockTextPainter.layout(); - stockTextPainter.paint( - canvas, - Offset(-stockTextPainter.width / 2, -stockTextPainter.height / 2), - ); - - canvas.restore(); - } } - // Draw white dots around the outer edge + // Draw white dots final dotPaint = Paint() ..color = Colors.white ..style = PaintingStyle.fill; - for (int i = 0; i < 24; i++) { final dotAngle = (2 * math.pi / 24) * i; final dotPosition = Offset( @@ -178,15 +126,14 @@ class WheelPainter extends CustomPainter { // Draw section dividers final dividerPaint = Paint() - ..color = AppColor.white + ..color = Colors.white ..style = PaintingStyle.stroke ..strokeWidth = 2; - for (int i = 0; i < gamePrizes.length; i++) { - final angle = i * sectionAngle - math.pi / 2; + final angle = (-math.pi / 2) + (i * sectionAngle); final lineStart = Offset( - center.dx + (radius - 130) * math.cos(angle), - center.dy + (radius - 130) * math.sin(angle), + center.dx + (radius - 110) * math.cos(angle), + center.dy + (radius - 110) * math.sin(angle), ); final lineEnd = Offset( center.dx + (radius - 22) * math.cos(angle), @@ -196,64 +143,6 @@ class WheelPainter extends CustomPainter { } } - IconData _getIconFromMetadata(Map metadata) { - final iconName = metadata['icon'] as String?; - switch (iconName?.toLowerCase()) { - case 'card_giftcard': - return Icons.card_giftcard; - case 'monetization_on': - return Icons.monetization_on; - case 'redeem': - return Icons.redeem; - case 'attach_money': - return Icons.attach_money; - case 'account_balance_wallet': - return Icons.account_balance_wallet; - case 'diamond': - return Icons.diamond; - case 'star': - return Icons.star; - case 'emoji_events': - return Icons.emoji_events; - case 'refresh': - return Icons.refresh; - case 'visibility': - return Icons.visibility; - default: - return Icons.card_giftcard; // Default icon - } - } - - String _getIconText(IconData icon) { - // Convert IconData to Unicode string for drawing - switch (icon.codePoint) { - case 0xe8f4: // Icons.visibility - return String.fromCharCode(0xe8f4); - case 0xe8f5: // Icons.visibility_off - return String.fromCharCode(0xe8f5); - case 0xe850: // Icons.account_balance_wallet - return String.fromCharCode(0xe850); - case 0xe151: // Icons.card_giftcard - return String.fromCharCode(0xe151); - case 0xe5d5: // Icons.refresh - return String.fromCharCode(0xe5d5); - case 0xe263: // Icons.attach_money - return String.fromCharCode(0xe263); - case 0xe8a1: // Icons.redeem - return String.fromCharCode(0xe8a1); - case 0xe57d: // Icons.monetization_on - return String.fromCharCode(0xe57d); - case 0xe5ca: // Icons.diamond - return String.fromCharCode(0xe5ca); - case 0xe838: // Icons.star - return String.fromCharCode(0xe838); - case 0xe2a3: // Icons.emoji_events - return String.fromCharCode(0xe2a3); - default: - return String.fromCharCode(0xe151); // Default card_giftcard icon - } - } - @override bool shouldRepaint(covariant CustomPainter oldDelegate) => true; } diff --git a/lib/common/url/api_path.dart b/lib/common/url/api_path.dart index 6ab7556..cf51d59 100644 --- a/lib/common/url/api_path.dart +++ b/lib/common/url/api_path.dart @@ -7,7 +7,7 @@ class ApiPath { static String resend = '/api/v1/customer-auth/resend-otp'; // Marketing - static String gamePrizeByGameId = '/api/v1/marketing/game-prizes/game'; + static String ferrisWheel = '/api/v1/customer/ferris-wheel'; // Customer static String customerPoint = '/api/v1/customer/points'; } diff --git a/lib/domain/game/entity/game_entity.dart b/lib/domain/game/entity/game_entity.dart index 670a296..5f56b9d 100644 --- a/lib/domain/game/entity/game_entity.dart +++ b/lib/domain/game/entity/game_entity.dart @@ -8,6 +8,7 @@ class Game with _$Game { required String type, required bool isActive, required Map metadata, + required List prizes, required String createdAt, required String updatedAt, }) = _Game; @@ -18,6 +19,7 @@ class Game with _$Game { type: '', isActive: false, metadata: {}, + prizes: [], createdAt: '', updatedAt: '', ); diff --git a/lib/domain/game/entity/game_prize_entity.dart b/lib/domain/game/entity/game_prize_entity.dart index e03b91d..1d32834 100644 --- a/lib/domain/game/entity/game_prize_entity.dart +++ b/lib/domain/game/entity/game_prize_entity.dart @@ -6,26 +6,16 @@ class GamePrize with _$GamePrize { required String id, required String gameId, required String name, - required int weight, - required int stock, - required int maxStock, - required int threshold, required Map metadata, - required Game game, required String createdAt, required String updatedAt, }) = _GamePrize; - factory GamePrize.empty() => GamePrize( + factory GamePrize.empty() => const GamePrize( id: '', gameId: '', name: '', - weight: 0, - stock: 0, - maxStock: 0, - threshold: 0, - metadata: const {}, - game: Game.empty(), + metadata: {}, createdAt: '', updatedAt: '', ); diff --git a/lib/domain/game/game.freezed.dart b/lib/domain/game/game.freezed.dart index f13f3a1..18c5557 100644 --- a/lib/domain/game/game.freezed.dart +++ b/lib/domain/game/game.freezed.dart @@ -22,6 +22,7 @@ mixin _$Game { String get type => throw _privateConstructorUsedError; bool get isActive => throw _privateConstructorUsedError; Map get metadata => throw _privateConstructorUsedError; + List get prizes => throw _privateConstructorUsedError; String get createdAt => throw _privateConstructorUsedError; String get updatedAt => throw _privateConstructorUsedError; @@ -42,6 +43,7 @@ abstract class $GameCopyWith<$Res> { String type, bool isActive, Map metadata, + List prizes, String createdAt, String updatedAt, }); @@ -67,6 +69,7 @@ class _$GameCopyWithImpl<$Res, $Val extends Game> Object? type = null, Object? isActive = null, Object? metadata = null, + Object? prizes = null, Object? createdAt = null, Object? updatedAt = null, }) { @@ -92,6 +95,10 @@ class _$GameCopyWithImpl<$Res, $Val extends Game> ? _value.metadata : metadata // ignore: cast_nullable_to_non_nullable as Map, + prizes: null == prizes + ? _value.prizes + : prizes // ignore: cast_nullable_to_non_nullable + as List, createdAt: null == createdAt ? _value.createdAt : createdAt // ignore: cast_nullable_to_non_nullable @@ -120,6 +127,7 @@ abstract class _$$GameImplCopyWith<$Res> implements $GameCopyWith<$Res> { String type, bool isActive, Map metadata, + List prizes, String createdAt, String updatedAt, }); @@ -142,6 +150,7 @@ class __$$GameImplCopyWithImpl<$Res> Object? type = null, Object? isActive = null, Object? metadata = null, + Object? prizes = null, Object? createdAt = null, Object? updatedAt = null, }) { @@ -167,6 +176,10 @@ class __$$GameImplCopyWithImpl<$Res> ? _value._metadata : metadata // ignore: cast_nullable_to_non_nullable as Map, + prizes: null == prizes + ? _value._prizes + : prizes // ignore: cast_nullable_to_non_nullable + as List, createdAt: null == createdAt ? _value.createdAt : createdAt // ignore: cast_nullable_to_non_nullable @@ -189,9 +202,11 @@ class _$GameImpl implements _Game { required this.type, required this.isActive, required final Map metadata, + required final List prizes, required this.createdAt, required this.updatedAt, - }) : _metadata = metadata; + }) : _metadata = metadata, + _prizes = prizes; @override final String id; @@ -209,6 +224,14 @@ class _$GameImpl implements _Game { return EqualUnmodifiableMapView(_metadata); } + final List _prizes; + @override + List get prizes { + if (_prizes is EqualUnmodifiableListView) return _prizes; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_prizes); + } + @override final String createdAt; @override @@ -216,7 +239,7 @@ class _$GameImpl implements _Game { @override String toString() { - return 'Game(id: $id, name: $name, type: $type, isActive: $isActive, metadata: $metadata, createdAt: $createdAt, updatedAt: $updatedAt)'; + return 'Game(id: $id, name: $name, type: $type, isActive: $isActive, metadata: $metadata, prizes: $prizes, createdAt: $createdAt, updatedAt: $updatedAt)'; } @override @@ -230,6 +253,7 @@ class _$GameImpl implements _Game { (identical(other.isActive, isActive) || other.isActive == isActive) && const DeepCollectionEquality().equals(other._metadata, _metadata) && + const DeepCollectionEquality().equals(other._prizes, _prizes) && (identical(other.createdAt, createdAt) || other.createdAt == createdAt) && (identical(other.updatedAt, updatedAt) || @@ -244,6 +268,7 @@ class _$GameImpl implements _Game { type, isActive, const DeepCollectionEquality().hash(_metadata), + const DeepCollectionEquality().hash(_prizes), createdAt, updatedAt, ); @@ -264,6 +289,7 @@ abstract class _Game implements Game { required final String type, required final bool isActive, required final Map metadata, + required final List prizes, required final String createdAt, required final String updatedAt, }) = _$GameImpl; @@ -279,6 +305,8 @@ abstract class _Game implements Game { @override Map get metadata; @override + List get prizes; + @override String get createdAt; @override String get updatedAt; @@ -296,12 +324,7 @@ mixin _$GamePrize { String get id => throw _privateConstructorUsedError; String get gameId => throw _privateConstructorUsedError; String get name => throw _privateConstructorUsedError; - int get weight => throw _privateConstructorUsedError; - int get stock => throw _privateConstructorUsedError; - int get maxStock => throw _privateConstructorUsedError; - int get threshold => throw _privateConstructorUsedError; Map get metadata => throw _privateConstructorUsedError; - Game get game => throw _privateConstructorUsedError; String get createdAt => throw _privateConstructorUsedError; String get updatedAt => throw _privateConstructorUsedError; @@ -321,17 +344,10 @@ abstract class $GamePrizeCopyWith<$Res> { String id, String gameId, String name, - int weight, - int stock, - int maxStock, - int threshold, Map metadata, - Game game, String createdAt, String updatedAt, }); - - $GameCopyWith<$Res> get game; } /// @nodoc @@ -352,12 +368,7 @@ class _$GamePrizeCopyWithImpl<$Res, $Val extends GamePrize> Object? id = null, Object? gameId = null, Object? name = null, - Object? weight = null, - Object? stock = null, - Object? maxStock = null, - Object? threshold = null, Object? metadata = null, - Object? game = null, Object? createdAt = null, Object? updatedAt = null, }) { @@ -375,30 +386,10 @@ class _$GamePrizeCopyWithImpl<$Res, $Val extends GamePrize> ? _value.name : name // ignore: cast_nullable_to_non_nullable as String, - weight: null == weight - ? _value.weight - : weight // ignore: cast_nullable_to_non_nullable - as int, - stock: null == stock - ? _value.stock - : stock // ignore: cast_nullable_to_non_nullable - as int, - maxStock: null == maxStock - ? _value.maxStock - : maxStock // ignore: cast_nullable_to_non_nullable - as int, - threshold: null == threshold - ? _value.threshold - : threshold // ignore: cast_nullable_to_non_nullable - as int, metadata: null == metadata ? _value.metadata : metadata // ignore: cast_nullable_to_non_nullable as Map, - game: null == game - ? _value.game - : game // ignore: cast_nullable_to_non_nullable - as Game, createdAt: null == createdAt ? _value.createdAt : createdAt // ignore: cast_nullable_to_non_nullable @@ -411,16 +402,6 @@ class _$GamePrizeCopyWithImpl<$Res, $Val extends GamePrize> as $Val, ); } - - /// Create a copy of GamePrize - /// with the given fields replaced by the non-null parameter values. - @override - @pragma('vm:prefer-inline') - $GameCopyWith<$Res> get game { - return $GameCopyWith<$Res>(_value.game, (value) { - return _then(_value.copyWith(game: value) as $Val); - }); - } } /// @nodoc @@ -436,18 +417,10 @@ abstract class _$$GamePrizeImplCopyWith<$Res> String id, String gameId, String name, - int weight, - int stock, - int maxStock, - int threshold, Map metadata, - Game game, String createdAt, String updatedAt, }); - - @override - $GameCopyWith<$Res> get game; } /// @nodoc @@ -467,12 +440,7 @@ class __$$GamePrizeImplCopyWithImpl<$Res> Object? id = null, Object? gameId = null, Object? name = null, - Object? weight = null, - Object? stock = null, - Object? maxStock = null, - Object? threshold = null, Object? metadata = null, - Object? game = null, Object? createdAt = null, Object? updatedAt = null, }) { @@ -490,30 +458,10 @@ class __$$GamePrizeImplCopyWithImpl<$Res> ? _value.name : name // ignore: cast_nullable_to_non_nullable as String, - weight: null == weight - ? _value.weight - : weight // ignore: cast_nullable_to_non_nullable - as int, - stock: null == stock - ? _value.stock - : stock // ignore: cast_nullable_to_non_nullable - as int, - maxStock: null == maxStock - ? _value.maxStock - : maxStock // ignore: cast_nullable_to_non_nullable - as int, - threshold: null == threshold - ? _value.threshold - : threshold // ignore: cast_nullable_to_non_nullable - as int, metadata: null == metadata ? _value._metadata : metadata // ignore: cast_nullable_to_non_nullable as Map, - game: null == game - ? _value.game - : game // ignore: cast_nullable_to_non_nullable - as Game, createdAt: null == createdAt ? _value.createdAt : createdAt // ignore: cast_nullable_to_non_nullable @@ -534,12 +482,7 @@ class _$GamePrizeImpl implements _GamePrize { required this.id, required this.gameId, required this.name, - required this.weight, - required this.stock, - required this.maxStock, - required this.threshold, required final Map metadata, - required this.game, required this.createdAt, required this.updatedAt, }) : _metadata = metadata; @@ -550,14 +493,6 @@ class _$GamePrizeImpl implements _GamePrize { final String gameId; @override final String name; - @override - final int weight; - @override - final int stock; - @override - final int maxStock; - @override - final int threshold; final Map _metadata; @override Map get metadata { @@ -566,8 +501,6 @@ class _$GamePrizeImpl implements _GamePrize { return EqualUnmodifiableMapView(_metadata); } - @override - final Game game; @override final String createdAt; @override @@ -575,7 +508,7 @@ class _$GamePrizeImpl implements _GamePrize { @override String toString() { - return 'GamePrize(id: $id, gameId: $gameId, name: $name, weight: $weight, stock: $stock, maxStock: $maxStock, threshold: $threshold, metadata: $metadata, game: $game, createdAt: $createdAt, updatedAt: $updatedAt)'; + return 'GamePrize(id: $id, gameId: $gameId, name: $name, metadata: $metadata, createdAt: $createdAt, updatedAt: $updatedAt)'; } @override @@ -586,14 +519,7 @@ class _$GamePrizeImpl implements _GamePrize { (identical(other.id, id) || other.id == id) && (identical(other.gameId, gameId) || other.gameId == gameId) && (identical(other.name, name) || other.name == name) && - (identical(other.weight, weight) || other.weight == weight) && - (identical(other.stock, stock) || other.stock == stock) && - (identical(other.maxStock, maxStock) || - other.maxStock == maxStock) && - (identical(other.threshold, threshold) || - other.threshold == threshold) && const DeepCollectionEquality().equals(other._metadata, _metadata) && - (identical(other.game, game) || other.game == game) && (identical(other.createdAt, createdAt) || other.createdAt == createdAt) && (identical(other.updatedAt, updatedAt) || @@ -606,12 +532,7 @@ class _$GamePrizeImpl implements _GamePrize { id, gameId, name, - weight, - stock, - maxStock, - threshold, const DeepCollectionEquality().hash(_metadata), - game, createdAt, updatedAt, ); @@ -630,12 +551,7 @@ abstract class _GamePrize implements GamePrize { required final String id, required final String gameId, required final String name, - required final int weight, - required final int stock, - required final int maxStock, - required final int threshold, required final Map metadata, - required final Game game, required final String createdAt, required final String updatedAt, }) = _$GamePrizeImpl; @@ -647,18 +563,8 @@ abstract class _GamePrize implements GamePrize { @override String get name; @override - int get weight; - @override - int get stock; - @override - int get maxStock; - @override - int get threshold; - @override Map get metadata; @override - Game get game; - @override String get createdAt; @override String get updatedAt; diff --git a/lib/domain/game/repositories/i_game_repository.dart b/lib/domain/game/repositories/i_game_repository.dart index e06917b..f297b14 100644 --- a/lib/domain/game/repositories/i_game_repository.dart +++ b/lib/domain/game/repositories/i_game_repository.dart @@ -1,7 +1,5 @@ part of '../game.dart'; abstract class IGameRepository { - Future>> gamePrizeByGameId({ - required String id, - }); + Future> ferrisWheel(); } diff --git a/lib/infrastructure/game/datasources/remote_data_provider.dart b/lib/infrastructure/game/datasources/remote_data_provider.dart index df528a7..8df1131 100644 --- a/lib/infrastructure/game/datasources/remote_data_provider.dart +++ b/lib/infrastructure/game/datasources/remote_data_provider.dart @@ -17,12 +17,10 @@ class GameRemoteDataProvider { GameRemoteDataProvider(this._apiClient); - Future>> gamePrizeByGameId({ - required String id, - }) async { + Future> ferrisWheel() async { try { final response = await _apiClient.get( - '${ApiPath.gamePrizeByGameId}/$id', + ApiPath.ferrisWheel, headers: getAuthorizationHeader(), ); @@ -38,12 +36,11 @@ class GameRemoteDataProvider { ); } - final dto = (response.data['data'] as List) - .map((e) => GamePrizeDto.fromJson(e)) - .toList(); + final dto = GameDto.fromJson(response.data['data']['data']['game']); + return DC.data(dto); } on ApiFailure catch (e, s) { - log('gamePrizeByGameId', name: _logName, error: e, stackTrace: s); + log('ferrisWheel', name: _logName, error: e, stackTrace: s); return DC.error(GameFailure.serverError(e)); } } diff --git a/lib/infrastructure/game/dto/game_dto.dart b/lib/infrastructure/game/dto/game_dto.dart index 329a8ca..f366d86 100644 --- a/lib/infrastructure/game/dto/game_dto.dart +++ b/lib/infrastructure/game/dto/game_dto.dart @@ -8,6 +8,7 @@ class GameDto with _$GameDto { @JsonKey(name: 'type') String? type, @JsonKey(name: 'is_active') bool? isActive, @JsonKey(name: 'metadata') Map? metadata, + @JsonKey(name: 'prizes') List? prizes, @JsonKey(name: 'created_at') String? createdAt, @JsonKey(name: 'updated_at') String? updatedAt, }) = _GameDto; @@ -22,7 +23,8 @@ class GameDto with _$GameDto { name: name ?? '', type: type ?? '', isActive: isActive ?? false, - metadata: metadata ?? const {}, + metadata: metadata ?? {}, + prizes: prizes?.map((e) => e.toDomain()).toList() ?? [], createdAt: createdAt ?? '', updatedAt: updatedAt ?? '', ); diff --git a/lib/infrastructure/game/dto/game_prize_dto.dart b/lib/infrastructure/game/dto/game_prize_dto.dart index 6f421b1..6ff0a5d 100644 --- a/lib/infrastructure/game/dto/game_prize_dto.dart +++ b/lib/infrastructure/game/dto/game_prize_dto.dart @@ -6,12 +6,7 @@ class GamePrizeDto with _$GamePrizeDto { @JsonKey(name: 'id') String? id, @JsonKey(name: 'game_id') String? gameId, @JsonKey(name: 'name') String? name, - @JsonKey(name: 'weight') int? weight, - @JsonKey(name: 'stock') int? stock, - @JsonKey(name: 'max_stock') int? maxStock, - @JsonKey(name: 'threshold') int? threshold, @JsonKey(name: 'metadata') Map? metadata, - @JsonKey(name: 'game') GameDto? game, @JsonKey(name: 'created_at') String? createdAt, @JsonKey(name: 'updated_at') String? updatedAt, }) = _GamePrizeDto; @@ -21,17 +16,11 @@ class GamePrizeDto with _$GamePrizeDto { const GamePrizeDto._(); - /// mapping ke domain GamePrize toDomain() => GamePrize( id: id ?? '', gameId: gameId ?? '', name: name ?? '', - weight: weight ?? 0, - stock: stock ?? 0, - maxStock: maxStock ?? 0, - threshold: threshold ?? 0, - metadata: metadata ?? const {}, - game: game?.toDomain() ?? Game.empty(), + metadata: metadata ?? {}, createdAt: createdAt ?? '', updatedAt: updatedAt ?? '', ); diff --git a/lib/infrastructure/game/game_dtos.freezed.dart b/lib/infrastructure/game/game_dtos.freezed.dart index dfb2473..3774c5f 100644 --- a/lib/infrastructure/game/game_dtos.freezed.dart +++ b/lib/infrastructure/game/game_dtos.freezed.dart @@ -31,6 +31,8 @@ mixin _$GameDto { bool? get isActive => throw _privateConstructorUsedError; @JsonKey(name: 'metadata') Map? get metadata => throw _privateConstructorUsedError; + @JsonKey(name: 'prizes') + List? get prizes => throw _privateConstructorUsedError; @JsonKey(name: 'created_at') String? get createdAt => throw _privateConstructorUsedError; @JsonKey(name: 'updated_at') @@ -56,6 +58,7 @@ abstract class $GameDtoCopyWith<$Res> { @JsonKey(name: 'type') String? type, @JsonKey(name: 'is_active') bool? isActive, @JsonKey(name: 'metadata') Map? metadata, + @JsonKey(name: 'prizes') List? prizes, @JsonKey(name: 'created_at') String? createdAt, @JsonKey(name: 'updated_at') String? updatedAt, }); @@ -81,6 +84,7 @@ class _$GameDtoCopyWithImpl<$Res, $Val extends GameDto> Object? type = freezed, Object? isActive = freezed, Object? metadata = freezed, + Object? prizes = freezed, Object? createdAt = freezed, Object? updatedAt = freezed, }) { @@ -106,6 +110,10 @@ class _$GameDtoCopyWithImpl<$Res, $Val extends GameDto> ? _value.metadata : metadata // ignore: cast_nullable_to_non_nullable as Map?, + prizes: freezed == prizes + ? _value.prizes + : prizes // ignore: cast_nullable_to_non_nullable + as List?, createdAt: freezed == createdAt ? _value.createdAt : createdAt // ignore: cast_nullable_to_non_nullable @@ -134,6 +142,7 @@ abstract class _$$GameDtoImplCopyWith<$Res> implements $GameDtoCopyWith<$Res> { @JsonKey(name: 'type') String? type, @JsonKey(name: 'is_active') bool? isActive, @JsonKey(name: 'metadata') Map? metadata, + @JsonKey(name: 'prizes') List? prizes, @JsonKey(name: 'created_at') String? createdAt, @JsonKey(name: 'updated_at') String? updatedAt, }); @@ -158,6 +167,7 @@ class __$$GameDtoImplCopyWithImpl<$Res> Object? type = freezed, Object? isActive = freezed, Object? metadata = freezed, + Object? prizes = freezed, Object? createdAt = freezed, Object? updatedAt = freezed, }) { @@ -183,6 +193,10 @@ class __$$GameDtoImplCopyWithImpl<$Res> ? _value._metadata : metadata // ignore: cast_nullable_to_non_nullable as Map?, + prizes: freezed == prizes + ? _value._prizes + : prizes // ignore: cast_nullable_to_non_nullable + as List?, createdAt: freezed == createdAt ? _value.createdAt : createdAt // ignore: cast_nullable_to_non_nullable @@ -205,9 +219,11 @@ class _$GameDtoImpl extends _GameDto { @JsonKey(name: 'type') this.type, @JsonKey(name: 'is_active') this.isActive, @JsonKey(name: 'metadata') final Map? metadata, + @JsonKey(name: 'prizes') final List? prizes, @JsonKey(name: 'created_at') this.createdAt, @JsonKey(name: 'updated_at') this.updatedAt, }) : _metadata = metadata, + _prizes = prizes, super._(); factory _$GameDtoImpl.fromJson(Map json) => @@ -236,6 +252,17 @@ class _$GameDtoImpl extends _GameDto { return EqualUnmodifiableMapView(value); } + final List? _prizes; + @override + @JsonKey(name: 'prizes') + List? get prizes { + final value = _prizes; + if (value == null) return null; + if (_prizes is EqualUnmodifiableListView) return _prizes; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); + } + @override @JsonKey(name: 'created_at') final String? createdAt; @@ -245,7 +272,7 @@ class _$GameDtoImpl extends _GameDto { @override String toString() { - return 'GameDto(id: $id, name: $name, type: $type, isActive: $isActive, metadata: $metadata, createdAt: $createdAt, updatedAt: $updatedAt)'; + return 'GameDto(id: $id, name: $name, type: $type, isActive: $isActive, metadata: $metadata, prizes: $prizes, createdAt: $createdAt, updatedAt: $updatedAt)'; } @override @@ -259,6 +286,7 @@ class _$GameDtoImpl extends _GameDto { (identical(other.isActive, isActive) || other.isActive == isActive) && const DeepCollectionEquality().equals(other._metadata, _metadata) && + const DeepCollectionEquality().equals(other._prizes, _prizes) && (identical(other.createdAt, createdAt) || other.createdAt == createdAt) && (identical(other.updatedAt, updatedAt) || @@ -274,6 +302,7 @@ class _$GameDtoImpl extends _GameDto { type, isActive, const DeepCollectionEquality().hash(_metadata), + const DeepCollectionEquality().hash(_prizes), createdAt, updatedAt, ); @@ -299,6 +328,7 @@ abstract class _GameDto extends GameDto { @JsonKey(name: 'type') final String? type, @JsonKey(name: 'is_active') final bool? isActive, @JsonKey(name: 'metadata') final Map? metadata, + @JsonKey(name: 'prizes') final List? prizes, @JsonKey(name: 'created_at') final String? createdAt, @JsonKey(name: 'updated_at') final String? updatedAt, }) = _$GameDtoImpl; @@ -322,6 +352,9 @@ abstract class _GameDto extends GameDto { @JsonKey(name: 'metadata') Map? get metadata; @override + @JsonKey(name: 'prizes') + List? get prizes; + @override @JsonKey(name: 'created_at') String? get createdAt; @override @@ -348,18 +381,8 @@ mixin _$GamePrizeDto { String? get gameId => throw _privateConstructorUsedError; @JsonKey(name: 'name') String? get name => throw _privateConstructorUsedError; - @JsonKey(name: 'weight') - int? get weight => throw _privateConstructorUsedError; - @JsonKey(name: 'stock') - int? get stock => throw _privateConstructorUsedError; - @JsonKey(name: 'max_stock') - int? get maxStock => throw _privateConstructorUsedError; - @JsonKey(name: 'threshold') - int? get threshold => throw _privateConstructorUsedError; @JsonKey(name: 'metadata') Map? get metadata => throw _privateConstructorUsedError; - @JsonKey(name: 'game') - GameDto? get game => throw _privateConstructorUsedError; @JsonKey(name: 'created_at') String? get createdAt => throw _privateConstructorUsedError; @JsonKey(name: 'updated_at') @@ -386,17 +409,10 @@ abstract class $GamePrizeDtoCopyWith<$Res> { @JsonKey(name: 'id') String? id, @JsonKey(name: 'game_id') String? gameId, @JsonKey(name: 'name') String? name, - @JsonKey(name: 'weight') int? weight, - @JsonKey(name: 'stock') int? stock, - @JsonKey(name: 'max_stock') int? maxStock, - @JsonKey(name: 'threshold') int? threshold, @JsonKey(name: 'metadata') Map? metadata, - @JsonKey(name: 'game') GameDto? game, @JsonKey(name: 'created_at') String? createdAt, @JsonKey(name: 'updated_at') String? updatedAt, }); - - $GameDtoCopyWith<$Res>? get game; } /// @nodoc @@ -417,12 +433,7 @@ class _$GamePrizeDtoCopyWithImpl<$Res, $Val extends GamePrizeDto> Object? id = freezed, Object? gameId = freezed, Object? name = freezed, - Object? weight = freezed, - Object? stock = freezed, - Object? maxStock = freezed, - Object? threshold = freezed, Object? metadata = freezed, - Object? game = freezed, Object? createdAt = freezed, Object? updatedAt = freezed, }) { @@ -440,30 +451,10 @@ class _$GamePrizeDtoCopyWithImpl<$Res, $Val extends GamePrizeDto> ? _value.name : name // ignore: cast_nullable_to_non_nullable as String?, - weight: freezed == weight - ? _value.weight - : weight // ignore: cast_nullable_to_non_nullable - as int?, - stock: freezed == stock - ? _value.stock - : stock // ignore: cast_nullable_to_non_nullable - as int?, - maxStock: freezed == maxStock - ? _value.maxStock - : maxStock // ignore: cast_nullable_to_non_nullable - as int?, - threshold: freezed == threshold - ? _value.threshold - : threshold // ignore: cast_nullable_to_non_nullable - as int?, metadata: freezed == metadata ? _value.metadata : metadata // ignore: cast_nullable_to_non_nullable as Map?, - game: freezed == game - ? _value.game - : game // ignore: cast_nullable_to_non_nullable - as GameDto?, createdAt: freezed == createdAt ? _value.createdAt : createdAt // ignore: cast_nullable_to_non_nullable @@ -476,20 +467,6 @@ class _$GamePrizeDtoCopyWithImpl<$Res, $Val extends GamePrizeDto> as $Val, ); } - - /// Create a copy of GamePrizeDto - /// with the given fields replaced by the non-null parameter values. - @override - @pragma('vm:prefer-inline') - $GameDtoCopyWith<$Res>? get game { - if (_value.game == null) { - return null; - } - - return $GameDtoCopyWith<$Res>(_value.game!, (value) { - return _then(_value.copyWith(game: value) as $Val); - }); - } } /// @nodoc @@ -505,18 +482,10 @@ abstract class _$$GamePrizeDtoImplCopyWith<$Res> @JsonKey(name: 'id') String? id, @JsonKey(name: 'game_id') String? gameId, @JsonKey(name: 'name') String? name, - @JsonKey(name: 'weight') int? weight, - @JsonKey(name: 'stock') int? stock, - @JsonKey(name: 'max_stock') int? maxStock, - @JsonKey(name: 'threshold') int? threshold, @JsonKey(name: 'metadata') Map? metadata, - @JsonKey(name: 'game') GameDto? game, @JsonKey(name: 'created_at') String? createdAt, @JsonKey(name: 'updated_at') String? updatedAt, }); - - @override - $GameDtoCopyWith<$Res>? get game; } /// @nodoc @@ -536,12 +505,7 @@ class __$$GamePrizeDtoImplCopyWithImpl<$Res> Object? id = freezed, Object? gameId = freezed, Object? name = freezed, - Object? weight = freezed, - Object? stock = freezed, - Object? maxStock = freezed, - Object? threshold = freezed, Object? metadata = freezed, - Object? game = freezed, Object? createdAt = freezed, Object? updatedAt = freezed, }) { @@ -559,30 +523,10 @@ class __$$GamePrizeDtoImplCopyWithImpl<$Res> ? _value.name : name // ignore: cast_nullable_to_non_nullable as String?, - weight: freezed == weight - ? _value.weight - : weight // ignore: cast_nullable_to_non_nullable - as int?, - stock: freezed == stock - ? _value.stock - : stock // ignore: cast_nullable_to_non_nullable - as int?, - maxStock: freezed == maxStock - ? _value.maxStock - : maxStock // ignore: cast_nullable_to_non_nullable - as int?, - threshold: freezed == threshold - ? _value.threshold - : threshold // ignore: cast_nullable_to_non_nullable - as int?, metadata: freezed == metadata ? _value._metadata : metadata // ignore: cast_nullable_to_non_nullable as Map?, - game: freezed == game - ? _value.game - : game // ignore: cast_nullable_to_non_nullable - as GameDto?, createdAt: freezed == createdAt ? _value.createdAt : createdAt // ignore: cast_nullable_to_non_nullable @@ -603,12 +547,7 @@ class _$GamePrizeDtoImpl extends _GamePrizeDto { @JsonKey(name: 'id') this.id, @JsonKey(name: 'game_id') this.gameId, @JsonKey(name: 'name') this.name, - @JsonKey(name: 'weight') this.weight, - @JsonKey(name: 'stock') this.stock, - @JsonKey(name: 'max_stock') this.maxStock, - @JsonKey(name: 'threshold') this.threshold, @JsonKey(name: 'metadata') final Map? metadata, - @JsonKey(name: 'game') this.game, @JsonKey(name: 'created_at') this.createdAt, @JsonKey(name: 'updated_at') this.updatedAt, }) : _metadata = metadata, @@ -626,18 +565,6 @@ class _$GamePrizeDtoImpl extends _GamePrizeDto { @override @JsonKey(name: 'name') final String? name; - @override - @JsonKey(name: 'weight') - final int? weight; - @override - @JsonKey(name: 'stock') - final int? stock; - @override - @JsonKey(name: 'max_stock') - final int? maxStock; - @override - @JsonKey(name: 'threshold') - final int? threshold; final Map? _metadata; @override @JsonKey(name: 'metadata') @@ -649,9 +576,6 @@ class _$GamePrizeDtoImpl extends _GamePrizeDto { return EqualUnmodifiableMapView(value); } - @override - @JsonKey(name: 'game') - final GameDto? game; @override @JsonKey(name: 'created_at') final String? createdAt; @@ -661,7 +585,7 @@ class _$GamePrizeDtoImpl extends _GamePrizeDto { @override String toString() { - return 'GamePrizeDto(id: $id, gameId: $gameId, name: $name, weight: $weight, stock: $stock, maxStock: $maxStock, threshold: $threshold, metadata: $metadata, game: $game, createdAt: $createdAt, updatedAt: $updatedAt)'; + return 'GamePrizeDto(id: $id, gameId: $gameId, name: $name, metadata: $metadata, createdAt: $createdAt, updatedAt: $updatedAt)'; } @override @@ -672,14 +596,7 @@ class _$GamePrizeDtoImpl extends _GamePrizeDto { (identical(other.id, id) || other.id == id) && (identical(other.gameId, gameId) || other.gameId == gameId) && (identical(other.name, name) || other.name == name) && - (identical(other.weight, weight) || other.weight == weight) && - (identical(other.stock, stock) || other.stock == stock) && - (identical(other.maxStock, maxStock) || - other.maxStock == maxStock) && - (identical(other.threshold, threshold) || - other.threshold == threshold) && const DeepCollectionEquality().equals(other._metadata, _metadata) && - (identical(other.game, game) || other.game == game) && (identical(other.createdAt, createdAt) || other.createdAt == createdAt) && (identical(other.updatedAt, updatedAt) || @@ -693,12 +610,7 @@ class _$GamePrizeDtoImpl extends _GamePrizeDto { id, gameId, name, - weight, - stock, - maxStock, - threshold, const DeepCollectionEquality().hash(_metadata), - game, createdAt, updatedAt, ); @@ -722,12 +634,7 @@ abstract class _GamePrizeDto extends GamePrizeDto { @JsonKey(name: 'id') final String? id, @JsonKey(name: 'game_id') final String? gameId, @JsonKey(name: 'name') final String? name, - @JsonKey(name: 'weight') final int? weight, - @JsonKey(name: 'stock') final int? stock, - @JsonKey(name: 'max_stock') final int? maxStock, - @JsonKey(name: 'threshold') final int? threshold, @JsonKey(name: 'metadata') final Map? metadata, - @JsonKey(name: 'game') final GameDto? game, @JsonKey(name: 'created_at') final String? createdAt, @JsonKey(name: 'updated_at') final String? updatedAt, }) = _$GamePrizeDtoImpl; @@ -746,24 +653,9 @@ abstract class _GamePrizeDto extends GamePrizeDto { @JsonKey(name: 'name') String? get name; @override - @JsonKey(name: 'weight') - int? get weight; - @override - @JsonKey(name: 'stock') - int? get stock; - @override - @JsonKey(name: 'max_stock') - int? get maxStock; - @override - @JsonKey(name: 'threshold') - int? get threshold; - @override @JsonKey(name: 'metadata') Map? get metadata; @override - @JsonKey(name: 'game') - GameDto? get game; - @override @JsonKey(name: 'created_at') String? get createdAt; @override diff --git a/lib/infrastructure/game/game_dtos.g.dart b/lib/infrastructure/game/game_dtos.g.dart index da51145..e159635 100644 --- a/lib/infrastructure/game/game_dtos.g.dart +++ b/lib/infrastructure/game/game_dtos.g.dart @@ -13,6 +13,9 @@ _$GameDtoImpl _$$GameDtoImplFromJson(Map json) => type: json['type'] as String?, isActive: json['is_active'] as bool?, metadata: json['metadata'] as Map?, + prizes: (json['prizes'] as List?) + ?.map((e) => GamePrizeDto.fromJson(e as Map)) + .toList(), createdAt: json['created_at'] as String?, updatedAt: json['updated_at'] as String?, ); @@ -24,6 +27,7 @@ Map _$$GameDtoImplToJson(_$GameDtoImpl instance) => 'type': instance.type, 'is_active': instance.isActive, 'metadata': instance.metadata, + 'prizes': instance.prizes, 'created_at': instance.createdAt, 'updated_at': instance.updatedAt, }; @@ -33,14 +37,7 @@ _$GamePrizeDtoImpl _$$GamePrizeDtoImplFromJson(Map json) => id: json['id'] as String?, gameId: json['game_id'] as String?, name: json['name'] as String?, - weight: (json['weight'] as num?)?.toInt(), - stock: (json['stock'] as num?)?.toInt(), - maxStock: (json['max_stock'] as num?)?.toInt(), - threshold: (json['threshold'] as num?)?.toInt(), metadata: json['metadata'] as Map?, - game: json['game'] == null - ? null - : GameDto.fromJson(json['game'] as Map), createdAt: json['created_at'] as String?, updatedAt: json['updated_at'] as String?, ); @@ -50,12 +47,7 @@ Map _$$GamePrizeDtoImplToJson(_$GamePrizeDtoImpl instance) => 'id': instance.id, 'game_id': instance.gameId, 'name': instance.name, - 'weight': instance.weight, - 'stock': instance.stock, - 'max_stock': instance.maxStock, - 'threshold': instance.threshold, 'metadata': instance.metadata, - 'game': instance.game, 'created_at': instance.createdAt, 'updated_at': instance.updatedAt, }; diff --git a/lib/infrastructure/game/repositories/game_repository.dart b/lib/infrastructure/game/repositories/game_repository.dart index a195e56..b038d26 100644 --- a/lib/infrastructure/game/repositories/game_repository.dart +++ b/lib/infrastructure/game/repositories/game_repository.dart @@ -15,21 +15,19 @@ class GameRepository implements IGameRepository { GameRepository(this._remoteDataProvider); @override - Future>> gamePrizeByGameId({ - required String id, - }) async { + Future> ferrisWheel() async { try { - final result = await _remoteDataProvider.gamePrizeByGameId(id: id); + final result = await _remoteDataProvider.ferrisWheel(); if (result.hasError) { return left(result.error!); } - final data = result.data!.map((e) => e.toDomain()).toList(); + final data = result.data!.toDomain(); return right(data); } catch (e, s) { - log('gamePrizeByGameId', name: _logName, error: e, stackTrace: s); + log('ferrisWheel', name: _logName, error: e, stackTrace: s); return left(const GameFailure.unexpectedError()); } } diff --git a/lib/injection.config.dart b/lib/injection.config.dart index ded36e4..5bcec36 100644 --- a/lib/injection.config.dart +++ b/lib/injection.config.dart @@ -28,8 +28,8 @@ import 'package:enaklo/application/auth/verify_form/verify_form_bloc.dart' as _i521; import 'package:enaklo/application/customer/customer_point_loader/customer_point_loader_bloc.dart' as _i497; -import 'package:enaklo/application/game/game_price_loader/game_prize_loader_bloc.dart' - as _i925; +import 'package:enaklo/application/game/ferris_wheel_loader/ferris_wheel_loader_bloc.dart' + as _i1013; import 'package:enaklo/common/api/api_client.dart' as _i842; import 'package:enaklo/common/di/di_auto_route.dart' as _i619; import 'package:enaklo/common/di/di_connectivity.dart' as _i644; @@ -103,8 +103,8 @@ extension GetItInjectableX on _i174.GetIt { gh.factory<_i96.IGameRepository>( () => _i547.GameRepository(gh<_i143.GameRemoteDataProvider>()), ); - gh.factory<_i925.GamePrizeLoaderBloc>( - () => _i925.GamePrizeLoaderBloc(gh<_i96.IGameRepository>()), + gh.factory<_i1013.FerrisWheelLoaderBloc>( + () => _i1013.FerrisWheelLoaderBloc(gh<_i96.IGameRepository>()), ); gh.factory<_i995.IAuthRepository>( () => _i879.AuthRepository( diff --git a/lib/presentation/pages/mini_games/ferris_wheel/ferris_wheel_page.dart b/lib/presentation/pages/mini_games/ferris_wheel/ferris_wheel_page.dart index 80bd2f0..605a945 100644 --- a/lib/presentation/pages/mini_games/ferris_wheel/ferris_wheel_page.dart +++ b/lib/presentation/pages/mini_games/ferris_wheel/ferris_wheel_page.dart @@ -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() - ..add( - const GamePrizeLoaderEvent.fetched( - '28d5aed3-4c1b-4b7b-bad5-67cdd4919dc2', - ), - ), + create: (context) => + getIt() + ..add(const FerrisWheelLoaderEvent.fetched()), child: this, ); } @@ -84,12 +99,10 @@ class _FerrisWheelPageState extends State 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 } } - 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 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 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 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 }); _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( - 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(begin: currentPos, end: finalRotation) + .animate( CurvedAnimation( parent: _rotationController, curve: Curves.easeOutCubic, @@ -270,27 +254,20 @@ class _FerrisWheelPageState extends State _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 }); _showWinDialog(wonPrize); - _idleRotationController.reset(); _idleRotationController.repeat(); }); } @@ -363,15 +339,6 @@ class _FerrisWheelPageState extends State 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 @override Widget build(BuildContext context) { - return BlocBuilder( + return BlocBuilder( builder: (context, state) { if (state.isFetching) { return Scaffold( @@ -425,10 +392,10 @@ class _FerrisWheelPageState extends State ); } - 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 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 ); } + 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( - 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( + 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 ); } + 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 ), ), 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 borderRadius: BorderRadius.circular(15), ), child: Text( - 'Weight: ${prize.weight}', + 'Hadiah', style: AppStyle.xs.copyWith( color: _getPrizeColor(prize, index), fontWeight: FontWeight.bold,