From 3c596461c60524dac4072e35769861161f5ac2f4 Mon Sep 17 00:00:00 2001 From: efrilm Date: Thu, 18 Sep 2025 10:39:54 +0700 Subject: [PATCH] Game Prize and Logout --- .../auth/logout_form/logout_form_bloc.dart | 37 + .../logout_form/logout_form_bloc.freezed.dart | 335 +++++ .../auth/logout_form/logout_form_event.dart | 6 + .../auth/logout_form/logout_form_state.dart | 12 + .../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 + .../unauthorized_interceptor.dart | 29 +- lib/common/function/app_function.dart | 11 + lib/common/painter/wheel_painter.dart | 101 +- lib/common/url/api_path.dart | 3 + lib/domain/game/entity/game_entity.dart | 24 + lib/domain/game/entity/game_prize_entity.dart | 32 + lib/domain/game/failures/game_failure.dart | 9 + lib/domain/game/game.dart | 11 + lib/domain/game/game.freezed.dart | 1162 +++++++++++++++++ .../game/repositories/i_game_repository.dart | 7 + .../datasources/remote_data_provider.dart | 50 + lib/infrastructure/game/dto/game_dto.dart | 29 + .../game/dto/game_prize_dto.dart | 38 + lib/infrastructure/game/game_dtos.dart | 9 + .../game/game_dtos.freezed.dart | 779 +++++++++++ lib/infrastructure/game/game_dtos.g.dart | 61 + .../game/repositories/game_repository.dart | 36 + lib/injection.config.dart | 21 + lib/presentation/app_widget.dart | 6 +- .../main/pages/profile/profile_page.dart | 628 ++++----- .../mini_games/ferris_wheel/data/model.dart | 69 + .../ferris_wheel/ferris_wheel_page.dart | 858 +++++++----- lib/presentation/router/app_router.gr.dart | 2 +- 31 files changed, 4189 insertions(+), 677 deletions(-) create mode 100644 lib/application/auth/logout_form/logout_form_bloc.dart create mode 100644 lib/application/auth/logout_form/logout_form_bloc.freezed.dart create mode 100644 lib/application/auth/logout_form/logout_form_event.dart create mode 100644 lib/application/auth/logout_form/logout_form_state.dart create mode 100644 lib/application/game/game_price_loader/game_prize_loader_bloc.dart create mode 100644 lib/application/game/game_price_loader/game_prize_loader_bloc.freezed.dart create mode 100644 lib/application/game/game_price_loader/game_prize_loader_event.dart create mode 100644 lib/application/game/game_price_loader/game_prize_loader_state.dart create mode 100644 lib/domain/game/entity/game_entity.dart create mode 100644 lib/domain/game/entity/game_prize_entity.dart create mode 100644 lib/domain/game/failures/game_failure.dart create mode 100644 lib/domain/game/game.dart create mode 100644 lib/domain/game/game.freezed.dart create mode 100644 lib/domain/game/repositories/i_game_repository.dart create mode 100644 lib/infrastructure/game/datasources/remote_data_provider.dart create mode 100644 lib/infrastructure/game/dto/game_dto.dart create mode 100644 lib/infrastructure/game/dto/game_prize_dto.dart create mode 100644 lib/infrastructure/game/game_dtos.dart create mode 100644 lib/infrastructure/game/game_dtos.freezed.dart create mode 100644 lib/infrastructure/game/game_dtos.g.dart create mode 100644 lib/infrastructure/game/repositories/game_repository.dart diff --git a/lib/application/auth/logout_form/logout_form_bloc.dart b/lib/application/auth/logout_form/logout_form_bloc.dart new file mode 100644 index 0000000..e07ca2f --- /dev/null +++ b/lib/application/auth/logout_form/logout_form_bloc.dart @@ -0,0 +1,37 @@ +import 'package:bloc/bloc.dart'; +import 'package:dartz/dartz.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:injectable/injectable.dart'; + +import '../../../domain/auth/auth.dart'; + +part 'logout_form_event.dart'; +part 'logout_form_state.dart'; +part 'logout_form_bloc.freezed.dart'; + +@injectable +class LogoutFormBloc extends Bloc { + final IAuthRepository _repository; + + LogoutFormBloc(this._repository) : super(LogoutFormState.initial()) { + on(_onLogoutFormEvent); + } + + Future _onLogoutFormEvent( + LogoutFormEvent event, + Emitter emit, + ) { + return event.map( + submitted: (e) async { + emit(state.copyWith(isSubmitting: true, failureOrAuthOption: none())); + final failureOrAuth = await _repository.logout(); + emit( + state.copyWith( + isSubmitting: false, + failureOrAuthOption: optionOf(failureOrAuth), + ), + ); + }, + ); + } +} diff --git a/lib/application/auth/logout_form/logout_form_bloc.freezed.dart b/lib/application/auth/logout_form/logout_form_bloc.freezed.dart new file mode 100644 index 0000000..2bdad02 --- /dev/null +++ b/lib/application/auth/logout_form/logout_form_bloc.freezed.dart @@ -0,0 +1,335 @@ +// 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 'logout_form_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 _$LogoutFormEvent { + @optionalTypeArgs + TResult when({ + required TResult Function() submitted, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? submitted, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? submitted, + required TResult orElse(), + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_Submitted value) submitted, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Submitted value)? submitted, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Submitted value)? submitted, + required TResult orElse(), + }) => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $LogoutFormEventCopyWith<$Res> { + factory $LogoutFormEventCopyWith( + LogoutFormEvent value, + $Res Function(LogoutFormEvent) then, + ) = _$LogoutFormEventCopyWithImpl<$Res, LogoutFormEvent>; +} + +/// @nodoc +class _$LogoutFormEventCopyWithImpl<$Res, $Val extends LogoutFormEvent> + implements $LogoutFormEventCopyWith<$Res> { + _$LogoutFormEventCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of LogoutFormEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc +abstract class _$$SubmittedImplCopyWith<$Res> { + factory _$$SubmittedImplCopyWith( + _$SubmittedImpl value, + $Res Function(_$SubmittedImpl) then, + ) = __$$SubmittedImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$SubmittedImplCopyWithImpl<$Res> + extends _$LogoutFormEventCopyWithImpl<$Res, _$SubmittedImpl> + implements _$$SubmittedImplCopyWith<$Res> { + __$$SubmittedImplCopyWithImpl( + _$SubmittedImpl _value, + $Res Function(_$SubmittedImpl) _then, + ) : super(_value, _then); + + /// Create a copy of LogoutFormEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$SubmittedImpl implements _Submitted { + const _$SubmittedImpl(); + + @override + String toString() { + return 'LogoutFormEvent.submitted()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$SubmittedImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() submitted, + }) { + return submitted(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? submitted, + }) { + return submitted?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? submitted, + required TResult orElse(), + }) { + if (submitted != null) { + return submitted(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Submitted value) submitted, + }) { + return submitted(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Submitted value)? submitted, + }) { + return submitted?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Submitted value)? submitted, + required TResult orElse(), + }) { + if (submitted != null) { + return submitted(this); + } + return orElse(); + } +} + +abstract class _Submitted implements LogoutFormEvent { + const factory _Submitted() = _$SubmittedImpl; +} + +/// @nodoc +mixin _$LogoutFormState { + Option> get failureOrAuthOption => + throw _privateConstructorUsedError; + bool get isSubmitting => throw _privateConstructorUsedError; + + /// Create a copy of LogoutFormState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $LogoutFormStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $LogoutFormStateCopyWith<$Res> { + factory $LogoutFormStateCopyWith( + LogoutFormState value, + $Res Function(LogoutFormState) then, + ) = _$LogoutFormStateCopyWithImpl<$Res, LogoutFormState>; + @useResult + $Res call({ + Option> failureOrAuthOption, + bool isSubmitting, + }); +} + +/// @nodoc +class _$LogoutFormStateCopyWithImpl<$Res, $Val extends LogoutFormState> + implements $LogoutFormStateCopyWith<$Res> { + _$LogoutFormStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of LogoutFormState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? failureOrAuthOption = null, Object? isSubmitting = null}) { + return _then( + _value.copyWith( + failureOrAuthOption: null == failureOrAuthOption + ? _value.failureOrAuthOption + : failureOrAuthOption // ignore: cast_nullable_to_non_nullable + as Option>, + isSubmitting: null == isSubmitting + ? _value.isSubmitting + : isSubmitting // ignore: cast_nullable_to_non_nullable + as bool, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$LogoutFormStateImplCopyWith<$Res> + implements $LogoutFormStateCopyWith<$Res> { + factory _$$LogoutFormStateImplCopyWith( + _$LogoutFormStateImpl value, + $Res Function(_$LogoutFormStateImpl) then, + ) = __$$LogoutFormStateImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + Option> failureOrAuthOption, + bool isSubmitting, + }); +} + +/// @nodoc +class __$$LogoutFormStateImplCopyWithImpl<$Res> + extends _$LogoutFormStateCopyWithImpl<$Res, _$LogoutFormStateImpl> + implements _$$LogoutFormStateImplCopyWith<$Res> { + __$$LogoutFormStateImplCopyWithImpl( + _$LogoutFormStateImpl _value, + $Res Function(_$LogoutFormStateImpl) _then, + ) : super(_value, _then); + + /// Create a copy of LogoutFormState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? failureOrAuthOption = null, Object? isSubmitting = null}) { + return _then( + _$LogoutFormStateImpl( + failureOrAuthOption: null == failureOrAuthOption + ? _value.failureOrAuthOption + : failureOrAuthOption // ignore: cast_nullable_to_non_nullable + as Option>, + isSubmitting: null == isSubmitting + ? _value.isSubmitting + : isSubmitting // ignore: cast_nullable_to_non_nullable + as bool, + ), + ); + } +} + +/// @nodoc + +class _$LogoutFormStateImpl implements _LogoutFormState { + const _$LogoutFormStateImpl({ + required this.failureOrAuthOption, + this.isSubmitting = false, + }); + + @override + final Option> failureOrAuthOption; + @override + @JsonKey() + final bool isSubmitting; + + @override + String toString() { + return 'LogoutFormState(failureOrAuthOption: $failureOrAuthOption, isSubmitting: $isSubmitting)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$LogoutFormStateImpl && + (identical(other.failureOrAuthOption, failureOrAuthOption) || + other.failureOrAuthOption == failureOrAuthOption) && + (identical(other.isSubmitting, isSubmitting) || + other.isSubmitting == isSubmitting)); + } + + @override + int get hashCode => + Object.hash(runtimeType, failureOrAuthOption, isSubmitting); + + /// Create a copy of LogoutFormState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$LogoutFormStateImplCopyWith<_$LogoutFormStateImpl> get copyWith => + __$$LogoutFormStateImplCopyWithImpl<_$LogoutFormStateImpl>( + this, + _$identity, + ); +} + +abstract class _LogoutFormState implements LogoutFormState { + const factory _LogoutFormState({ + required final Option> failureOrAuthOption, + final bool isSubmitting, + }) = _$LogoutFormStateImpl; + + @override + Option> get failureOrAuthOption; + @override + bool get isSubmitting; + + /// Create a copy of LogoutFormState + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$LogoutFormStateImplCopyWith<_$LogoutFormStateImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/application/auth/logout_form/logout_form_event.dart b/lib/application/auth/logout_form/logout_form_event.dart new file mode 100644 index 0000000..606bf91 --- /dev/null +++ b/lib/application/auth/logout_form/logout_form_event.dart @@ -0,0 +1,6 @@ +part of 'logout_form_bloc.dart'; + +@freezed +class LogoutFormEvent with _$LogoutFormEvent { + const factory LogoutFormEvent.submitted() = _Submitted; +} diff --git a/lib/application/auth/logout_form/logout_form_state.dart b/lib/application/auth/logout_form/logout_form_state.dart new file mode 100644 index 0000000..613c5e1 --- /dev/null +++ b/lib/application/auth/logout_form/logout_form_state.dart @@ -0,0 +1,12 @@ +part of 'logout_form_bloc.dart'; + +@freezed +class LogoutFormState with _$LogoutFormState { + const factory LogoutFormState({ + required Option> failureOrAuthOption, + @Default(false) bool isSubmitting, + }) = _LogoutFormState; + + factory LogoutFormState.initial() => + LogoutFormState(failureOrAuthOption: none(), isSubmitting: false); +} 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 new file mode 100644 index 0000000..bb1ad73 --- /dev/null +++ b/lib/application/game/game_price_loader/game_prize_loader_bloc.dart @@ -0,0 +1,40 @@ +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 new file mode 100644 index 0000000..8c22ad9 --- /dev/null +++ b/lib/application/game/game_price_loader/game_prize_loader_bloc.freezed.dart @@ -0,0 +1,442 @@ +// 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 new file mode 100644 index 0000000..ad7b58a --- /dev/null +++ b/lib/application/game/game_price_loader/game_prize_loader_event.dart @@ -0,0 +1,6 @@ +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 new file mode 100644 index 0000000..50258c5 --- /dev/null +++ b/lib/application/game/game_price_loader/game_prize_loader_state.dart @@ -0,0 +1,13 @@ +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/api/interceptors/unauthorized_interceptor.dart b/lib/common/api/interceptors/unauthorized_interceptor.dart index 36752e0..646ef40 100644 --- a/lib/common/api/interceptors/unauthorized_interceptor.dart +++ b/lib/common/api/interceptors/unauthorized_interceptor.dart @@ -1,15 +1,40 @@ -import 'package:dio/dio.dart'; +import 'dart:developer'; +import 'package:auto_route/auto_route.dart'; +import 'package:dio/dio.dart'; +import 'package:flutter/material.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import '../../../presentation/router/app_router.gr.dart'; +import '../../constant/local_storage_key.dart'; import '../errors/unauthorized_error.dart'; class UnauthorizedInterceptor extends Interceptor { + static final GlobalKey navigatorKey = + GlobalKey(); @override - void onError(DioException err, ErrorInterceptorHandler handler) { + void onError(DioException err, ErrorInterceptorHandler handler) async { if (err.response?.statusCode == 401 || err.response?.statusCode == 403 || err.response?.statusCode == 419) { + await _handleTokenExpired(); return super.onError(UnauthorizedError(err, null), handler); } super.onError(err, handler); } + + Future _handleTokenExpired() async { + // Clear stored token + final prefs = await SharedPreferences.getInstance(); + await prefs.remove(LocalStorageKey.token); + await prefs.remove(LocalStorageKey.user); + await prefs.clear(); // Optional: clear all user data + log('handleTokenExpired'); + // Navigate to login page + final context = navigatorKey.currentContext; + if (context != null) { + // Option 1: Navigate and remove all previous routes + context.router.replaceAll([LoginRoute()]); + } + } } diff --git a/lib/common/function/app_function.dart b/lib/common/function/app_function.dart index 3b11f3d..d140a91 100644 --- a/lib/common/function/app_function.dart +++ b/lib/common/function/app_function.dart @@ -1,4 +1,8 @@ import 'package:flutter/material.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import '../../injection.dart'; +import '../constant/local_storage_key.dart'; void dismissKeyboard(BuildContext context) { final currentFocus = FocusScope.of(context); @@ -13,3 +17,10 @@ String getNormalizePhone(String phoneNumber) { : phoneNumber; return '62$normalizedPhone'; } + +Map getAuthorizationHeader() { + return { + 'Authorization': + 'Bearer ${getIt().getString(LocalStorageKey.token)}', + }; +} diff --git a/lib/common/painter/wheel_painter.dart b/lib/common/painter/wheel_painter.dart index 2c2545c..c0b72e8 100644 --- a/lib/common/painter/wheel_painter.dart +++ b/lib/common/painter/wheel_painter.dart @@ -1,19 +1,22 @@ import 'dart:math' as math; import 'package:flutter/material.dart'; -import '../../presentation/pages/mini_games/ferris_wheel/data/model.dart'; +import '../../domain/game/game.dart'; import '../theme/theme.dart'; class WheelPainter extends CustomPainter { - final List sections; + final List gamePrizes; + final Color Function(GamePrize prize, int index) getPrizeColor; - WheelPainter({required this.sections}); + WheelPainter({required this.gamePrizes, required this.getPrizeColor}); @override void paint(Canvas canvas, Size size) { + if (gamePrizes.isEmpty) return; + final center = Offset(size.width / 2, size.height / 2); final radius = size.width / 2; - final sectionAngle = 2 * math.pi / sections.length; + final sectionAngle = 2 * math.pi / gamePrizes.length; // Draw outer white border final outerBorderPaint = Paint() @@ -34,12 +37,13 @@ class WheelPainter extends CustomPainter { canvas.drawCircle(center, radius - 20, innerWhitePaint); // Draw sections - for (int i = 0; i < sections.length; i++) { + for (int i = 0; i < gamePrizes.length; i++) { + final prize = gamePrizes[i]; final startAngle = i * sectionAngle - math.pi / 2; // Section background final sectionPaint = Paint() - ..color = sections[i].color + ..color = getPrizeColor(prize, i) ..style = PaintingStyle.fill; canvas.drawArc( @@ -67,15 +71,16 @@ class WheelPainter extends CustomPainter { canvas.save(); canvas.translate(iconPosition.dx, iconPosition.dy); - // Draw icon using TextPainter to simulate Icon widget - final iconText = _getIconText(sections[i].icon); + // 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: sections[i].color, + color: getPrizeColor(prize, i), fontWeight: FontWeight.normal, ), ), @@ -103,7 +108,7 @@ class WheelPainter extends CustomPainter { final textPainter = TextPainter( text: TextSpan( - text: sections[i].prize, + text: prize.name, style: const TextStyle( color: Colors.white, fontSize: 10, @@ -119,6 +124,42 @@ class WheelPainter extends CustomPainter { ); 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 @@ -141,7 +182,7 @@ class WheelPainter extends CustomPainter { ..style = PaintingStyle.stroke ..strokeWidth = 2; - for (int i = 0; i < sections.length; i++) { + for (int i = 0; i < gamePrizes.length; i++) { final angle = i * sectionAngle - math.pi / 2; final lineStart = Offset( center.dx + (radius - 130) * math.cos(angle), @@ -155,6 +196,34 @@ 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) { @@ -174,11 +243,17 @@ class WheelPainter extends CustomPainter { 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(0xe87c); // Default star icon + return String.fromCharCode(0xe151); // Default card_giftcard icon } } @override - bool shouldRepaint(covariant CustomPainter oldDelegate) => false; + bool shouldRepaint(covariant CustomPainter oldDelegate) => true; } diff --git a/lib/common/url/api_path.dart b/lib/common/url/api_path.dart index 5033af9..7b839fb 100644 --- a/lib/common/url/api_path.dart +++ b/lib/common/url/api_path.dart @@ -5,4 +5,7 @@ class ApiPath { static String setPassword = '/api/v1/customer-auth/register/set-password'; static String login = '/api/v1/customer-auth/login'; static String resend = '/api/v1/customer-auth/resend-otp'; + + // Marketing + static String gamePrizeByGameId = '/api/v1/marketing/game-prizes/game'; } diff --git a/lib/domain/game/entity/game_entity.dart b/lib/domain/game/entity/game_entity.dart new file mode 100644 index 0000000..670a296 --- /dev/null +++ b/lib/domain/game/entity/game_entity.dart @@ -0,0 +1,24 @@ +part of '../game.dart'; + +@freezed +class Game with _$Game { + const factory Game({ + required String id, + required String name, + required String type, + required bool isActive, + required Map metadata, + required String createdAt, + required String updatedAt, + }) = _Game; + + factory Game.empty() => const Game( + id: '', + name: '', + type: '', + isActive: false, + metadata: {}, + createdAt: '', + updatedAt: '', + ); +} diff --git a/lib/domain/game/entity/game_prize_entity.dart b/lib/domain/game/entity/game_prize_entity.dart new file mode 100644 index 0000000..e03b91d --- /dev/null +++ b/lib/domain/game/entity/game_prize_entity.dart @@ -0,0 +1,32 @@ +part of '../game.dart'; + +@freezed +class GamePrize with _$GamePrize { + const factory 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( + id: '', + gameId: '', + name: '', + weight: 0, + stock: 0, + maxStock: 0, + threshold: 0, + metadata: const {}, + game: Game.empty(), + createdAt: '', + updatedAt: '', + ); +} diff --git a/lib/domain/game/failures/game_failure.dart b/lib/domain/game/failures/game_failure.dart new file mode 100644 index 0000000..ea61756 --- /dev/null +++ b/lib/domain/game/failures/game_failure.dart @@ -0,0 +1,9 @@ +part of '../game.dart'; + +@freezed +sealed class GameFailure with _$GameFailure { + const factory GameFailure.serverError(ApiFailure failure) = _ServerError; + const factory GameFailure.unexpectedError() = _UnexpectedError; + const factory GameFailure.dynamicErrorMessage(String erroMessage) = + _DynamicErrorMessage; +} diff --git a/lib/domain/game/game.dart b/lib/domain/game/game.dart new file mode 100644 index 0000000..af0911b --- /dev/null +++ b/lib/domain/game/game.dart @@ -0,0 +1,11 @@ +import 'package:dartz/dartz.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +import '../../common/api/api_failure.dart'; + +part 'game.freezed.dart'; + +part 'entity/game_entity.dart'; +part 'entity/game_prize_entity.dart'; +part 'failures/game_failure.dart'; +part 'repositories/i_game_repository.dart'; diff --git a/lib/domain/game/game.freezed.dart b/lib/domain/game/game.freezed.dart new file mode 100644 index 0000000..f13f3a1 --- /dev/null +++ b/lib/domain/game/game.freezed.dart @@ -0,0 +1,1162 @@ +// 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.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 _$Game { + String get id => throw _privateConstructorUsedError; + String get name => throw _privateConstructorUsedError; + String get type => throw _privateConstructorUsedError; + bool get isActive => throw _privateConstructorUsedError; + Map get metadata => throw _privateConstructorUsedError; + String get createdAt => throw _privateConstructorUsedError; + String get updatedAt => throw _privateConstructorUsedError; + + /// Create a copy of Game + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $GameCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $GameCopyWith<$Res> { + factory $GameCopyWith(Game value, $Res Function(Game) then) = + _$GameCopyWithImpl<$Res, Game>; + @useResult + $Res call({ + String id, + String name, + String type, + bool isActive, + Map metadata, + String createdAt, + String updatedAt, + }); +} + +/// @nodoc +class _$GameCopyWithImpl<$Res, $Val extends Game> + implements $GameCopyWith<$Res> { + _$GameCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of Game + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? name = null, + Object? type = null, + Object? isActive = null, + Object? metadata = null, + Object? createdAt = null, + Object? updatedAt = null, + }) { + return _then( + _value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + type: null == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as String, + isActive: null == isActive + ? _value.isActive + : isActive // ignore: cast_nullable_to_non_nullable + as bool, + metadata: null == metadata + ? _value.metadata + : metadata // ignore: cast_nullable_to_non_nullable + as Map, + createdAt: null == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as String, + updatedAt: null == updatedAt + ? _value.updatedAt + : updatedAt // ignore: cast_nullable_to_non_nullable + as String, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$GameImplCopyWith<$Res> implements $GameCopyWith<$Res> { + factory _$$GameImplCopyWith( + _$GameImpl value, + $Res Function(_$GameImpl) then, + ) = __$$GameImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + String id, + String name, + String type, + bool isActive, + Map metadata, + String createdAt, + String updatedAt, + }); +} + +/// @nodoc +class __$$GameImplCopyWithImpl<$Res> + extends _$GameCopyWithImpl<$Res, _$GameImpl> + implements _$$GameImplCopyWith<$Res> { + __$$GameImplCopyWithImpl(_$GameImpl _value, $Res Function(_$GameImpl) _then) + : super(_value, _then); + + /// Create a copy of Game + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? name = null, + Object? type = null, + Object? isActive = null, + Object? metadata = null, + Object? createdAt = null, + Object? updatedAt = null, + }) { + return _then( + _$GameImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + type: null == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as String, + isActive: null == isActive + ? _value.isActive + : isActive // ignore: cast_nullable_to_non_nullable + as bool, + metadata: null == metadata + ? _value._metadata + : metadata // ignore: cast_nullable_to_non_nullable + as Map, + createdAt: null == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as String, + updatedAt: null == updatedAt + ? _value.updatedAt + : updatedAt // ignore: cast_nullable_to_non_nullable + as String, + ), + ); + } +} + +/// @nodoc + +class _$GameImpl implements _Game { + const _$GameImpl({ + required this.id, + required this.name, + required this.type, + required this.isActive, + required final Map metadata, + required this.createdAt, + required this.updatedAt, + }) : _metadata = metadata; + + @override + final String id; + @override + final String name; + @override + final String type; + @override + final bool isActive; + final Map _metadata; + @override + Map get metadata { + if (_metadata is EqualUnmodifiableMapView) return _metadata; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_metadata); + } + + @override + final String createdAt; + @override + final String updatedAt; + + @override + String toString() { + return 'Game(id: $id, name: $name, type: $type, isActive: $isActive, metadata: $metadata, createdAt: $createdAt, updatedAt: $updatedAt)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$GameImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.name, name) || other.name == name) && + (identical(other.type, type) || other.type == type) && + (identical(other.isActive, isActive) || + other.isActive == isActive) && + const DeepCollectionEquality().equals(other._metadata, _metadata) && + (identical(other.createdAt, createdAt) || + other.createdAt == createdAt) && + (identical(other.updatedAt, updatedAt) || + other.updatedAt == updatedAt)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + id, + name, + type, + isActive, + const DeepCollectionEquality().hash(_metadata), + createdAt, + updatedAt, + ); + + /// Create a copy of Game + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$GameImplCopyWith<_$GameImpl> get copyWith => + __$$GameImplCopyWithImpl<_$GameImpl>(this, _$identity); +} + +abstract class _Game implements Game { + const factory _Game({ + required final String id, + required final String name, + required final String type, + required final bool isActive, + required final Map metadata, + required final String createdAt, + required final String updatedAt, + }) = _$GameImpl; + + @override + String get id; + @override + String get name; + @override + String get type; + @override + bool get isActive; + @override + Map get metadata; + @override + String get createdAt; + @override + String get updatedAt; + + /// Create a copy of Game + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$GameImplCopyWith<_$GameImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +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; + + /// Create a copy of GamePrize + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $GamePrizeCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $GamePrizeCopyWith<$Res> { + factory $GamePrizeCopyWith(GamePrize value, $Res Function(GamePrize) then) = + _$GamePrizeCopyWithImpl<$Res, GamePrize>; + @useResult + $Res call({ + 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 +class _$GamePrizeCopyWithImpl<$Res, $Val extends GamePrize> + implements $GamePrizeCopyWith<$Res> { + _$GamePrizeCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of GamePrize + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + 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, + }) { + return _then( + _value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + gameId: null == gameId + ? _value.gameId + : gameId // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _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 + as String, + updatedAt: null == updatedAt + ? _value.updatedAt + : updatedAt // ignore: cast_nullable_to_non_nullable + as String, + ) + 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 +abstract class _$$GamePrizeImplCopyWith<$Res> + implements $GamePrizeCopyWith<$Res> { + factory _$$GamePrizeImplCopyWith( + _$GamePrizeImpl value, + $Res Function(_$GamePrizeImpl) then, + ) = __$$GamePrizeImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + 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 +class __$$GamePrizeImplCopyWithImpl<$Res> + extends _$GamePrizeCopyWithImpl<$Res, _$GamePrizeImpl> + implements _$$GamePrizeImplCopyWith<$Res> { + __$$GamePrizeImplCopyWithImpl( + _$GamePrizeImpl _value, + $Res Function(_$GamePrizeImpl) _then, + ) : super(_value, _then); + + /// Create a copy of GamePrize + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + 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, + }) { + return _then( + _$GamePrizeImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + gameId: null == gameId + ? _value.gameId + : gameId // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _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 + as String, + updatedAt: null == updatedAt + ? _value.updatedAt + : updatedAt // ignore: cast_nullable_to_non_nullable + as String, + ), + ); + } +} + +/// @nodoc + +class _$GamePrizeImpl implements _GamePrize { + const _$GamePrizeImpl({ + 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; + + @override + final String id; + @override + 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 { + if (_metadata is EqualUnmodifiableMapView) return _metadata; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_metadata); + } + + @override + final Game game; + @override + final String createdAt; + @override + final String updatedAt; + + @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)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$GamePrizeImpl && + (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) || + other.updatedAt == updatedAt)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + id, + gameId, + name, + weight, + stock, + maxStock, + threshold, + const DeepCollectionEquality().hash(_metadata), + game, + createdAt, + updatedAt, + ); + + /// Create a copy of GamePrize + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$GamePrizeImplCopyWith<_$GamePrizeImpl> get copyWith => + __$$GamePrizeImplCopyWithImpl<_$GamePrizeImpl>(this, _$identity); +} + +abstract class _GamePrize implements GamePrize { + const factory _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; + + @override + String get id; + @override + String get gameId; + @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; + + /// Create a copy of GamePrize + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$GamePrizeImplCopyWith<_$GamePrizeImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +mixin _$GameFailure { + @optionalTypeArgs + TResult when({ + required TResult Function(ApiFailure failure) serverError, + required TResult Function() unexpectedError, + required TResult Function(String erroMessage) dynamicErrorMessage, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(ApiFailure failure)? serverError, + TResult? Function()? unexpectedError, + TResult? Function(String erroMessage)? dynamicErrorMessage, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(ApiFailure failure)? serverError, + TResult Function()? unexpectedError, + TResult Function(String erroMessage)? dynamicErrorMessage, + required TResult orElse(), + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_ServerError value) serverError, + required TResult Function(_UnexpectedError value) unexpectedError, + required TResult Function(_DynamicErrorMessage value) dynamicErrorMessage, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_ServerError value)? serverError, + TResult? Function(_UnexpectedError value)? unexpectedError, + TResult? Function(_DynamicErrorMessage value)? dynamicErrorMessage, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_ServerError value)? serverError, + TResult Function(_UnexpectedError value)? unexpectedError, + TResult Function(_DynamicErrorMessage value)? dynamicErrorMessage, + required TResult orElse(), + }) => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $GameFailureCopyWith<$Res> { + factory $GameFailureCopyWith( + GameFailure value, + $Res Function(GameFailure) then, + ) = _$GameFailureCopyWithImpl<$Res, GameFailure>; +} + +/// @nodoc +class _$GameFailureCopyWithImpl<$Res, $Val extends GameFailure> + implements $GameFailureCopyWith<$Res> { + _$GameFailureCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of GameFailure + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc +abstract class _$$ServerErrorImplCopyWith<$Res> { + factory _$$ServerErrorImplCopyWith( + _$ServerErrorImpl value, + $Res Function(_$ServerErrorImpl) then, + ) = __$$ServerErrorImplCopyWithImpl<$Res>; + @useResult + $Res call({ApiFailure failure}); + + $ApiFailureCopyWith<$Res> get failure; +} + +/// @nodoc +class __$$ServerErrorImplCopyWithImpl<$Res> + extends _$GameFailureCopyWithImpl<$Res, _$ServerErrorImpl> + implements _$$ServerErrorImplCopyWith<$Res> { + __$$ServerErrorImplCopyWithImpl( + _$ServerErrorImpl _value, + $Res Function(_$ServerErrorImpl) _then, + ) : super(_value, _then); + + /// Create a copy of GameFailure + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? failure = null}) { + return _then( + _$ServerErrorImpl( + null == failure + ? _value.failure + : failure // ignore: cast_nullable_to_non_nullable + as ApiFailure, + ), + ); + } + + /// Create a copy of GameFailure + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $ApiFailureCopyWith<$Res> get failure { + return $ApiFailureCopyWith<$Res>(_value.failure, (value) { + return _then(_value.copyWith(failure: value)); + }); + } +} + +/// @nodoc + +class _$ServerErrorImpl implements _ServerError { + const _$ServerErrorImpl(this.failure); + + @override + final ApiFailure failure; + + @override + String toString() { + return 'GameFailure.serverError(failure: $failure)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ServerErrorImpl && + (identical(other.failure, failure) || other.failure == failure)); + } + + @override + int get hashCode => Object.hash(runtimeType, failure); + + /// Create a copy of GameFailure + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ServerErrorImplCopyWith<_$ServerErrorImpl> get copyWith => + __$$ServerErrorImplCopyWithImpl<_$ServerErrorImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(ApiFailure failure) serverError, + required TResult Function() unexpectedError, + required TResult Function(String erroMessage) dynamicErrorMessage, + }) { + return serverError(failure); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(ApiFailure failure)? serverError, + TResult? Function()? unexpectedError, + TResult? Function(String erroMessage)? dynamicErrorMessage, + }) { + return serverError?.call(failure); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(ApiFailure failure)? serverError, + TResult Function()? unexpectedError, + TResult Function(String erroMessage)? dynamicErrorMessage, + required TResult orElse(), + }) { + if (serverError != null) { + return serverError(failure); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_ServerError value) serverError, + required TResult Function(_UnexpectedError value) unexpectedError, + required TResult Function(_DynamicErrorMessage value) dynamicErrorMessage, + }) { + return serverError(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_ServerError value)? serverError, + TResult? Function(_UnexpectedError value)? unexpectedError, + TResult? Function(_DynamicErrorMessage value)? dynamicErrorMessage, + }) { + return serverError?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_ServerError value)? serverError, + TResult Function(_UnexpectedError value)? unexpectedError, + TResult Function(_DynamicErrorMessage value)? dynamicErrorMessage, + required TResult orElse(), + }) { + if (serverError != null) { + return serverError(this); + } + return orElse(); + } +} + +abstract class _ServerError implements GameFailure { + const factory _ServerError(final ApiFailure failure) = _$ServerErrorImpl; + + ApiFailure get failure; + + /// Create a copy of GameFailure + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ServerErrorImplCopyWith<_$ServerErrorImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$UnexpectedErrorImplCopyWith<$Res> { + factory _$$UnexpectedErrorImplCopyWith( + _$UnexpectedErrorImpl value, + $Res Function(_$UnexpectedErrorImpl) then, + ) = __$$UnexpectedErrorImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$UnexpectedErrorImplCopyWithImpl<$Res> + extends _$GameFailureCopyWithImpl<$Res, _$UnexpectedErrorImpl> + implements _$$UnexpectedErrorImplCopyWith<$Res> { + __$$UnexpectedErrorImplCopyWithImpl( + _$UnexpectedErrorImpl _value, + $Res Function(_$UnexpectedErrorImpl) _then, + ) : super(_value, _then); + + /// Create a copy of GameFailure + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$UnexpectedErrorImpl implements _UnexpectedError { + const _$UnexpectedErrorImpl(); + + @override + String toString() { + return 'GameFailure.unexpectedError()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$UnexpectedErrorImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(ApiFailure failure) serverError, + required TResult Function() unexpectedError, + required TResult Function(String erroMessage) dynamicErrorMessage, + }) { + return unexpectedError(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(ApiFailure failure)? serverError, + TResult? Function()? unexpectedError, + TResult? Function(String erroMessage)? dynamicErrorMessage, + }) { + return unexpectedError?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(ApiFailure failure)? serverError, + TResult Function()? unexpectedError, + TResult Function(String erroMessage)? dynamicErrorMessage, + required TResult orElse(), + }) { + if (unexpectedError != null) { + return unexpectedError(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_ServerError value) serverError, + required TResult Function(_UnexpectedError value) unexpectedError, + required TResult Function(_DynamicErrorMessage value) dynamicErrorMessage, + }) { + return unexpectedError(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_ServerError value)? serverError, + TResult? Function(_UnexpectedError value)? unexpectedError, + TResult? Function(_DynamicErrorMessage value)? dynamicErrorMessage, + }) { + return unexpectedError?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_ServerError value)? serverError, + TResult Function(_UnexpectedError value)? unexpectedError, + TResult Function(_DynamicErrorMessage value)? dynamicErrorMessage, + required TResult orElse(), + }) { + if (unexpectedError != null) { + return unexpectedError(this); + } + return orElse(); + } +} + +abstract class _UnexpectedError implements GameFailure { + const factory _UnexpectedError() = _$UnexpectedErrorImpl; +} + +/// @nodoc +abstract class _$$DynamicErrorMessageImplCopyWith<$Res> { + factory _$$DynamicErrorMessageImplCopyWith( + _$DynamicErrorMessageImpl value, + $Res Function(_$DynamicErrorMessageImpl) then, + ) = __$$DynamicErrorMessageImplCopyWithImpl<$Res>; + @useResult + $Res call({String erroMessage}); +} + +/// @nodoc +class __$$DynamicErrorMessageImplCopyWithImpl<$Res> + extends _$GameFailureCopyWithImpl<$Res, _$DynamicErrorMessageImpl> + implements _$$DynamicErrorMessageImplCopyWith<$Res> { + __$$DynamicErrorMessageImplCopyWithImpl( + _$DynamicErrorMessageImpl _value, + $Res Function(_$DynamicErrorMessageImpl) _then, + ) : super(_value, _then); + + /// Create a copy of GameFailure + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? erroMessage = null}) { + return _then( + _$DynamicErrorMessageImpl( + null == erroMessage + ? _value.erroMessage + : erroMessage // ignore: cast_nullable_to_non_nullable + as String, + ), + ); + } +} + +/// @nodoc + +class _$DynamicErrorMessageImpl implements _DynamicErrorMessage { + const _$DynamicErrorMessageImpl(this.erroMessage); + + @override + final String erroMessage; + + @override + String toString() { + return 'GameFailure.dynamicErrorMessage(erroMessage: $erroMessage)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$DynamicErrorMessageImpl && + (identical(other.erroMessage, erroMessage) || + other.erroMessage == erroMessage)); + } + + @override + int get hashCode => Object.hash(runtimeType, erroMessage); + + /// Create a copy of GameFailure + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$DynamicErrorMessageImplCopyWith<_$DynamicErrorMessageImpl> get copyWith => + __$$DynamicErrorMessageImplCopyWithImpl<_$DynamicErrorMessageImpl>( + this, + _$identity, + ); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(ApiFailure failure) serverError, + required TResult Function() unexpectedError, + required TResult Function(String erroMessage) dynamicErrorMessage, + }) { + return dynamicErrorMessage(erroMessage); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(ApiFailure failure)? serverError, + TResult? Function()? unexpectedError, + TResult? Function(String erroMessage)? dynamicErrorMessage, + }) { + return dynamicErrorMessage?.call(erroMessage); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(ApiFailure failure)? serverError, + TResult Function()? unexpectedError, + TResult Function(String erroMessage)? dynamicErrorMessage, + required TResult orElse(), + }) { + if (dynamicErrorMessage != null) { + return dynamicErrorMessage(erroMessage); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_ServerError value) serverError, + required TResult Function(_UnexpectedError value) unexpectedError, + required TResult Function(_DynamicErrorMessage value) dynamicErrorMessage, + }) { + return dynamicErrorMessage(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_ServerError value)? serverError, + TResult? Function(_UnexpectedError value)? unexpectedError, + TResult? Function(_DynamicErrorMessage value)? dynamicErrorMessage, + }) { + return dynamicErrorMessage?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_ServerError value)? serverError, + TResult Function(_UnexpectedError value)? unexpectedError, + TResult Function(_DynamicErrorMessage value)? dynamicErrorMessage, + required TResult orElse(), + }) { + if (dynamicErrorMessage != null) { + return dynamicErrorMessage(this); + } + return orElse(); + } +} + +abstract class _DynamicErrorMessage implements GameFailure { + const factory _DynamicErrorMessage(final String erroMessage) = + _$DynamicErrorMessageImpl; + + String get erroMessage; + + /// Create a copy of GameFailure + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$DynamicErrorMessageImplCopyWith<_$DynamicErrorMessageImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/domain/game/repositories/i_game_repository.dart b/lib/domain/game/repositories/i_game_repository.dart new file mode 100644 index 0000000..e06917b --- /dev/null +++ b/lib/domain/game/repositories/i_game_repository.dart @@ -0,0 +1,7 @@ +part of '../game.dart'; + +abstract class IGameRepository { + Future>> gamePrizeByGameId({ + required String id, + }); +} diff --git a/lib/infrastructure/game/datasources/remote_data_provider.dart b/lib/infrastructure/game/datasources/remote_data_provider.dart new file mode 100644 index 0000000..df528a7 --- /dev/null +++ b/lib/infrastructure/game/datasources/remote_data_provider.dart @@ -0,0 +1,50 @@ +import 'dart:developer'; + +import 'package:data_channel/data_channel.dart'; +import 'package:injectable/injectable.dart'; + +import '../../../common/api/api_client.dart'; +import '../../../common/api/api_failure.dart'; +import '../../../common/function/app_function.dart'; +import '../../../common/url/api_path.dart'; +import '../../../domain/game/game.dart'; +import '../game_dtos.dart'; + +@injectable +class GameRemoteDataProvider { + final ApiClient _apiClient; + final String _logName = "GameRemoteDataProvider"; + + GameRemoteDataProvider(this._apiClient); + + Future>> gamePrizeByGameId({ + required String id, + }) async { + try { + final response = await _apiClient.get( + '${ApiPath.gamePrizeByGameId}/$id', + headers: getAuthorizationHeader(), + ); + + if (response.data['code'] == 401) { + return DC.error( + GameFailure.serverError(ApiFailure.unauthorized('Session Expired')), + ); + } + + if (response.data['status'] == false) { + return DC.error( + GameFailure.dynamicErrorMessage('Terjadi kesalahan coba lagi nanti'), + ); + } + + final dto = (response.data['data'] as List) + .map((e) => GamePrizeDto.fromJson(e)) + .toList(); + return DC.data(dto); + } on ApiFailure catch (e, s) { + log('gamePrizeByGameId', 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 new file mode 100644 index 0000000..329a8ca --- /dev/null +++ b/lib/infrastructure/game/dto/game_dto.dart @@ -0,0 +1,29 @@ +part of '../game_dtos.dart'; + +@freezed +class GameDto with _$GameDto { + const factory GameDto({ + @JsonKey(name: 'id') String? id, + @JsonKey(name: 'name') String? name, + @JsonKey(name: 'type') String? type, + @JsonKey(name: 'is_active') bool? isActive, + @JsonKey(name: 'metadata') Map? metadata, + @JsonKey(name: 'created_at') String? createdAt, + @JsonKey(name: 'updated_at') String? updatedAt, + }) = _GameDto; + + factory GameDto.fromJson(Map json) => + _$GameDtoFromJson(json); + + const GameDto._(); + + Game toDomain() => Game( + id: id ?? '', + name: name ?? '', + type: type ?? '', + isActive: isActive ?? false, + metadata: metadata ?? const {}, + createdAt: createdAt ?? '', + updatedAt: updatedAt ?? '', + ); +} diff --git a/lib/infrastructure/game/dto/game_prize_dto.dart b/lib/infrastructure/game/dto/game_prize_dto.dart new file mode 100644 index 0000000..6f421b1 --- /dev/null +++ b/lib/infrastructure/game/dto/game_prize_dto.dart @@ -0,0 +1,38 @@ +part of '../game_dtos.dart'; + +@freezed +class GamePrizeDto with _$GamePrizeDto { + const factory 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; + + factory GamePrizeDto.fromJson(Map json) => + _$GamePrizeDtoFromJson(json); + + 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(), + createdAt: createdAt ?? '', + updatedAt: updatedAt ?? '', + ); +} diff --git a/lib/infrastructure/game/game_dtos.dart b/lib/infrastructure/game/game_dtos.dart new file mode 100644 index 0000000..899b9fa --- /dev/null +++ b/lib/infrastructure/game/game_dtos.dart @@ -0,0 +1,9 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +import '../../domain/game/game.dart'; + +part 'game_dtos.freezed.dart'; +part 'game_dtos.g.dart'; + +part 'dto/game_dto.dart'; +part 'dto/game_prize_dto.dart'; diff --git a/lib/infrastructure/game/game_dtos.freezed.dart b/lib/infrastructure/game/game_dtos.freezed.dart new file mode 100644 index 0000000..dfb2473 --- /dev/null +++ b/lib/infrastructure/game/game_dtos.freezed.dart @@ -0,0 +1,779 @@ +// 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_dtos.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', +); + +GameDto _$GameDtoFromJson(Map json) { + return _GameDto.fromJson(json); +} + +/// @nodoc +mixin _$GameDto { + @JsonKey(name: 'id') + String? get id => throw _privateConstructorUsedError; + @JsonKey(name: 'name') + String? get name => throw _privateConstructorUsedError; + @JsonKey(name: 'type') + String? get type => throw _privateConstructorUsedError; + @JsonKey(name: 'is_active') + bool? get isActive => throw _privateConstructorUsedError; + @JsonKey(name: 'metadata') + Map? get metadata => throw _privateConstructorUsedError; + @JsonKey(name: 'created_at') + String? get createdAt => throw _privateConstructorUsedError; + @JsonKey(name: 'updated_at') + String? get updatedAt => throw _privateConstructorUsedError; + + /// Serializes this GameDto to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of GameDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $GameDtoCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $GameDtoCopyWith<$Res> { + factory $GameDtoCopyWith(GameDto value, $Res Function(GameDto) then) = + _$GameDtoCopyWithImpl<$Res, GameDto>; + @useResult + $Res call({ + @JsonKey(name: 'id') String? id, + @JsonKey(name: 'name') String? name, + @JsonKey(name: 'type') String? type, + @JsonKey(name: 'is_active') bool? isActive, + @JsonKey(name: 'metadata') Map? metadata, + @JsonKey(name: 'created_at') String? createdAt, + @JsonKey(name: 'updated_at') String? updatedAt, + }); +} + +/// @nodoc +class _$GameDtoCopyWithImpl<$Res, $Val extends GameDto> + implements $GameDtoCopyWith<$Res> { + _$GameDtoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of GameDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = freezed, + Object? name = freezed, + Object? type = freezed, + Object? isActive = freezed, + Object? metadata = freezed, + Object? createdAt = freezed, + Object? updatedAt = freezed, + }) { + return _then( + _value.copyWith( + id: freezed == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String?, + name: freezed == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String?, + type: freezed == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as String?, + isActive: freezed == isActive + ? _value.isActive + : isActive // ignore: cast_nullable_to_non_nullable + as bool?, + metadata: freezed == metadata + ? _value.metadata + : metadata // ignore: cast_nullable_to_non_nullable + as Map?, + createdAt: freezed == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as String?, + updatedAt: freezed == updatedAt + ? _value.updatedAt + : updatedAt // ignore: cast_nullable_to_non_nullable + as String?, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$GameDtoImplCopyWith<$Res> implements $GameDtoCopyWith<$Res> { + factory _$$GameDtoImplCopyWith( + _$GameDtoImpl value, + $Res Function(_$GameDtoImpl) then, + ) = __$$GameDtoImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + @JsonKey(name: 'id') String? id, + @JsonKey(name: 'name') String? name, + @JsonKey(name: 'type') String? type, + @JsonKey(name: 'is_active') bool? isActive, + @JsonKey(name: 'metadata') Map? metadata, + @JsonKey(name: 'created_at') String? createdAt, + @JsonKey(name: 'updated_at') String? updatedAt, + }); +} + +/// @nodoc +class __$$GameDtoImplCopyWithImpl<$Res> + extends _$GameDtoCopyWithImpl<$Res, _$GameDtoImpl> + implements _$$GameDtoImplCopyWith<$Res> { + __$$GameDtoImplCopyWithImpl( + _$GameDtoImpl _value, + $Res Function(_$GameDtoImpl) _then, + ) : super(_value, _then); + + /// Create a copy of GameDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = freezed, + Object? name = freezed, + Object? type = freezed, + Object? isActive = freezed, + Object? metadata = freezed, + Object? createdAt = freezed, + Object? updatedAt = freezed, + }) { + return _then( + _$GameDtoImpl( + id: freezed == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String?, + name: freezed == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String?, + type: freezed == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as String?, + isActive: freezed == isActive + ? _value.isActive + : isActive // ignore: cast_nullable_to_non_nullable + as bool?, + metadata: freezed == metadata + ? _value._metadata + : metadata // ignore: cast_nullable_to_non_nullable + as Map?, + createdAt: freezed == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as String?, + updatedAt: freezed == updatedAt + ? _value.updatedAt + : updatedAt // ignore: cast_nullable_to_non_nullable + as String?, + ), + ); + } +} + +/// @nodoc +@JsonSerializable() +class _$GameDtoImpl extends _GameDto { + const _$GameDtoImpl({ + @JsonKey(name: 'id') this.id, + @JsonKey(name: 'name') this.name, + @JsonKey(name: 'type') this.type, + @JsonKey(name: 'is_active') this.isActive, + @JsonKey(name: 'metadata') final Map? metadata, + @JsonKey(name: 'created_at') this.createdAt, + @JsonKey(name: 'updated_at') this.updatedAt, + }) : _metadata = metadata, + super._(); + + factory _$GameDtoImpl.fromJson(Map json) => + _$$GameDtoImplFromJson(json); + + @override + @JsonKey(name: 'id') + final String? id; + @override + @JsonKey(name: 'name') + final String? name; + @override + @JsonKey(name: 'type') + final String? type; + @override + @JsonKey(name: 'is_active') + final bool? isActive; + final Map? _metadata; + @override + @JsonKey(name: 'metadata') + Map? get metadata { + final value = _metadata; + if (value == null) return null; + if (_metadata is EqualUnmodifiableMapView) return _metadata; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(value); + } + + @override + @JsonKey(name: 'created_at') + final String? createdAt; + @override + @JsonKey(name: 'updated_at') + final String? updatedAt; + + @override + String toString() { + return 'GameDto(id: $id, name: $name, type: $type, isActive: $isActive, metadata: $metadata, createdAt: $createdAt, updatedAt: $updatedAt)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$GameDtoImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.name, name) || other.name == name) && + (identical(other.type, type) || other.type == type) && + (identical(other.isActive, isActive) || + other.isActive == isActive) && + const DeepCollectionEquality().equals(other._metadata, _metadata) && + (identical(other.createdAt, createdAt) || + other.createdAt == createdAt) && + (identical(other.updatedAt, updatedAt) || + other.updatedAt == updatedAt)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, + id, + name, + type, + isActive, + const DeepCollectionEquality().hash(_metadata), + createdAt, + updatedAt, + ); + + /// Create a copy of GameDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$GameDtoImplCopyWith<_$GameDtoImpl> get copyWith => + __$$GameDtoImplCopyWithImpl<_$GameDtoImpl>(this, _$identity); + + @override + Map toJson() { + return _$$GameDtoImplToJson(this); + } +} + +abstract class _GameDto extends GameDto { + const factory _GameDto({ + @JsonKey(name: 'id') final String? id, + @JsonKey(name: 'name') final String? name, + @JsonKey(name: 'type') final String? type, + @JsonKey(name: 'is_active') final bool? isActive, + @JsonKey(name: 'metadata') final Map? metadata, + @JsonKey(name: 'created_at') final String? createdAt, + @JsonKey(name: 'updated_at') final String? updatedAt, + }) = _$GameDtoImpl; + const _GameDto._() : super._(); + + factory _GameDto.fromJson(Map json) = _$GameDtoImpl.fromJson; + + @override + @JsonKey(name: 'id') + String? get id; + @override + @JsonKey(name: 'name') + String? get name; + @override + @JsonKey(name: 'type') + String? get type; + @override + @JsonKey(name: 'is_active') + bool? get isActive; + @override + @JsonKey(name: 'metadata') + Map? get metadata; + @override + @JsonKey(name: 'created_at') + String? get createdAt; + @override + @JsonKey(name: 'updated_at') + String? get updatedAt; + + /// Create a copy of GameDto + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$GameDtoImplCopyWith<_$GameDtoImpl> get copyWith => + throw _privateConstructorUsedError; +} + +GamePrizeDto _$GamePrizeDtoFromJson(Map json) { + return _GamePrizeDto.fromJson(json); +} + +/// @nodoc +mixin _$GamePrizeDto { + @JsonKey(name: 'id') + String? get id => throw _privateConstructorUsedError; + @JsonKey(name: 'game_id') + 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') + String? get updatedAt => throw _privateConstructorUsedError; + + /// Serializes this GamePrizeDto to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of GamePrizeDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $GamePrizeDtoCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $GamePrizeDtoCopyWith<$Res> { + factory $GamePrizeDtoCopyWith( + GamePrizeDto value, + $Res Function(GamePrizeDto) then, + ) = _$GamePrizeDtoCopyWithImpl<$Res, GamePrizeDto>; + @useResult + $Res call({ + @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 +class _$GamePrizeDtoCopyWithImpl<$Res, $Val extends GamePrizeDto> + implements $GamePrizeDtoCopyWith<$Res> { + _$GamePrizeDtoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of GamePrizeDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + 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, + }) { + return _then( + _value.copyWith( + id: freezed == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String?, + gameId: freezed == gameId + ? _value.gameId + : gameId // ignore: cast_nullable_to_non_nullable + as String?, + name: freezed == name + ? _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 + as String?, + updatedAt: freezed == updatedAt + ? _value.updatedAt + : updatedAt // ignore: cast_nullable_to_non_nullable + as String?, + ) + 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 +abstract class _$$GamePrizeDtoImplCopyWith<$Res> + implements $GamePrizeDtoCopyWith<$Res> { + factory _$$GamePrizeDtoImplCopyWith( + _$GamePrizeDtoImpl value, + $Res Function(_$GamePrizeDtoImpl) then, + ) = __$$GamePrizeDtoImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + @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 +class __$$GamePrizeDtoImplCopyWithImpl<$Res> + extends _$GamePrizeDtoCopyWithImpl<$Res, _$GamePrizeDtoImpl> + implements _$$GamePrizeDtoImplCopyWith<$Res> { + __$$GamePrizeDtoImplCopyWithImpl( + _$GamePrizeDtoImpl _value, + $Res Function(_$GamePrizeDtoImpl) _then, + ) : super(_value, _then); + + /// Create a copy of GamePrizeDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + 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, + }) { + return _then( + _$GamePrizeDtoImpl( + id: freezed == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String?, + gameId: freezed == gameId + ? _value.gameId + : gameId // ignore: cast_nullable_to_non_nullable + as String?, + name: freezed == name + ? _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 + as String?, + updatedAt: freezed == updatedAt + ? _value.updatedAt + : updatedAt // ignore: cast_nullable_to_non_nullable + as String?, + ), + ); + } +} + +/// @nodoc +@JsonSerializable() +class _$GamePrizeDtoImpl extends _GamePrizeDto { + const _$GamePrizeDtoImpl({ + @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, + super._(); + + factory _$GamePrizeDtoImpl.fromJson(Map json) => + _$$GamePrizeDtoImplFromJson(json); + + @override + @JsonKey(name: 'id') + final String? id; + @override + @JsonKey(name: 'game_id') + final String? gameId; + @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') + Map? get metadata { + final value = _metadata; + if (value == null) return null; + if (_metadata is EqualUnmodifiableMapView) return _metadata; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(value); + } + + @override + @JsonKey(name: 'game') + final GameDto? game; + @override + @JsonKey(name: 'created_at') + final String? createdAt; + @override + @JsonKey(name: 'updated_at') + final String? updatedAt; + + @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)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$GamePrizeDtoImpl && + (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) || + other.updatedAt == updatedAt)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, + id, + gameId, + name, + weight, + stock, + maxStock, + threshold, + const DeepCollectionEquality().hash(_metadata), + game, + createdAt, + updatedAt, + ); + + /// Create a copy of GamePrizeDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$GamePrizeDtoImplCopyWith<_$GamePrizeDtoImpl> get copyWith => + __$$GamePrizeDtoImplCopyWithImpl<_$GamePrizeDtoImpl>(this, _$identity); + + @override + Map toJson() { + return _$$GamePrizeDtoImplToJson(this); + } +} + +abstract class _GamePrizeDto extends GamePrizeDto { + const factory _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; + const _GamePrizeDto._() : super._(); + + factory _GamePrizeDto.fromJson(Map json) = + _$GamePrizeDtoImpl.fromJson; + + @override + @JsonKey(name: 'id') + String? get id; + @override + @JsonKey(name: 'game_id') + String? get gameId; + @override + @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 + @JsonKey(name: 'updated_at') + String? get updatedAt; + + /// Create a copy of GamePrizeDto + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$GamePrizeDtoImplCopyWith<_$GamePrizeDtoImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/infrastructure/game/game_dtos.g.dart b/lib/infrastructure/game/game_dtos.g.dart new file mode 100644 index 0000000..da51145 --- /dev/null +++ b/lib/infrastructure/game/game_dtos.g.dart @@ -0,0 +1,61 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'game_dtos.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$GameDtoImpl _$$GameDtoImplFromJson(Map json) => + _$GameDtoImpl( + id: json['id'] as String?, + name: json['name'] as String?, + type: json['type'] as String?, + isActive: json['is_active'] as bool?, + metadata: json['metadata'] as Map?, + createdAt: json['created_at'] as String?, + updatedAt: json['updated_at'] as String?, + ); + +Map _$$GameDtoImplToJson(_$GameDtoImpl instance) => + { + 'id': instance.id, + 'name': instance.name, + 'type': instance.type, + 'is_active': instance.isActive, + 'metadata': instance.metadata, + 'created_at': instance.createdAt, + 'updated_at': instance.updatedAt, + }; + +_$GamePrizeDtoImpl _$$GamePrizeDtoImplFromJson(Map json) => + _$GamePrizeDtoImpl( + 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?, + ); + +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 new file mode 100644 index 0000000..a195e56 --- /dev/null +++ b/lib/infrastructure/game/repositories/game_repository.dart @@ -0,0 +1,36 @@ +import 'dart:developer'; + +import 'package:dartz/dartz.dart'; +import 'package:injectable/injectable.dart'; + +import '../../../domain/game/game.dart'; +import '../datasources/remote_data_provider.dart'; + +@Injectable(as: IGameRepository) +class GameRepository implements IGameRepository { + final GameRemoteDataProvider _remoteDataProvider; + + final String _logName = 'GameRepository'; + + GameRepository(this._remoteDataProvider); + + @override + Future>> gamePrizeByGameId({ + required String id, + }) async { + try { + final result = await _remoteDataProvider.gamePrizeByGameId(id: id); + + if (result.hasError) { + return left(result.error!); + } + + final data = result.data!.map((e) => e.toDomain()).toList(); + + return right(data); + } catch (e, s) { + log('gamePrizeByGameId', name: _logName, error: e, stackTrace: s); + return left(const GameFailure.unexpectedError()); + } + } +} diff --git a/lib/injection.config.dart b/lib/injection.config.dart index ac18927..4d195d5 100644 --- a/lib/injection.config.dart +++ b/lib/injection.config.dart @@ -16,6 +16,8 @@ import 'package:enaklo/application/auth/check_phone_form/check_phone_form_bloc.d as _i869; import 'package:enaklo/application/auth/login_form/login_form_bloc.dart' as _i510; +import 'package:enaklo/application/auth/logout_form/logout_form_bloc.dart' + as _i216; import 'package:enaklo/application/auth/register_form/register_form_bloc.dart' as _i260; import 'package:enaklo/application/auth/resend_form/resend_form_bloc.dart' @@ -24,6 +26,8 @@ import 'package:enaklo/application/auth/set_password/set_password_form_bloc.dart as _i174; import 'package:enaklo/application/auth/verify_form/verify_form_bloc.dart' as _i521; +import 'package:enaklo/application/game/game_price_loader/game_prize_loader_bloc.dart' + as _i925; 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; @@ -31,6 +35,7 @@ import 'package:enaklo/common/di/di_dio.dart' as _i842; import 'package:enaklo/common/di/di_shared_preferences.dart' as _i672; import 'package:enaklo/common/network/network_client.dart' as _i109; import 'package:enaklo/domain/auth/auth.dart' as _i995; +import 'package:enaklo/domain/game/game.dart' as _i96; import 'package:enaklo/env.dart' as _i372; import 'package:enaklo/infrastructure/auth/datasources/local_data_provider.dart' as _i1003; @@ -38,6 +43,10 @@ import 'package:enaklo/infrastructure/auth/datasources/remote_data_provider.dart as _i818; import 'package:enaklo/infrastructure/auth/repositories/auth_repository.dart' as _i879; +import 'package:enaklo/infrastructure/game/datasources/remote_data_provider.dart' + as _i143; +import 'package:enaklo/infrastructure/game/repositories/game_repository.dart' + as _i547; import 'package:enaklo/presentation/router/app_router.dart' as _i698; import 'package:get_it/get_it.dart' as _i174; import 'package:injectable/injectable.dart' as _i526; @@ -78,6 +87,15 @@ extension GetItInjectableX on _i174.GetIt { gh.factory<_i818.AuthRemoteDataProvider>( () => _i818.AuthRemoteDataProvider(gh<_i842.ApiClient>()), ); + gh.factory<_i143.GameRemoteDataProvider>( + () => _i143.GameRemoteDataProvider(gh<_i842.ApiClient>()), + ); + gh.factory<_i96.IGameRepository>( + () => _i547.GameRepository(gh<_i143.GameRemoteDataProvider>()), + ); + gh.factory<_i925.GamePrizeLoaderBloc>( + () => _i925.GamePrizeLoaderBloc(gh<_i96.IGameRepository>()), + ); gh.factory<_i995.IAuthRepository>( () => _i879.AuthRepository( gh<_i818.AuthRemoteDataProvider>(), @@ -102,6 +120,9 @@ extension GetItInjectableX on _i174.GetIt { gh.factory<_i771.AuthBloc>( () => _i771.AuthBloc(gh<_i995.IAuthRepository>()), ); + gh.factory<_i216.LogoutFormBloc>( + () => _i216.LogoutFormBloc(gh<_i995.IAuthRepository>()), + ); gh.factory<_i510.LoginFormBloc>( () => _i510.LoginFormBloc(gh<_i995.IAuthRepository>()), ); diff --git a/lib/presentation/app_widget.dart b/lib/presentation/app_widget.dart index 9e1e18f..fec381f 100644 --- a/lib/presentation/app_widget.dart +++ b/lib/presentation/app_widget.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../application/auth/auth_bloc.dart'; +import '../application/auth/logout_form/logout_form_bloc.dart'; import '../common/theme/theme.dart'; import '../common/constant/app_constant.dart'; import '../injection.dart'; @@ -21,7 +22,10 @@ class _AppWidgetState extends State { @override Widget build(BuildContext context) { return MultiBlocProvider( - providers: [BlocProvider(create: (context) => getIt())], + providers: [ + BlocProvider(create: (context) => getIt()), + BlocProvider(create: (context) => getIt()), + ], child: MaterialApp.router( debugShowCheckedModeBanner: false, title: AppConstant.appName, diff --git a/lib/presentation/pages/main/pages/profile/profile_page.dart b/lib/presentation/pages/main/pages/profile/profile_page.dart index 4bedde1..ceb8670 100644 --- a/lib/presentation/pages/main/pages/profile/profile_page.dart +++ b/lib/presentation/pages/main/pages/profile/profile_page.dart @@ -1,341 +1,368 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import '../../../../../application/auth/logout_form/logout_form_bloc.dart'; import '../../../../../common/theme/theme.dart'; +import '../../../../../injection.dart'; +import '../../../../components/toast/flushbar.dart'; import '../../../../router/app_router.gr.dart'; @RoutePage() -class ProfilePage extends StatelessWidget { +class ProfilePage extends StatelessWidget implements AutoRouteWrapper { const ProfilePage({super.key}); @override Widget build(BuildContext context) { - return Scaffold( - backgroundColor: AppColor.background, - appBar: AppBar(title: Text('Profil'), automaticallyImplyLeading: false), - body: SingleChildScrollView( - child: Column( - children: [ - // Profile Header - Container( - width: double.infinity, - color: AppColor.white, - padding: const EdgeInsets.all(20), - child: Column( - children: [ - // Profile Avatar & Info - GestureDetector( - onTap: () => context.router.push(AccountMyRoute()), - child: Container( - decoration: BoxDecoration( - gradient: LinearGradient( - colors: AppColor.primaryGradient, - begin: Alignment.topLeft, - end: Alignment.bottomRight, + return BlocListener( + listener: (context, state) { + state.failureOrAuthOption.fold( + () => null, + (either) => either.fold( + (f) => AppFlushbar.showAuthFailureToast(context, f), + (_) => context.router.replaceAll([LoginRoute()]), + ), + ); + }, + + child: Scaffold( + backgroundColor: AppColor.background, + appBar: AppBar(title: Text('Profil'), automaticallyImplyLeading: false), + body: SingleChildScrollView( + child: Column( + children: [ + // Profile Header + Container( + width: double.infinity, + color: AppColor.white, + padding: const EdgeInsets.all(20), + child: Column( + children: [ + // Profile Avatar & Info + GestureDetector( + onTap: () => context.router.push(AccountMyRoute()), + child: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: AppColor.primaryGradient, + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular(16), ), - borderRadius: BorderRadius.circular(16), - ), - child: Stack( - children: [ - // Background Pattern - Positioned( - top: -20, - right: -20, - child: Container( - width: 80, - height: 80, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: AppColor.white.withOpacity(0.1), - ), - ), - ), - Positioned( - top: 30, - right: 20, - child: Container( - width: 40, - height: 40, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: AppColor.white.withOpacity(0.08), - ), - ), - ), - Positioned( - bottom: -10, - left: -10, - child: Container( - width: 60, - height: 60, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: AppColor.white.withOpacity(0.06), - ), - ), - ), - // Decorative Lines - Positioned( - top: 10, - left: -5, - child: Transform.rotate( - angle: 0.5, + child: Stack( + children: [ + // Background Pattern + Positioned( + top: -20, + right: -20, child: Container( - width: 30, - height: 2, + width: 80, + height: 80, decoration: BoxDecoration( - color: AppColor.white.withOpacity(0.15), - borderRadius: BorderRadius.circular(1), + shape: BoxShape.circle, + color: AppColor.white.withOpacity(0.1), ), ), ), - ), - Positioned( - bottom: 15, - right: 10, - child: Transform.rotate( - angle: -0.5, + Positioned( + top: 30, + right: 20, child: Container( - width: 25, - height: 2, + width: 40, + height: 40, decoration: BoxDecoration( - color: AppColor.white.withOpacity(0.15), - borderRadius: BorderRadius.circular(1), + shape: BoxShape.circle, + color: AppColor.white.withOpacity(0.08), ), ), ), - ), - // Main Content - Padding( - padding: const EdgeInsets.all(16.0), - child: Row( - children: [ - // Avatar - Container( - width: 60, - height: 60, + Positioned( + bottom: -10, + left: -10, + child: Container( + width: 60, + height: 60, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: AppColor.white.withOpacity(0.06), + ), + ), + ), + // Decorative Lines + Positioned( + top: 10, + left: -5, + child: Transform.rotate( + angle: 0.5, + child: Container( + width: 30, + height: 2, decoration: BoxDecoration( - color: AppColor.white, - shape: BoxShape.circle, - boxShadow: [ - BoxShadow( - color: AppColor.black.withOpacity(0.1), - blurRadius: 8, - offset: const Offset(0, 2), - ), - ], - ), - child: Icon( - Icons.person, - size: 30, - color: AppColor.primary, + color: AppColor.white.withOpacity(0.15), + borderRadius: BorderRadius.circular(1), ), ), - const SizedBox(width: 16), - // User Info - Expanded( - child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Text( - 'EFRIL', - style: AppStyle.lg.copyWith( - fontWeight: FontWeight.bold, - color: AppColor.white, - letterSpacing: 0.5, + ), + ), + Positioned( + bottom: 15, + right: 10, + child: Transform.rotate( + angle: -0.5, + child: Container( + width: 25, + height: 2, + decoration: BoxDecoration( + color: AppColor.white.withOpacity(0.15), + borderRadius: BorderRadius.circular(1), + ), + ), + ), + ), + // Main Content + Padding( + padding: const EdgeInsets.all(16.0), + child: Row( + children: [ + // Avatar + Container( + width: 60, + height: 60, + decoration: BoxDecoration( + color: AppColor.white, + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: AppColor.black.withOpacity( + 0.1, + ), + blurRadius: 8, + offset: const Offset(0, 2), ), - ), - const SizedBox(height: 4), - Text( - '+6283873987851', - style: AppStyle.sm.copyWith( - color: AppColor.white.withOpacity( - 0.9, + ], + ), + child: Icon( + Icons.person, + size: 30, + color: AppColor.primary, + ), + ), + const SizedBox(width: 16), + // User Info + Expanded( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + 'EFRIL', + style: AppStyle.lg.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.white, + letterSpacing: 0.5, ), ), - ), - ], + const SizedBox(height: 4), + Text( + '+6283873987851', + style: AppStyle.sm.copyWith( + color: AppColor.white.withOpacity( + 0.9, + ), + ), + ), + ], + ), + ), + // Arrow Icon + Icon( + Icons.arrow_forward_ios, + color: AppColor.white, + size: 14, + ), + ], + ), + ), + ], + ), + ), + ), + ], + ), + ), + + const SizedBox(height: 8), + + // Menu Items + Container( + color: AppColor.white, + child: Column( + children: [ + _buildMenuItem( + icon: Icons.location_on_outlined, + title: 'Alamat Tersimpan', + onTap: () => context.router.push(AddressRoute()), + ), + _buildMenuItem( + icon: Icons.payment_outlined, + title: 'Pembayaran', + onTap: () => context.router.push(PaymentRoute()), + ), + _buildMenuItem( + icon: Icons.help_outline, + title: 'Pusat Bantuan', + onTap: () {}, + ), + _buildMenuItem( + icon: Icons.settings_outlined, + title: 'Pengaturan', + onTap: () {}, + showDivider: false, + ), + ], + ), + ), + + const SizedBox(height: 8), + + // Legal & Privacy Section + Container( + color: AppColor.white, + child: Column( + children: [ + _buildMenuItem( + icon: Icons.description_outlined, + title: 'Syarat dan Ketentuan', + onTap: () {}, + ), + _buildMenuItem( + icon: Icons.privacy_tip_outlined, + title: 'Kebijakan Privasi', + onTap: () {}, + showDivider: false, + ), + ], + ), + ), + + const SizedBox(height: 8), + + // Customer Service Section + Container( + color: AppColor.white, + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Butuh Bantuan?', + style: AppStyle.lg.copyWith( + fontWeight: FontWeight.w600, + color: AppColor.textPrimary, + ), + ), + const SizedBox(height: 8), + Text( + 'Customer Service kami siap untuk membantu', + style: AppStyle.sm.copyWith( + color: AppColor.textSecondary, + ), + ), + const SizedBox(height: 16), + // WhatsApp Customer Service + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppColor.backgroundLight, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: AppColor.borderLight), + ), + child: Row( + children: [ + Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: Colors.green, + borderRadius: BorderRadius.circular(8), + ), + child: Icon( + Icons.chat, + color: AppColor.white, + size: 20, + ), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Enaklo Customer Service (chat only)', + style: AppStyle.sm.copyWith( + color: AppColor.textSecondary, ), ), - // Arrow Icon - Icon( - Icons.arrow_forward_ios, - color: AppColor.white, - size: 14, + Text( + '0812-1111-8456', + style: AppStyle.md.copyWith( + fontWeight: FontWeight.w600, + color: AppColor.success, + ), ), ], ), ), + Icon( + Icons.arrow_forward_ios, + color: AppColor.textSecondary, + size: 14, + ), ], ), ), - ), - ], + ], + ), ), - ), - const SizedBox(height: 8), + const SizedBox(height: 20), - // Menu Items - Container( - color: AppColor.white, - child: Column( - children: [ - _buildMenuItem( - icon: Icons.location_on_outlined, - title: 'Alamat Tersimpan', - onTap: () => context.router.push(AddressRoute()), - ), - _buildMenuItem( - icon: Icons.payment_outlined, - title: 'Pembayaran', - onTap: () => context.router.push(PaymentRoute()), - ), - _buildMenuItem( - icon: Icons.help_outline, - title: 'Pusat Bantuan', - onTap: () {}, - ), - _buildMenuItem( - icon: Icons.settings_outlined, - title: 'Pengaturan', - onTap: () {}, - showDivider: false, - ), - ], - ), - ), - - const SizedBox(height: 8), - - // Legal & Privacy Section - Container( - color: AppColor.white, - child: Column( - children: [ - _buildMenuItem( - icon: Icons.description_outlined, - title: 'Syarat dan Ketentuan', - onTap: () {}, - ), - _buildMenuItem( - icon: Icons.privacy_tip_outlined, - title: 'Kebijakan Privasi', - onTap: () {}, - showDivider: false, - ), - ], - ), - ), - - const SizedBox(height: 8), - - // Customer Service Section - Container( - color: AppColor.white, - padding: const EdgeInsets.all(20), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Butuh Bantuan?', - style: AppStyle.lg.copyWith( - fontWeight: FontWeight.w600, - color: AppColor.textPrimary, - ), - ), - const SizedBox(height: 8), - Text( - 'Customer Service kami siap untuk membantu', - style: AppStyle.sm.copyWith(color: AppColor.textSecondary), - ), - const SizedBox(height: 16), - // WhatsApp Customer Service - Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: AppColor.backgroundLight, - borderRadius: BorderRadius.circular(12), - border: Border.all(color: AppColor.borderLight), - ), - child: Row( - children: [ - Container( - width: 40, - height: 40, - decoration: BoxDecoration( - color: Colors.green, - borderRadius: BorderRadius.circular(8), - ), - child: Icon( - Icons.chat, - color: AppColor.white, - size: 20, - ), - ), - const SizedBox(width: 12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Enaklo Customer Service (chat only)', - style: AppStyle.sm.copyWith( - color: AppColor.textSecondary, - ), - ), - Text( - '0812-1111-8456', - style: AppStyle.md.copyWith( - fontWeight: FontWeight.w600, - color: AppColor.success, - ), - ), - ], - ), - ), - Icon( - Icons.arrow_forward_ios, - color: AppColor.textSecondary, - size: 14, - ), - ], - ), - ), - ], - ), - ), - - const SizedBox(height: 20), - - // Footer Section - Container( - color: AppColor.white, - padding: const EdgeInsets.all(20), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - 'Version 4.6.1', - style: AppStyle.sm.copyWith(color: AppColor.textSecondary), - ), - GestureDetector( - onTap: () => _showLogoutDialog(context), - child: Text( - 'Logout', + // Footer Section + Container( + color: AppColor.white, + padding: const EdgeInsets.all(20), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Version 4.6.1', style: AppStyle.sm.copyWith( - color: AppColor.primary, - fontWeight: FontWeight.w600, + color: AppColor.textSecondary, ), ), - ), - ], + GestureDetector( + onTap: () => _showLogoutDialog( + context, + onLogout: () => context.read().add( + const LogoutFormEvent.submitted(), + ), + ), + child: Text( + 'Logout', + style: AppStyle.sm.copyWith( + color: AppColor.primary, + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ), ), - ), - const SizedBox(height: 100), // Bottom spacing - ], + const SizedBox(height: 100), // Bottom spacing + ], + ), ), ), ); @@ -372,7 +399,10 @@ class ProfilePage extends StatelessWidget { ); } - void _showLogoutDialog(BuildContext context) { + void _showLogoutDialog( + BuildContext context, { + required VoidCallback onLogout, + }) { showDialog( context: context, builder: (BuildContext context) { @@ -395,6 +425,7 @@ class ProfilePage extends StatelessWidget { onPressed: () => Navigator.of(context).pop(), ), TextButton( + onPressed: onLogout, child: Text( 'Logout', style: AppStyle.md.copyWith( @@ -402,15 +433,14 @@ class ProfilePage extends StatelessWidget { fontWeight: FontWeight.w600, ), ), - onPressed: () { - Navigator.of(context).pop(); - // Add logout logic here - // Example: context.router.pushAndClearStack('/login'); - }, ), ], ); }, ); } + + @override + Widget wrappedRoute(BuildContext context) => + BlocProvider(create: (_) => getIt(), child: this); } diff --git a/lib/presentation/pages/mini_games/ferris_wheel/data/model.dart b/lib/presentation/pages/mini_games/ferris_wheel/data/model.dart index 55a85dd..bbb1165 100644 --- a/lib/presentation/pages/mini_games/ferris_wheel/data/model.dart +++ b/lib/presentation/pages/mini_games/ferris_wheel/data/model.dart @@ -20,6 +20,7 @@ class PrizeHistory { final int value; final Color color; final IconData icon; + final String? gamePrizeId; // Added for API integration PrizeHistory({ required this.prize, @@ -27,5 +28,73 @@ class PrizeHistory { required this.value, required this.color, required this.icon, + this.gamePrizeId, }); + + // CopyWith method for updating properties + PrizeHistory copyWith({ + String? prize, + DateTime? dateTime, + int? value, + Color? color, + IconData? icon, + String? gamePrizeId, + }) { + return PrizeHistory( + prize: prize ?? this.prize, + dateTime: dateTime ?? this.dateTime, + value: value ?? this.value, + color: color ?? this.color, + icon: icon ?? this.icon, + gamePrizeId: gamePrizeId ?? this.gamePrizeId, + ); + } + + // Convert to Map for storage/API calls + Map toMap() { + return { + 'prize': prize, + 'dateTime': dateTime.toIso8601String(), + 'value': value, + 'gamePrizeId': gamePrizeId, + }; + } + + // Create from Map for loading from storage/API + factory PrizeHistory.fromMap( + Map map, { + required Color color, + required IconData icon, + }) { + return PrizeHistory( + prize: map['prize'] ?? '', + dateTime: DateTime.tryParse(map['dateTime'] ?? '') ?? DateTime.now(), + value: map['value'] ?? 0, + color: color, + icon: icon, + gamePrizeId: map['gamePrizeId'], + ); + } + + // Format date for display + String get formattedDate { + return '${dateTime.day.toString().padLeft(2, '0')}/${dateTime.month.toString().padLeft(2, '0')}/${dateTime.year} ${dateTime.hour.toString().padLeft(2, '0')}:${dateTime.minute.toString().padLeft(2, '0')}'; + } + + // Get prize category based on value + String get prizeCategory { + if (value >= 1000000) return 'Jackpot'; + if (value >= 100000) return 'Big Prize'; + if (value >= 10000) return 'Medium Prize'; + if (value >= 1000) return 'Small Prize'; + return 'Token/Spin'; + } + + // Get prize category color + Color get categoryColor { + if (value >= 1000000) return const Color(0xFFFFD700); // Gold + if (value >= 100000) return const Color(0xFFC0C0C0); // Silver + if (value >= 10000) return const Color(0xFFCD7F32); // Bronze + return color; // Default color + } } 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 fdb4925..80bd2f0 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 @@ -1,18 +1,35 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:audioplayers/audioplayers.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +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 '../../../../common/theme/theme.dart'; import '../../../../common/painter/wheel_painter.dart'; +import '../../../../injection.dart'; +import '../../../../domain/game/game.dart'; import 'data/model.dart'; @RoutePage() -class FerrisWheelPage extends StatefulWidget { +class FerrisWheelPage extends StatefulWidget implements AutoRouteWrapper { const FerrisWheelPage({super.key}); @override State createState() => _FerrisWheelPageState(); + + @override + Widget wrappedRoute(BuildContext context) => BlocProvider( + create: (context) => getIt() + ..add( + const GamePrizeLoaderEvent.fetched( + '28d5aed3-4c1b-4b7b-bad5-67cdd4919dc2', + ), + ), + child: this, + ); } class _FerrisWheelPageState extends State @@ -45,56 +62,7 @@ class _FerrisWheelPageState extends State // Game Data List prizeHistory = []; - List wheelSections = [ - WheelSection( - color: AppColor.primary, - icon: Icons.visibility, - prize: '1 JT', - value: 1000000, - ), - WheelSection( - color: AppColor.info, - icon: Icons.account_balance_wallet, - prize: '10RB', - value: 10000, - ), - WheelSection( - color: AppColor.warning, - icon: Icons.card_giftcard, - prize: '50', - value: 50, - ), - WheelSection( - color: AppColor.success, - icon: Icons.refresh, - prize: 'SPIN', - value: 1, - ), - WheelSection( - color: AppColor.primaryDark, - icon: Icons.visibility, - prize: '30', - value: 30, - ), - WheelSection( - color: AppColor.textLight, - icon: Icons.attach_money, - prize: '5RB', - value: 5000, - ), - WheelSection( - color: AppColor.secondary, - icon: Icons.redeem, - prize: '20', - value: 20, - ), - WheelSection( - color: AppColor.info, - icon: Icons.monetization_on, - prize: '5RB', - value: 5000, - ), - ]; + List gamePrizes = []; @override void initState() { @@ -146,7 +114,8 @@ class _FerrisWheelPageState extends State void _playWheelSpin() => _playSound('audio/carnival/sfx/wheel_spin.mp3', volume: 0.8); - void _playWinSound(int prizeValue) { + 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) { @@ -198,8 +167,73 @@ class _FerrisWheelPageState extends State _idleRotationController.repeat(); } + int _selectPrizeWithWeight() { + if (gamePrizes.isEmpty) return 0; + + final availablePrizes = gamePrizes + .asMap() + .entries + .where((entry) => entry.value.stock > 0) + .toList(); + + if (availablePrizes.isEmpty) { + return math.Random().nextInt(gamePrizes.length); + } + + int totalWeight = availablePrizes + .map((entry) => entry.value.weight) + .reduce((a, b) => a + b); + + if (totalWeight <= 0) { + final randomEntry = + availablePrizes[math.Random().nextInt(availablePrizes.length)]; + return randomEntry.key; + } + + int randomWeight = math.Random().nextInt(totalWeight); + int currentWeight = 0; + + for (final entry in availablePrizes) { + currentWeight += entry.value.weight; + if (randomWeight < currentWeight) { + return entry.key; + } + } + + return availablePrizes.last.key; + } + + 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]; + } + } + void _spinWheel() { - if (isSpinning || tokens <= 0) return; + if (isSpinning || tokens <= 0 || gamePrizes.isEmpty) return; _playWheelSpin(); _playTokenSound(); @@ -212,8 +246,8 @@ class _FerrisWheelPageState extends State _idleRotationController.stop(); - int targetSection = math.Random().nextInt(8); - double sectionAngle = (2 * math.pi) / 8; + int targetSection = _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; @@ -236,32 +270,40 @@ class _FerrisWheelPageState extends State _rotationController.reset(); _rotationController.animateTo(1.0).then((_) { - _playWinSound(wheelSections[targetSection].value); + final wonPrize = gamePrizes[targetSection]; + _playWinSound(wonPrize); setState(() { currentRotation = finalRotation; isSpinning = false; - resultText = - 'Selamat! Anda mendapat ${wheelSections[targetSection].prize}!'; + resultText = 'Selamat! Anda mendapat ${wonPrize.name}!'; + + if (wonPrize.stock > 0) { + gamePrizes[targetSection] = wonPrize.copyWith( + stock: wonPrize.stock - 1, + ); + } + prizeHistory.insert( 0, PrizeHistory( - prize: wheelSections[targetSection].prize, + prize: wonPrize.name, dateTime: DateTime.now(), - value: wheelSections[targetSection].value, - color: wheelSections[targetSection].color, - icon: wheelSections[targetSection].icon, + value: wonPrize.metadata['value'] ?? wonPrize.weight, + color: _getPrizeColor(wonPrize, targetSection), + icon: Icons.card_giftcard, + gamePrizeId: wonPrize.id, ), ); }); - _showWinDialog(wheelSections[targetSection]); + _showWinDialog(wonPrize); _idleRotationController.reset(); _idleRotationController.repeat(); }); } - void _showWinDialog(WheelSection section) { + void _showWinDialog(GamePrize wonPrize) { showDialog( context: context, barrierDismissible: false, @@ -294,7 +336,11 @@ class _FerrisWheelPageState extends State ), ], ), - child: Icon(section.icon, color: section.color, size: 40), + child: Icon( + Icons.card_giftcard, + color: AppColor.primary, + size: 40, + ), ), const SizedBox(height: 16), Text( @@ -311,12 +357,21 @@ class _FerrisWheelPageState extends State ), const SizedBox(height: 4), Text( - section.prize, + wonPrize.name, style: AppStyle.h5.copyWith( fontWeight: FontWeight.bold, 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: () { @@ -351,117 +406,152 @@ class _FerrisWheelPageState extends State @override Widget build(BuildContext context) { - return Scaffold( - body: Container( - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: AppColor.primaryGradient, - ), - ), - 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, - ), - ), - ], + return BlocBuilder( + builder: (context, state) { + if (state.isFetching) { + return Scaffold( + body: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: AppColor.primaryGradient, ), ), + child: Center( + child: SpinKitFadingCircle(color: AppColor.textWhite, size: 36), + ), + ), + ); + } - // 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: () { + if (gamePrizes.isEmpty && state.gamePrize.isNotEmpty) { + WidgetsBinding.instance.addPostFrameCallback((_) { + setState(() { + gamePrizes = state.gamePrize; + }); + }); + } + + return Scaffold( + body: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: AppColor.primaryGradient, + ), + ), + child: SafeArea( + child: Column( + children: [ + // Header + Container( + padding: const EdgeInsets.all(16), + child: Row( + children: [ + IconButton( + onPressed: () { _playButtonTap(); - setState(() => currentTabIndex = i); + context.router.back(); }, - 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, - ), + 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, + ), + ), + ], + ), + ), - const SizedBox(height: 20), + // 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, + ), + ), + ), + ), + ), + ], + ), + ), - // Content Area - Expanded( - child: currentTabIndex == 0 - ? _buildMainContent() - : currentTabIndex == 1 - ? _buildPrizeListContent() - : _buildHistoryContent(), + const SizedBox(height: 20), + + // Content Area + Expanded( + child: currentTabIndex == 0 + ? _buildMainContent() + : currentTabIndex == 1 + ? _buildPrizeListContent() + : _buildHistoryContent(), + ), + ], ), - ], + ), ), - ), - ), + ); + }, ); } @@ -486,45 +576,49 @@ class _FerrisWheelPageState extends State child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - 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, + BlocBuilder( + builder: (context, auth) { + return Row( children: [ - Text( - 'GPY268e42', - style: AppStyle.lg.copyWith( - fontWeight: FontWeight.bold, - color: AppColor.textPrimary, + 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, + ), + ), ), ), - Text( - 'Warrior', - style: AppStyle.sm.copyWith( - color: AppColor.textSecondary, - ), + 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: [ @@ -564,116 +658,132 @@ class _FerrisWheelPageState extends State // Wheel Section Expanded( child: Center( - child: 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, + child: gamePrizes.isEmpty + ? SpinKitFadingCircle(color: AppColor.textWhite, size: 36) + : 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, + ), + ], ), - 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)); - - return Transform.rotate( - angle: rotationAngle, - child: CustomPaint( - size: const Size(320, 320), - painter: WheelPainter(sections: wheelSections), ), - ); - }, - ), - // 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, - ), - ], + // 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, + ), + ), + ); + }, ), - 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, + + // 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), + // 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, + ), + ), + ), + ), ), - ), + ], ), - ), - ], - ), ), ), @@ -729,82 +839,100 @@ class _FerrisWheelPageState extends State ), const SizedBox(height: 20), Expanded( - child: ListView.builder( - itemCount: wheelSections.length, - itemBuilder: (context, index) { - final section = wheelSections[index]; - return Container( - margin: const EdgeInsets.only(bottom: 12), - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: AppColor.white, - borderRadius: BorderRadius.circular(12), - boxShadow: [ - BoxShadow( - color: AppColor.black.withOpacity(0.05), - blurRadius: 5, - offset: const Offset(0, 2), - ), - ], - ), - child: Row( - children: [ - Container( - width: 50, - height: 50, + child: gamePrizes.isEmpty + ? Center( + child: SpinKitFadingCircle( + color: AppColor.white.withOpacity(0.7), + size: 36, + ), + ) + : ListView.builder( + itemCount: gamePrizes.length, + itemBuilder: (context, index) { + final prize = gamePrizes[index]; + return Container( + margin: const EdgeInsets.only(bottom: 12), + padding: const EdgeInsets.all(16), decoration: BoxDecoration( - color: section.color, - borderRadius: BorderRadius.circular(25), - ), - child: Icon( - section.icon, color: AppColor.white, - size: 24, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: AppColor.black.withOpacity(0.05), + blurRadius: 5, + offset: const Offset(0, 2), + ), + ], ), - ), - const SizedBox(width: 16), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + child: Row( children: [ - Text( - section.prize, - style: AppStyle.lg.copyWith( - fontWeight: FontWeight.bold, - color: AppColor.textPrimary, + Container( + width: 50, + height: 50, + decoration: BoxDecoration( + color: _getPrizeColor(prize, index), + borderRadius: BorderRadius.circular(25), + ), + child: Icon( + Icons.card_giftcard, + color: AppColor.white, + size: 24, ), ), - Text( - 'Nilai: ${section.value}', - style: AppStyle.sm.copyWith( - color: AppColor.textSecondary, + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + prize.name, + style: AppStyle.lg.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.textPrimary, + ), + ), + Text( + 'Nilai: ${prize.metadata['value'] ?? prize.weight}', + style: AppStyle.sm.copyWith( + color: AppColor.textSecondary, + ), + ), + Text( + 'Stok: ${prize.stock}/${prize.maxStock}', + style: AppStyle.xs.copyWith( + color: prize.stock > 0 + ? AppColor.success + : AppColor.error, + ), + ), + ], + ), + ), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 6, + ), + decoration: BoxDecoration( + color: _getPrizeColor( + prize, + index, + ).withOpacity(0.1), + borderRadius: BorderRadius.circular(15), + ), + child: Text( + 'Weight: ${prize.weight}', + style: AppStyle.xs.copyWith( + color: _getPrizeColor(prize, index), + fontWeight: FontWeight.bold, + ), ), ), ], ), - ), - Container( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 6, - ), - decoration: BoxDecoration( - color: section.color.withOpacity(0.1), - borderRadius: BorderRadius.circular(15), - ), - child: Text( - 'Hadiah', - style: AppStyle.sm.copyWith( - color: section.color, - fontWeight: FontWeight.bold, - ), - ), - ), - ], + ); + }, ), - ); - }, - ), ), ], ), @@ -918,6 +1046,14 @@ class _FerrisWheelPageState extends State color: AppColor.textSecondary, ), ), + if (history.gamePrizeId != null) + Text( + 'ID: ${history.gamePrizeId}', + style: AppStyle.xs.copyWith( + color: AppColor.textSecondary + .withOpacity(0.7), + ), + ), ], ), ), diff --git a/lib/presentation/router/app_router.gr.dart b/lib/presentation/router/app_router.gr.dart index eda2b70..8e71cda 100644 --- a/lib/presentation/router/app_router.gr.dart +++ b/lib/presentation/router/app_router.gr.dart @@ -252,7 +252,7 @@ class FerrisWheelRoute extends _i33.PageRouteInfo { static _i33.PageInfo page = _i33.PageInfo( name, builder: (data) { - return const _i10.FerrisWheelPage(); + return _i33.WrappedRoute(child: const _i10.FerrisWheelPage()); }, ); }