Compare commits

..
7 Commits
Author SHA1 Message Date
Efril 7887704a5c update service type 2026-01-16 16:07:56 +07:00
Efril 4c12244d3c checkout page 2026-01-16 15:53:06 +07:00
Efril f4f775b9ed menu detail page 2026-01-16 14:41:09 +07:00
Efril 44d48d41d4 menu page 2026-01-16 13:27:09 +07:00
efrilm 82e1f93cce update 2026-01-16 12:26:06 +07:00
efrilm 1ce980a87b mystery box 2025-10-12 13:27:39 +07:00
efrilm 909c312af0 Ferish Wheel and Music 2025-09-18 14:53:39 +07:00
77 changed files with 4797 additions and 2041 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 332 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 367 KiB

@@ -0,0 +1,42 @@
import 'package:bloc/bloc.dart';
import 'package:dartz/dartz.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:injectable/injectable.dart';
import '../../../domain/game/game.dart';
part 'ferris_wheel_loader_event.dart';
part 'ferris_wheel_loader_state.dart';
part 'ferris_wheel_loader_bloc.freezed.dart';
@injectable
class FerrisWheelLoaderBloc
extends Bloc<FerrisWheelLoaderEvent, FerrisWheelLoaderState> {
final IGameRepository _repository;
FerrisWheelLoaderBloc(this._repository)
: super(FerrisWheelLoaderState.initial()) {
on<FerrisWheelLoaderEvent>(_onFerrisWheelLoaderEvent);
}
Future<void> _onFerrisWheelLoaderEvent(
FerrisWheelLoaderEvent event,
Emitter<FerrisWheelLoaderState> emit,
) {
return event.map(
fetched: (e) async {
emit(
state.copyWith(isFetching: true, failureOptionFerrisWheel: none()),
);
final result = await _repository.ferrisWheel();
var data = result.fold(
(f) => state.copyWith(failureOptionFerrisWheel: optionOf(f)),
(ferrisWheel) => state.copyWith(ferrisWheel: ferrisWheel),
);
emit(data.copyWith(isFetching: false));
},
);
}
}
@@ -0,0 +1,388 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'ferris_wheel_loader_bloc.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models',
);
/// @nodoc
mixin _$FerrisWheelLoaderEvent {
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() fetched,
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? fetched,
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? fetched,
required TResult orElse(),
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Fetched value) fetched,
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Fetched value)? fetched,
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Fetched value)? fetched,
required TResult orElse(),
}) => throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $FerrisWheelLoaderEventCopyWith<$Res> {
factory $FerrisWheelLoaderEventCopyWith(
FerrisWheelLoaderEvent value,
$Res Function(FerrisWheelLoaderEvent) then,
) = _$FerrisWheelLoaderEventCopyWithImpl<$Res, FerrisWheelLoaderEvent>;
}
/// @nodoc
class _$FerrisWheelLoaderEventCopyWithImpl<
$Res,
$Val extends FerrisWheelLoaderEvent
>
implements $FerrisWheelLoaderEventCopyWith<$Res> {
_$FerrisWheelLoaderEventCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of FerrisWheelLoaderEvent
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
abstract class _$$FetchedImplCopyWith<$Res> {
factory _$$FetchedImplCopyWith(
_$FetchedImpl value,
$Res Function(_$FetchedImpl) then,
) = __$$FetchedImplCopyWithImpl<$Res>;
}
/// @nodoc
class __$$FetchedImplCopyWithImpl<$Res>
extends _$FerrisWheelLoaderEventCopyWithImpl<$Res, _$FetchedImpl>
implements _$$FetchedImplCopyWith<$Res> {
__$$FetchedImplCopyWithImpl(
_$FetchedImpl _value,
$Res Function(_$FetchedImpl) _then,
) : super(_value, _then);
/// Create a copy of FerrisWheelLoaderEvent
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
class _$FetchedImpl implements _Fetched {
const _$FetchedImpl();
@override
String toString() {
return 'FerrisWheelLoaderEvent.fetched()';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType && other is _$FetchedImpl);
}
@override
int get hashCode => runtimeType.hashCode;
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({required TResult Function() fetched}) {
return fetched();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({TResult? Function()? fetched}) {
return fetched?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? fetched,
required TResult orElse(),
}) {
if (fetched != null) {
return fetched();
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Fetched value) fetched,
}) {
return fetched(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Fetched value)? fetched,
}) {
return fetched?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Fetched value)? fetched,
required TResult orElse(),
}) {
if (fetched != null) {
return fetched(this);
}
return orElse();
}
}
abstract class _Fetched implements FerrisWheelLoaderEvent {
const factory _Fetched() = _$FetchedImpl;
}
/// @nodoc
mixin _$FerrisWheelLoaderState {
Game get ferrisWheel => throw _privateConstructorUsedError;
Option<GameFailure> get failureOptionFerrisWheel =>
throw _privateConstructorUsedError;
bool get isFetching => throw _privateConstructorUsedError;
/// Create a copy of FerrisWheelLoaderState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$FerrisWheelLoaderStateCopyWith<FerrisWheelLoaderState> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $FerrisWheelLoaderStateCopyWith<$Res> {
factory $FerrisWheelLoaderStateCopyWith(
FerrisWheelLoaderState value,
$Res Function(FerrisWheelLoaderState) then,
) = _$FerrisWheelLoaderStateCopyWithImpl<$Res, FerrisWheelLoaderState>;
@useResult
$Res call({
Game ferrisWheel,
Option<GameFailure> failureOptionFerrisWheel,
bool isFetching,
});
$GameCopyWith<$Res> get ferrisWheel;
}
/// @nodoc
class _$FerrisWheelLoaderStateCopyWithImpl<
$Res,
$Val extends FerrisWheelLoaderState
>
implements $FerrisWheelLoaderStateCopyWith<$Res> {
_$FerrisWheelLoaderStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of FerrisWheelLoaderState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? ferrisWheel = null,
Object? failureOptionFerrisWheel = null,
Object? isFetching = null,
}) {
return _then(
_value.copyWith(
ferrisWheel: null == ferrisWheel
? _value.ferrisWheel
: ferrisWheel // ignore: cast_nullable_to_non_nullable
as Game,
failureOptionFerrisWheel: null == failureOptionFerrisWheel
? _value.failureOptionFerrisWheel
: failureOptionFerrisWheel // ignore: cast_nullable_to_non_nullable
as Option<GameFailure>,
isFetching: null == isFetching
? _value.isFetching
: isFetching // ignore: cast_nullable_to_non_nullable
as bool,
)
as $Val,
);
}
/// Create a copy of FerrisWheelLoaderState
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$GameCopyWith<$Res> get ferrisWheel {
return $GameCopyWith<$Res>(_value.ferrisWheel, (value) {
return _then(_value.copyWith(ferrisWheel: value) as $Val);
});
}
}
/// @nodoc
abstract class _$$FerrisWheelLoaderStateImplCopyWith<$Res>
implements $FerrisWheelLoaderStateCopyWith<$Res> {
factory _$$FerrisWheelLoaderStateImplCopyWith(
_$FerrisWheelLoaderStateImpl value,
$Res Function(_$FerrisWheelLoaderStateImpl) then,
) = __$$FerrisWheelLoaderStateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({
Game ferrisWheel,
Option<GameFailure> failureOptionFerrisWheel,
bool isFetching,
});
@override
$GameCopyWith<$Res> get ferrisWheel;
}
/// @nodoc
class __$$FerrisWheelLoaderStateImplCopyWithImpl<$Res>
extends
_$FerrisWheelLoaderStateCopyWithImpl<$Res, _$FerrisWheelLoaderStateImpl>
implements _$$FerrisWheelLoaderStateImplCopyWith<$Res> {
__$$FerrisWheelLoaderStateImplCopyWithImpl(
_$FerrisWheelLoaderStateImpl _value,
$Res Function(_$FerrisWheelLoaderStateImpl) _then,
) : super(_value, _then);
/// Create a copy of FerrisWheelLoaderState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? ferrisWheel = null,
Object? failureOptionFerrisWheel = null,
Object? isFetching = null,
}) {
return _then(
_$FerrisWheelLoaderStateImpl(
ferrisWheel: null == ferrisWheel
? _value.ferrisWheel
: ferrisWheel // ignore: cast_nullable_to_non_nullable
as Game,
failureOptionFerrisWheel: null == failureOptionFerrisWheel
? _value.failureOptionFerrisWheel
: failureOptionFerrisWheel // ignore: cast_nullable_to_non_nullable
as Option<GameFailure>,
isFetching: null == isFetching
? _value.isFetching
: isFetching // ignore: cast_nullable_to_non_nullable
as bool,
),
);
}
}
/// @nodoc
class _$FerrisWheelLoaderStateImpl implements _FerrisWheelLoaderState {
const _$FerrisWheelLoaderStateImpl({
required this.ferrisWheel,
required this.failureOptionFerrisWheel,
this.isFetching = false,
});
@override
final Game ferrisWheel;
@override
final Option<GameFailure> failureOptionFerrisWheel;
@override
@JsonKey()
final bool isFetching;
@override
String toString() {
return 'FerrisWheelLoaderState(ferrisWheel: $ferrisWheel, failureOptionFerrisWheel: $failureOptionFerrisWheel, isFetching: $isFetching)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$FerrisWheelLoaderStateImpl &&
(identical(other.ferrisWheel, ferrisWheel) ||
other.ferrisWheel == ferrisWheel) &&
(identical(
other.failureOptionFerrisWheel,
failureOptionFerrisWheel,
) ||
other.failureOptionFerrisWheel == failureOptionFerrisWheel) &&
(identical(other.isFetching, isFetching) ||
other.isFetching == isFetching));
}
@override
int get hashCode => Object.hash(
runtimeType,
ferrisWheel,
failureOptionFerrisWheel,
isFetching,
);
/// Create a copy of FerrisWheelLoaderState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$FerrisWheelLoaderStateImplCopyWith<_$FerrisWheelLoaderStateImpl>
get copyWith =>
__$$FerrisWheelLoaderStateImplCopyWithImpl<_$FerrisWheelLoaderStateImpl>(
this,
_$identity,
);
}
abstract class _FerrisWheelLoaderState implements FerrisWheelLoaderState {
const factory _FerrisWheelLoaderState({
required final Game ferrisWheel,
required final Option<GameFailure> failureOptionFerrisWheel,
final bool isFetching,
}) = _$FerrisWheelLoaderStateImpl;
@override
Game get ferrisWheel;
@override
Option<GameFailure> get failureOptionFerrisWheel;
@override
bool get isFetching;
/// Create a copy of FerrisWheelLoaderState
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$FerrisWheelLoaderStateImplCopyWith<_$FerrisWheelLoaderStateImpl>
get copyWith => throw _privateConstructorUsedError;
}
@@ -0,0 +1,6 @@
part of 'ferris_wheel_loader_bloc.dart';
@freezed
class FerrisWheelLoaderEvent with _$FerrisWheelLoaderEvent {
const factory FerrisWheelLoaderEvent.fetched() = _Fetched;
}
@@ -0,0 +1,15 @@
part of 'ferris_wheel_loader_bloc.dart';
@freezed
class FerrisWheelLoaderState with _$FerrisWheelLoaderState {
const factory FerrisWheelLoaderState({
required Game ferrisWheel,
required Option<GameFailure> failureOptionFerrisWheel,
@Default(false) bool isFetching,
}) = _FerrisWheelLoaderState;
factory FerrisWheelLoaderState.initial() => FerrisWheelLoaderState(
ferrisWheel: Game.empty(),
failureOptionFerrisWheel: none(),
);
}
@@ -1,40 +0,0 @@
import 'package:bloc/bloc.dart';
import 'package:dartz/dartz.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:injectable/injectable.dart';
import '../../../domain/game/game.dart';
part 'game_prize_loader_event.dart';
part 'game_prize_loader_state.dart';
part 'game_prize_loader_bloc.freezed.dart';
@injectable
class GamePrizeLoaderBloc
extends Bloc<GamePrizeLoaderEvent, GamePrizeLoaderState> {
final IGameRepository _repository;
GamePrizeLoaderBloc(this._repository)
: super(GamePrizeLoaderState.initial()) {
on<GamePrizeLoaderEvent>(_onGamePrizeLoaderEvent);
}
Future<void> _onGamePrizeLoaderEvent(
GamePrizeLoaderEvent event,
Emitter<GamePrizeLoaderState> 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));
},
);
}
}
@@ -1,442 +0,0 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'game_prize_loader_bloc.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(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<TResult extends Object?>({
required TResult Function(String id) fetched,
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function(String id)? fetched,
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(String id)? fetched,
required TResult orElse(),
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Fetched value) fetched,
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Fetched value)? fetched,
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
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<GamePrizeLoaderEvent> 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<TResult extends Object?>({
required TResult Function(String id) fetched,
}) {
return fetched(id);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function(String id)? fetched,
}) {
return fetched?.call(id);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(String id)? fetched,
required TResult orElse(),
}) {
if (fetched != null) {
return fetched(id);
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Fetched value) fetched,
}) {
return fetched(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Fetched value)? fetched,
}) {
return fetched?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
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<GamePrize> get gamePrize => throw _privateConstructorUsedError;
Option<GameFailure> 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<GamePrizeLoaderState> 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> gamePrize,
Option<GameFailure> 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<GamePrize>,
failureOptionGamePrize: null == failureOptionGamePrize
? _value.failureOptionGamePrize
: failureOptionGamePrize // ignore: cast_nullable_to_non_nullable
as Option<GameFailure>,
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> gamePrize,
Option<GameFailure> 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<GamePrize>,
failureOptionGamePrize: null == failureOptionGamePrize
? _value.failureOptionGamePrize
: failureOptionGamePrize // ignore: cast_nullable_to_non_nullable
as Option<GameFailure>,
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> gamePrize,
required this.failureOptionGamePrize,
this.isFetching = false,
}) : _gamePrize = gamePrize;
final List<GamePrize> _gamePrize;
@override
List<GamePrize> get gamePrize {
if (_gamePrize is EqualUnmodifiableListView) return _gamePrize;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_gamePrize);
}
@override
final Option<GameFailure> 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> gamePrize,
required final Option<GameFailure> failureOptionGamePrize,
final bool isFetching,
}) = _$GamePrizeLoaderStateImpl;
@override
List<GamePrize> get gamePrize;
@override
Option<GameFailure> 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;
}
@@ -1,6 +0,0 @@
part of 'game_prize_loader_bloc.dart';
@freezed
class GamePrizeLoaderEvent with _$GamePrizeLoaderEvent {
const factory GamePrizeLoaderEvent.fetched(String id) = _Fetched;
}
@@ -1,13 +0,0 @@
part of 'game_prize_loader_bloc.dart';
@freezed
class GamePrizeLoaderState with _$GamePrizeLoaderState {
const factory GamePrizeLoaderState({
required List<GamePrize> gamePrize,
required Option<GameFailure> failureOptionGamePrize,
@Default(false) bool isFetching,
}) = _GamePrizeLoaderState;
factory GamePrizeLoaderState.initial() =>
GamePrizeLoaderState(gamePrize: [], failureOptionGamePrize: none());
}
+7 -1
View File
@@ -1,3 +1,9 @@
import '../../sample/sample_data.dart';
class AppConstant { class AppConstant {
static const String appName = ""; static const String appName = "Enaklo";
static const String coinName = "EnakCoin";
static const String poinName = "EnakPoin";
} }
MerchantModel merchant = merchants.first;
+26
View File
@@ -0,0 +1,26 @@
import '../../presentation/components/assets/assets.gen.dart';
class Service {
Service({
required this.name,
required this.description,
required this.imagePath,
});
final String name;
final String imagePath;
final String description;
}
List<Service> services = [
Service(
name: 'Dine In',
description: 'Makan langsung di tempat',
imagePath: Assets.icons.dineIn.path,
),
Service(
name: 'Take Away',
description: 'Pesan dan bawa pulang',
imagePath: Assets.icons.takeaway.path,
),
];
@@ -0,0 +1,28 @@
part of 'extension.dart';
extension DoubleExt on double {
String get currencyFormatRpV2 => NumberFormat.currency(
locale: 'id',
symbol: 'Rp ',
decimalDigits: 0,
).format(this);
}
extension StringX on String {
String get currencyFormatRp {
final parsedValue = int.tryParse(this) ?? 0;
return NumberFormat.currency(
locale: 'id',
symbol: 'Rp ',
decimalDigits: 0,
).format(parsedValue);
}
}
extension IntegerExt on int {
String get currencyFormatRp => NumberFormat.currency(
locale: 'id',
symbol: 'Rp ',
decimalDigits: 0,
).format(this);
}
+1
View File
@@ -3,6 +3,7 @@ import 'package:intl/intl.dart';
import '../../domain/auth/auth.dart'; import '../../domain/auth/auth.dart';
part 'date_extension.dart'; part 'date_extension.dart';
part 'currency_extension.dart';
extension StringExt on String { extension StringExt on String {
CheckPhoneStatus toCheckPhoneStatus() { CheckPhoneStatus toCheckPhoneStatus() {
+43 -154
View File
@@ -1,8 +1,7 @@
// wheel_painter.dart - Fixed implementation with consistent positioning
import 'dart:math' as math; import 'dart:math' as math;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../domain/game/game.dart'; import '../../domain/game/game.dart';
import '../theme/theme.dart';
class WheelPainter extends CustomPainter { class WheelPainter extends CustomPainter {
final List<GamePrize> gamePrizes; final List<GamePrize> gamePrizes;
@@ -36,10 +35,11 @@ class WheelPainter extends CustomPainter {
..style = PaintingStyle.fill; ..style = PaintingStyle.fill;
canvas.drawCircle(center, radius - 20, innerWhitePaint); canvas.drawCircle(center, radius - 20, innerWhitePaint);
// Draw sections // Draw sections - KONSISTEN dengan logic spin
for (int i = 0; i < gamePrizes.length; i++) { for (int i = 0; i < gamePrizes.length; i++) {
final prize = gamePrizes[i]; final prize = gamePrizes[i];
final startAngle = i * sectionAngle - math.pi / 2; // Section 0 di top (-π/2), section 1 di kanan atas, dst (clockwise)
final startAngle = (-math.pi / 2) + (i * sectionAngle);
// Section background // Section background
final sectionPaint = Paint() final sectionPaint = Paint()
@@ -54,54 +54,13 @@ class WheelPainter extends CustomPainter {
sectionPaint, sectionPaint,
); );
// Draw icon in each section
final iconAngle = startAngle + sectionAngle / 2;
final iconPosition = Offset(
center.dx + (radius - 60) * math.cos(iconAngle),
center.dy + (radius - 60) * math.sin(iconAngle),
);
// Draw icon background circle
final iconBgPaint = Paint()
..color = Colors.white
..style = PaintingStyle.fill;
canvas.drawCircle(iconPosition, 16, iconBgPaint);
// Save canvas state for icon drawing
canvas.save();
canvas.translate(iconPosition.dx, iconPosition.dy);
// Get icon from metadata or use default
final iconData = _getIconFromMetadata(prize.metadata);
final iconText = _getIconText(iconData);
final iconTextPainter = TextPainter(
text: TextSpan(
text: iconText,
style: TextStyle(
fontFamily: 'MaterialIcons',
fontSize: 20,
color: getPrizeColor(prize, i),
fontWeight: FontWeight.normal,
),
),
textDirection: TextDirection.ltr,
);
iconTextPainter.layout();
iconTextPainter.paint(
canvas,
Offset(-iconTextPainter.width / 2, -iconTextPainter.height / 2),
);
canvas.restore();
// Draw prize text // Draw prize text
final textAngle = startAngle + sectionAngle / 2; final textAngle = startAngle + sectionAngle / 2;
final textPosition = Offset( final textPosition = Offset(
center.dx + (radius - 100) * math.cos(textAngle), center.dx + (radius - 80) * math.cos(textAngle),
center.dy + (radius - 100) * math.sin(textAngle), center.dy + (radius - 80) * math.sin(textAngle),
); );
// Save canvas state for text rotation
canvas.save(); canvas.save();
canvas.translate(textPosition.dx, textPosition.dy); canvas.translate(textPosition.dx, textPosition.dy);
canvas.rotate(textAngle + math.pi / 2); canvas.rotate(textAngle + math.pi / 2);
@@ -111,62 +70,51 @@ class WheelPainter extends CustomPainter {
text: prize.name, text: prize.name,
style: const TextStyle( style: const TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 10, fontSize: 12,
fontWeight: FontWeight.bold,
),
),
textDirection: TextDirection.ltr,
textAlign: TextAlign.center,
);
textPainter.layout(maxWidth: 100);
textPainter.paint(
canvas,
Offset(-textPainter.width / 2, -textPainter.height / 2),
);
canvas.restore();
// DEBUG: Draw section number
final numberPosition = Offset(
center.dx + (radius - 50) * math.cos(textAngle),
center.dy + (radius - 50) * math.sin(textAngle),
);
canvas.drawCircle(numberPosition, 15, Paint()..color = Colors.white);
final numberPainter = TextPainter(
text: TextSpan(
text: i.toString(),
style: const TextStyle(
color: Colors.black,
fontSize: 14,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
); );
textPainter.layout(); numberPainter.layout();
textPainter.paint( numberPainter.paint(
canvas, canvas,
Offset(-textPainter.width / 2, -textPainter.height / 2), Offset(
numberPosition.dx - numberPainter.width / 2,
numberPosition.dy - numberPainter.height / 2,
),
); );
canvas.restore();
// Draw stock indicator if stock is low
if (prize.stock <= 5 && prize.stock > 0) {
final stockAngle = startAngle + sectionAngle / 2;
final stockPosition = Offset(
center.dx + (radius - 35) * math.cos(stockAngle),
center.dy + (radius - 35) * math.sin(stockAngle),
);
final stockPaint = Paint()
..color = AppColor.error
..style = PaintingStyle.fill;
canvas.drawCircle(stockPosition, 8, stockPaint);
canvas.save();
canvas.translate(stockPosition.dx, stockPosition.dy);
final stockTextPainter = TextPainter(
text: TextSpan(
text: '!',
style: const TextStyle(
color: Colors.white,
fontSize: 10,
fontWeight: FontWeight.bold,
),
),
textDirection: TextDirection.ltr,
);
stockTextPainter.layout();
stockTextPainter.paint(
canvas,
Offset(-stockTextPainter.width / 2, -stockTextPainter.height / 2),
);
canvas.restore();
}
} }
// Draw white dots around the outer edge // Draw white dots
final dotPaint = Paint() final dotPaint = Paint()
..color = Colors.white ..color = Colors.white
..style = PaintingStyle.fill; ..style = PaintingStyle.fill;
for (int i = 0; i < 24; i++) { for (int i = 0; i < 24; i++) {
final dotAngle = (2 * math.pi / 24) * i; final dotAngle = (2 * math.pi / 24) * i;
final dotPosition = Offset( final dotPosition = Offset(
@@ -178,15 +126,14 @@ class WheelPainter extends CustomPainter {
// Draw section dividers // Draw section dividers
final dividerPaint = Paint() final dividerPaint = Paint()
..color = AppColor.white ..color = Colors.white
..style = PaintingStyle.stroke ..style = PaintingStyle.stroke
..strokeWidth = 2; ..strokeWidth = 2;
for (int i = 0; i < gamePrizes.length; i++) { for (int i = 0; i < gamePrizes.length; i++) {
final angle = i * sectionAngle - math.pi / 2; final angle = (-math.pi / 2) + (i * sectionAngle);
final lineStart = Offset( final lineStart = Offset(
center.dx + (radius - 130) * math.cos(angle), center.dx + (radius - 110) * math.cos(angle),
center.dy + (radius - 130) * math.sin(angle), center.dy + (radius - 110) * math.sin(angle),
); );
final lineEnd = Offset( final lineEnd = Offset(
center.dx + (radius - 22) * math.cos(angle), center.dx + (radius - 22) * math.cos(angle),
@@ -196,64 +143,6 @@ class WheelPainter extends CustomPainter {
} }
} }
IconData _getIconFromMetadata(Map<String, dynamic> metadata) {
final iconName = metadata['icon'] as String?;
switch (iconName?.toLowerCase()) {
case 'card_giftcard':
return Icons.card_giftcard;
case 'monetization_on':
return Icons.monetization_on;
case 'redeem':
return Icons.redeem;
case 'attach_money':
return Icons.attach_money;
case 'account_balance_wallet':
return Icons.account_balance_wallet;
case 'diamond':
return Icons.diamond;
case 'star':
return Icons.star;
case 'emoji_events':
return Icons.emoji_events;
case 'refresh':
return Icons.refresh;
case 'visibility':
return Icons.visibility;
default:
return Icons.card_giftcard; // Default icon
}
}
String _getIconText(IconData icon) {
// Convert IconData to Unicode string for drawing
switch (icon.codePoint) {
case 0xe8f4: // Icons.visibility
return String.fromCharCode(0xe8f4);
case 0xe8f5: // Icons.visibility_off
return String.fromCharCode(0xe8f5);
case 0xe850: // Icons.account_balance_wallet
return String.fromCharCode(0xe850);
case 0xe151: // Icons.card_giftcard
return String.fromCharCode(0xe151);
case 0xe5d5: // Icons.refresh
return String.fromCharCode(0xe5d5);
case 0xe263: // Icons.attach_money
return String.fromCharCode(0xe263);
case 0xe8a1: // Icons.redeem
return String.fromCharCode(0xe8a1);
case 0xe57d: // Icons.monetization_on
return String.fromCharCode(0xe57d);
case 0xe5ca: // Icons.diamond
return String.fromCharCode(0xe5ca);
case 0xe838: // Icons.star
return String.fromCharCode(0xe838);
case 0xe2a3: // Icons.emoji_events
return String.fromCharCode(0xe2a3);
default:
return String.fromCharCode(0xe151); // Default card_giftcard icon
}
}
@override @override
bool shouldRepaint(covariant CustomPainter oldDelegate) => true; bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
} }
+5 -1
View File
@@ -1,3 +1,7 @@
part of 'theme.dart'; part of 'theme.dart';
class AppValue {} class AppValue {
static const double padding = 16;
static const double margin = 16;
static const double borderRadius = 12;
}
+13 -2
View File
@@ -43,7 +43,7 @@ class ThemeApp {
foregroundColor: AppColor.textPrimary, foregroundColor: AppColor.textPrimary,
elevation: 0, elevation: 0,
titleTextStyle: AppStyle.xl.copyWith( titleTextStyle: AppStyle.xl.copyWith(
color: AppColor.primary, color: AppColor.textPrimary,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
), ),
centerTitle: true, centerTitle: true,
@@ -55,7 +55,18 @@ class ThemeApp {
backgroundColor: AppColor.primary, backgroundColor: AppColor.primary,
foregroundColor: Colors.white, foregroundColor: Colors.white,
elevation: 0, elevation: 0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)), shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppValue.borderRadius),
),
),
),
outlinedButtonTheme: OutlinedButtonThemeData(
style: OutlinedButton.styleFrom(
foregroundColor: AppColor.primary,
shape: RoundedRectangleBorder(
side: BorderSide(color: AppColor.border),
borderRadius: BorderRadiusGeometry.circular(AppValue.borderRadius),
),
), ),
), ),
inputDecorationTheme: InputDecorationTheme( inputDecorationTheme: InputDecorationTheme(
+2 -1
View File
@@ -7,7 +7,8 @@ class ApiPath {
static String resend = '/api/v1/customer-auth/resend-otp'; static String resend = '/api/v1/customer-auth/resend-otp';
// Marketing // Marketing
static String gamePrizeByGameId = '/api/v1/marketing/game-prizes/game'; static String ferrisWheel = '/api/v1/customer/ferris-wheel';
// Customer // Customer
static String customerPoint = '/api/v1/customer/points'; static String customerPoint = '/api/v1/customer/points';
} }
+2
View File
@@ -8,6 +8,7 @@ class Game with _$Game {
required String type, required String type,
required bool isActive, required bool isActive,
required Map<String, dynamic> metadata, required Map<String, dynamic> metadata,
required List<GamePrize> prizes,
required String createdAt, required String createdAt,
required String updatedAt, required String updatedAt,
}) = _Game; }) = _Game;
@@ -18,6 +19,7 @@ class Game with _$Game {
type: '', type: '',
isActive: false, isActive: false,
metadata: {}, metadata: {},
prizes: [],
createdAt: '', createdAt: '',
updatedAt: '', updatedAt: '',
); );
+2 -12
View File
@@ -6,26 +6,16 @@ class GamePrize with _$GamePrize {
required String id, required String id,
required String gameId, required String gameId,
required String name, required String name,
required int weight,
required int stock,
required int maxStock,
required int threshold,
required Map<String, dynamic> metadata, required Map<String, dynamic> metadata,
required Game game,
required String createdAt, required String createdAt,
required String updatedAt, required String updatedAt,
}) = _GamePrize; }) = _GamePrize;
factory GamePrize.empty() => GamePrize( factory GamePrize.empty() => const GamePrize(
id: '', id: '',
gameId: '', gameId: '',
name: '', name: '',
weight: 0, metadata: {},
stock: 0,
maxStock: 0,
threshold: 0,
metadata: const {},
game: Game.empty(),
createdAt: '', createdAt: '',
updatedAt: '', updatedAt: '',
); );
+31 -125
View File
@@ -22,6 +22,7 @@ mixin _$Game {
String get type => throw _privateConstructorUsedError; String get type => throw _privateConstructorUsedError;
bool get isActive => throw _privateConstructorUsedError; bool get isActive => throw _privateConstructorUsedError;
Map<String, dynamic> get metadata => throw _privateConstructorUsedError; Map<String, dynamic> get metadata => throw _privateConstructorUsedError;
List<GamePrize> get prizes => throw _privateConstructorUsedError;
String get createdAt => throw _privateConstructorUsedError; String get createdAt => throw _privateConstructorUsedError;
String get updatedAt => throw _privateConstructorUsedError; String get updatedAt => throw _privateConstructorUsedError;
@@ -42,6 +43,7 @@ abstract class $GameCopyWith<$Res> {
String type, String type,
bool isActive, bool isActive,
Map<String, dynamic> metadata, Map<String, dynamic> metadata,
List<GamePrize> prizes,
String createdAt, String createdAt,
String updatedAt, String updatedAt,
}); });
@@ -67,6 +69,7 @@ class _$GameCopyWithImpl<$Res, $Val extends Game>
Object? type = null, Object? type = null,
Object? isActive = null, Object? isActive = null,
Object? metadata = null, Object? metadata = null,
Object? prizes = null,
Object? createdAt = null, Object? createdAt = null,
Object? updatedAt = null, Object? updatedAt = null,
}) { }) {
@@ -92,6 +95,10 @@ class _$GameCopyWithImpl<$Res, $Val extends Game>
? _value.metadata ? _value.metadata
: metadata // ignore: cast_nullable_to_non_nullable : metadata // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>, as Map<String, dynamic>,
prizes: null == prizes
? _value.prizes
: prizes // ignore: cast_nullable_to_non_nullable
as List<GamePrize>,
createdAt: null == createdAt createdAt: null == createdAt
? _value.createdAt ? _value.createdAt
: createdAt // ignore: cast_nullable_to_non_nullable : createdAt // ignore: cast_nullable_to_non_nullable
@@ -120,6 +127,7 @@ abstract class _$$GameImplCopyWith<$Res> implements $GameCopyWith<$Res> {
String type, String type,
bool isActive, bool isActive,
Map<String, dynamic> metadata, Map<String, dynamic> metadata,
List<GamePrize> prizes,
String createdAt, String createdAt,
String updatedAt, String updatedAt,
}); });
@@ -142,6 +150,7 @@ class __$$GameImplCopyWithImpl<$Res>
Object? type = null, Object? type = null,
Object? isActive = null, Object? isActive = null,
Object? metadata = null, Object? metadata = null,
Object? prizes = null,
Object? createdAt = null, Object? createdAt = null,
Object? updatedAt = null, Object? updatedAt = null,
}) { }) {
@@ -167,6 +176,10 @@ class __$$GameImplCopyWithImpl<$Res>
? _value._metadata ? _value._metadata
: metadata // ignore: cast_nullable_to_non_nullable : metadata // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>, as Map<String, dynamic>,
prizes: null == prizes
? _value._prizes
: prizes // ignore: cast_nullable_to_non_nullable
as List<GamePrize>,
createdAt: null == createdAt createdAt: null == createdAt
? _value.createdAt ? _value.createdAt
: createdAt // ignore: cast_nullable_to_non_nullable : createdAt // ignore: cast_nullable_to_non_nullable
@@ -189,9 +202,11 @@ class _$GameImpl implements _Game {
required this.type, required this.type,
required this.isActive, required this.isActive,
required final Map<String, dynamic> metadata, required final Map<String, dynamic> metadata,
required final List<GamePrize> prizes,
required this.createdAt, required this.createdAt,
required this.updatedAt, required this.updatedAt,
}) : _metadata = metadata; }) : _metadata = metadata,
_prizes = prizes;
@override @override
final String id; final String id;
@@ -209,6 +224,14 @@ class _$GameImpl implements _Game {
return EqualUnmodifiableMapView(_metadata); return EqualUnmodifiableMapView(_metadata);
} }
final List<GamePrize> _prizes;
@override
List<GamePrize> get prizes {
if (_prizes is EqualUnmodifiableListView) return _prizes;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_prizes);
}
@override @override
final String createdAt; final String createdAt;
@override @override
@@ -216,7 +239,7 @@ class _$GameImpl implements _Game {
@override @override
String toString() { String toString() {
return 'Game(id: $id, name: $name, type: $type, isActive: $isActive, metadata: $metadata, createdAt: $createdAt, updatedAt: $updatedAt)'; return 'Game(id: $id, name: $name, type: $type, isActive: $isActive, metadata: $metadata, prizes: $prizes, createdAt: $createdAt, updatedAt: $updatedAt)';
} }
@override @override
@@ -230,6 +253,7 @@ class _$GameImpl implements _Game {
(identical(other.isActive, isActive) || (identical(other.isActive, isActive) ||
other.isActive == isActive) && other.isActive == isActive) &&
const DeepCollectionEquality().equals(other._metadata, _metadata) && const DeepCollectionEquality().equals(other._metadata, _metadata) &&
const DeepCollectionEquality().equals(other._prizes, _prizes) &&
(identical(other.createdAt, createdAt) || (identical(other.createdAt, createdAt) ||
other.createdAt == createdAt) && other.createdAt == createdAt) &&
(identical(other.updatedAt, updatedAt) || (identical(other.updatedAt, updatedAt) ||
@@ -244,6 +268,7 @@ class _$GameImpl implements _Game {
type, type,
isActive, isActive,
const DeepCollectionEquality().hash(_metadata), const DeepCollectionEquality().hash(_metadata),
const DeepCollectionEquality().hash(_prizes),
createdAt, createdAt,
updatedAt, updatedAt,
); );
@@ -264,6 +289,7 @@ abstract class _Game implements Game {
required final String type, required final String type,
required final bool isActive, required final bool isActive,
required final Map<String, dynamic> metadata, required final Map<String, dynamic> metadata,
required final List<GamePrize> prizes,
required final String createdAt, required final String createdAt,
required final String updatedAt, required final String updatedAt,
}) = _$GameImpl; }) = _$GameImpl;
@@ -279,6 +305,8 @@ abstract class _Game implements Game {
@override @override
Map<String, dynamic> get metadata; Map<String, dynamic> get metadata;
@override @override
List<GamePrize> get prizes;
@override
String get createdAt; String get createdAt;
@override @override
String get updatedAt; String get updatedAt;
@@ -296,12 +324,7 @@ mixin _$GamePrize {
String get id => throw _privateConstructorUsedError; String get id => throw _privateConstructorUsedError;
String get gameId => throw _privateConstructorUsedError; String get gameId => throw _privateConstructorUsedError;
String get name => 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<String, dynamic> get metadata => throw _privateConstructorUsedError; Map<String, dynamic> get metadata => throw _privateConstructorUsedError;
Game get game => throw _privateConstructorUsedError;
String get createdAt => throw _privateConstructorUsedError; String get createdAt => throw _privateConstructorUsedError;
String get updatedAt => throw _privateConstructorUsedError; String get updatedAt => throw _privateConstructorUsedError;
@@ -321,17 +344,10 @@ abstract class $GamePrizeCopyWith<$Res> {
String id, String id,
String gameId, String gameId,
String name, String name,
int weight,
int stock,
int maxStock,
int threshold,
Map<String, dynamic> metadata, Map<String, dynamic> metadata,
Game game,
String createdAt, String createdAt,
String updatedAt, String updatedAt,
}); });
$GameCopyWith<$Res> get game;
} }
/// @nodoc /// @nodoc
@@ -352,12 +368,7 @@ class _$GamePrizeCopyWithImpl<$Res, $Val extends GamePrize>
Object? id = null, Object? id = null,
Object? gameId = null, Object? gameId = null,
Object? name = null, Object? name = null,
Object? weight = null,
Object? stock = null,
Object? maxStock = null,
Object? threshold = null,
Object? metadata = null, Object? metadata = null,
Object? game = null,
Object? createdAt = null, Object? createdAt = null,
Object? updatedAt = null, Object? updatedAt = null,
}) { }) {
@@ -375,30 +386,10 @@ class _$GamePrizeCopyWithImpl<$Res, $Val extends GamePrize>
? _value.name ? _value.name
: name // ignore: cast_nullable_to_non_nullable : name // ignore: cast_nullable_to_non_nullable
as String, 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 metadata: null == metadata
? _value.metadata ? _value.metadata
: metadata // ignore: cast_nullable_to_non_nullable : metadata // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>, as Map<String, dynamic>,
game: null == game
? _value.game
: game // ignore: cast_nullable_to_non_nullable
as Game,
createdAt: null == createdAt createdAt: null == createdAt
? _value.createdAt ? _value.createdAt
: createdAt // ignore: cast_nullable_to_non_nullable : createdAt // ignore: cast_nullable_to_non_nullable
@@ -411,16 +402,6 @@ class _$GamePrizeCopyWithImpl<$Res, $Val extends GamePrize>
as $Val, 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 /// @nodoc
@@ -436,18 +417,10 @@ abstract class _$$GamePrizeImplCopyWith<$Res>
String id, String id,
String gameId, String gameId,
String name, String name,
int weight,
int stock,
int maxStock,
int threshold,
Map<String, dynamic> metadata, Map<String, dynamic> metadata,
Game game,
String createdAt, String createdAt,
String updatedAt, String updatedAt,
}); });
@override
$GameCopyWith<$Res> get game;
} }
/// @nodoc /// @nodoc
@@ -467,12 +440,7 @@ class __$$GamePrizeImplCopyWithImpl<$Res>
Object? id = null, Object? id = null,
Object? gameId = null, Object? gameId = null,
Object? name = null, Object? name = null,
Object? weight = null,
Object? stock = null,
Object? maxStock = null,
Object? threshold = null,
Object? metadata = null, Object? metadata = null,
Object? game = null,
Object? createdAt = null, Object? createdAt = null,
Object? updatedAt = null, Object? updatedAt = null,
}) { }) {
@@ -490,30 +458,10 @@ class __$$GamePrizeImplCopyWithImpl<$Res>
? _value.name ? _value.name
: name // ignore: cast_nullable_to_non_nullable : name // ignore: cast_nullable_to_non_nullable
as String, 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 metadata: null == metadata
? _value._metadata ? _value._metadata
: metadata // ignore: cast_nullable_to_non_nullable : metadata // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>, as Map<String, dynamic>,
game: null == game
? _value.game
: game // ignore: cast_nullable_to_non_nullable
as Game,
createdAt: null == createdAt createdAt: null == createdAt
? _value.createdAt ? _value.createdAt
: createdAt // ignore: cast_nullable_to_non_nullable : createdAt // ignore: cast_nullable_to_non_nullable
@@ -534,12 +482,7 @@ class _$GamePrizeImpl implements _GamePrize {
required this.id, required this.id,
required this.gameId, required this.gameId,
required this.name, required this.name,
required this.weight,
required this.stock,
required this.maxStock,
required this.threshold,
required final Map<String, dynamic> metadata, required final Map<String, dynamic> metadata,
required this.game,
required this.createdAt, required this.createdAt,
required this.updatedAt, required this.updatedAt,
}) : _metadata = metadata; }) : _metadata = metadata;
@@ -550,14 +493,6 @@ class _$GamePrizeImpl implements _GamePrize {
final String gameId; final String gameId;
@override @override
final String name; final String name;
@override
final int weight;
@override
final int stock;
@override
final int maxStock;
@override
final int threshold;
final Map<String, dynamic> _metadata; final Map<String, dynamic> _metadata;
@override @override
Map<String, dynamic> get metadata { Map<String, dynamic> get metadata {
@@ -566,8 +501,6 @@ class _$GamePrizeImpl implements _GamePrize {
return EqualUnmodifiableMapView(_metadata); return EqualUnmodifiableMapView(_metadata);
} }
@override
final Game game;
@override @override
final String createdAt; final String createdAt;
@override @override
@@ -575,7 +508,7 @@ class _$GamePrizeImpl implements _GamePrize {
@override @override
String toString() { String toString() {
return 'GamePrize(id: $id, gameId: $gameId, name: $name, weight: $weight, stock: $stock, maxStock: $maxStock, threshold: $threshold, metadata: $metadata, game: $game, createdAt: $createdAt, updatedAt: $updatedAt)'; return 'GamePrize(id: $id, gameId: $gameId, name: $name, metadata: $metadata, createdAt: $createdAt, updatedAt: $updatedAt)';
} }
@override @override
@@ -586,14 +519,7 @@ class _$GamePrizeImpl implements _GamePrize {
(identical(other.id, id) || other.id == id) && (identical(other.id, id) || other.id == id) &&
(identical(other.gameId, gameId) || other.gameId == gameId) && (identical(other.gameId, gameId) || other.gameId == gameId) &&
(identical(other.name, name) || other.name == name) && (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) && const DeepCollectionEquality().equals(other._metadata, _metadata) &&
(identical(other.game, game) || other.game == game) &&
(identical(other.createdAt, createdAt) || (identical(other.createdAt, createdAt) ||
other.createdAt == createdAt) && other.createdAt == createdAt) &&
(identical(other.updatedAt, updatedAt) || (identical(other.updatedAt, updatedAt) ||
@@ -606,12 +532,7 @@ class _$GamePrizeImpl implements _GamePrize {
id, id,
gameId, gameId,
name, name,
weight,
stock,
maxStock,
threshold,
const DeepCollectionEquality().hash(_metadata), const DeepCollectionEquality().hash(_metadata),
game,
createdAt, createdAt,
updatedAt, updatedAt,
); );
@@ -630,12 +551,7 @@ abstract class _GamePrize implements GamePrize {
required final String id, required final String id,
required final String gameId, required final String gameId,
required final String name, required final String name,
required final int weight,
required final int stock,
required final int maxStock,
required final int threshold,
required final Map<String, dynamic> metadata, required final Map<String, dynamic> metadata,
required final Game game,
required final String createdAt, required final String createdAt,
required final String updatedAt, required final String updatedAt,
}) = _$GamePrizeImpl; }) = _$GamePrizeImpl;
@@ -647,18 +563,8 @@ abstract class _GamePrize implements GamePrize {
@override @override
String get name; String get name;
@override @override
int get weight;
@override
int get stock;
@override
int get maxStock;
@override
int get threshold;
@override
Map<String, dynamic> get metadata; Map<String, dynamic> get metadata;
@override @override
Game get game;
@override
String get createdAt; String get createdAt;
@override @override
String get updatedAt; String get updatedAt;
@@ -1,7 +1,5 @@
part of '../game.dart'; part of '../game.dart';
abstract class IGameRepository { abstract class IGameRepository {
Future<Either<GameFailure, List<GamePrize>>> gamePrizeByGameId({ Future<Either<GameFailure, Game>> ferrisWheel();
required String id,
});
} }
+3 -2
View File
@@ -9,12 +9,13 @@ abstract class Env {
@dev @dev
class DevEnv implements Env { class DevEnv implements Env {
@override @override
String get baseUrl => 'http://192.168.1.30:4000'; // example value // String get baseUrl => 'http://192.168.1.30:4000'; // example value
String get baseUrl => 'https://api-pos.apskel.id'; // example value
} }
@Injectable(as: Env) @Injectable(as: Env)
@prod @prod
class ProdEnv implements Env { class ProdEnv implements Env {
@override @override
String get baseUrl => 'https://enaklo-pos-be.altru.id'; String get baseUrl => 'https://api-pos.apskel.id';
} }
@@ -211,7 +211,9 @@ class AuthRemoteDataProvider {
if ((response.data['errors'] as List).isNotEmpty) { if ((response.data['errors'] as List).isNotEmpty) {
if (response.data['errors'][0]['code'] == "900") { if (response.data['errors'][0]['code'] == "900") {
return DC.error( return DC.error(
AuthFailure.dynamicErrorMessage('Kamu Belum Terdaftar'), AuthFailure.dynamicErrorMessage(
response.data['errors'][0]['cause'],
),
); );
} else { } else {
return DC.error( return DC.error(
@@ -17,12 +17,10 @@ class GameRemoteDataProvider {
GameRemoteDataProvider(this._apiClient); GameRemoteDataProvider(this._apiClient);
Future<DC<GameFailure, List<GamePrizeDto>>> gamePrizeByGameId({ Future<DC<GameFailure, GameDto>> ferrisWheel() async {
required String id,
}) async {
try { try {
final response = await _apiClient.get( final response = await _apiClient.get(
'${ApiPath.gamePrizeByGameId}/$id', ApiPath.ferrisWheel,
headers: getAuthorizationHeader(), headers: getAuthorizationHeader(),
); );
@@ -38,12 +36,11 @@ class GameRemoteDataProvider {
); );
} }
final dto = (response.data['data'] as List) final dto = GameDto.fromJson(response.data['data']['data']['game']);
.map((e) => GamePrizeDto.fromJson(e))
.toList();
return DC.data(dto); return DC.data(dto);
} on ApiFailure catch (e, s) { } on ApiFailure catch (e, s) {
log('gamePrizeByGameId', name: _logName, error: e, stackTrace: s); log('ferrisWheel', name: _logName, error: e, stackTrace: s);
return DC.error(GameFailure.serverError(e)); return DC.error(GameFailure.serverError(e));
} }
} }
+3 -1
View File
@@ -8,6 +8,7 @@ class GameDto with _$GameDto {
@JsonKey(name: 'type') String? type, @JsonKey(name: 'type') String? type,
@JsonKey(name: 'is_active') bool? isActive, @JsonKey(name: 'is_active') bool? isActive,
@JsonKey(name: 'metadata') Map<String, dynamic>? metadata, @JsonKey(name: 'metadata') Map<String, dynamic>? metadata,
@JsonKey(name: 'prizes') List<GamePrizeDto>? prizes,
@JsonKey(name: 'created_at') String? createdAt, @JsonKey(name: 'created_at') String? createdAt,
@JsonKey(name: 'updated_at') String? updatedAt, @JsonKey(name: 'updated_at') String? updatedAt,
}) = _GameDto; }) = _GameDto;
@@ -22,7 +23,8 @@ class GameDto with _$GameDto {
name: name ?? '', name: name ?? '',
type: type ?? '', type: type ?? '',
isActive: isActive ?? false, isActive: isActive ?? false,
metadata: metadata ?? const {}, metadata: metadata ?? {},
prizes: prizes?.map((e) => e.toDomain()).toList() ?? [],
createdAt: createdAt ?? '', createdAt: createdAt ?? '',
updatedAt: updatedAt ?? '', updatedAt: updatedAt ?? '',
); );
@@ -6,12 +6,7 @@ class GamePrizeDto with _$GamePrizeDto {
@JsonKey(name: 'id') String? id, @JsonKey(name: 'id') String? id,
@JsonKey(name: 'game_id') String? gameId, @JsonKey(name: 'game_id') String? gameId,
@JsonKey(name: 'name') String? name, @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<String, dynamic>? metadata, @JsonKey(name: 'metadata') Map<String, dynamic>? metadata,
@JsonKey(name: 'game') GameDto? game,
@JsonKey(name: 'created_at') String? createdAt, @JsonKey(name: 'created_at') String? createdAt,
@JsonKey(name: 'updated_at') String? updatedAt, @JsonKey(name: 'updated_at') String? updatedAt,
}) = _GamePrizeDto; }) = _GamePrizeDto;
@@ -21,17 +16,11 @@ class GamePrizeDto with _$GamePrizeDto {
const GamePrizeDto._(); const GamePrizeDto._();
/// mapping ke domain
GamePrize toDomain() => GamePrize( GamePrize toDomain() => GamePrize(
id: id ?? '', id: id ?? '',
gameId: gameId ?? '', gameId: gameId ?? '',
name: name ?? '', name: name ?? '',
weight: weight ?? 0, metadata: metadata ?? {},
stock: stock ?? 0,
maxStock: maxStock ?? 0,
threshold: threshold ?? 0,
metadata: metadata ?? const {},
game: game?.toDomain() ?? Game.empty(),
createdAt: createdAt ?? '', createdAt: createdAt ?? '',
updatedAt: updatedAt ?? '', updatedAt: updatedAt ?? '',
); );
+35 -143
View File
@@ -31,6 +31,8 @@ mixin _$GameDto {
bool? get isActive => throw _privateConstructorUsedError; bool? get isActive => throw _privateConstructorUsedError;
@JsonKey(name: 'metadata') @JsonKey(name: 'metadata')
Map<String, dynamic>? get metadata => throw _privateConstructorUsedError; Map<String, dynamic>? get metadata => throw _privateConstructorUsedError;
@JsonKey(name: 'prizes')
List<GamePrizeDto>? get prizes => throw _privateConstructorUsedError;
@JsonKey(name: 'created_at') @JsonKey(name: 'created_at')
String? get createdAt => throw _privateConstructorUsedError; String? get createdAt => throw _privateConstructorUsedError;
@JsonKey(name: 'updated_at') @JsonKey(name: 'updated_at')
@@ -56,6 +58,7 @@ abstract class $GameDtoCopyWith<$Res> {
@JsonKey(name: 'type') String? type, @JsonKey(name: 'type') String? type,
@JsonKey(name: 'is_active') bool? isActive, @JsonKey(name: 'is_active') bool? isActive,
@JsonKey(name: 'metadata') Map<String, dynamic>? metadata, @JsonKey(name: 'metadata') Map<String, dynamic>? metadata,
@JsonKey(name: 'prizes') List<GamePrizeDto>? prizes,
@JsonKey(name: 'created_at') String? createdAt, @JsonKey(name: 'created_at') String? createdAt,
@JsonKey(name: 'updated_at') String? updatedAt, @JsonKey(name: 'updated_at') String? updatedAt,
}); });
@@ -81,6 +84,7 @@ class _$GameDtoCopyWithImpl<$Res, $Val extends GameDto>
Object? type = freezed, Object? type = freezed,
Object? isActive = freezed, Object? isActive = freezed,
Object? metadata = freezed, Object? metadata = freezed,
Object? prizes = freezed,
Object? createdAt = freezed, Object? createdAt = freezed,
Object? updatedAt = freezed, Object? updatedAt = freezed,
}) { }) {
@@ -106,6 +110,10 @@ class _$GameDtoCopyWithImpl<$Res, $Val extends GameDto>
? _value.metadata ? _value.metadata
: metadata // ignore: cast_nullable_to_non_nullable : metadata // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>?, as Map<String, dynamic>?,
prizes: freezed == prizes
? _value.prizes
: prizes // ignore: cast_nullable_to_non_nullable
as List<GamePrizeDto>?,
createdAt: freezed == createdAt createdAt: freezed == createdAt
? _value.createdAt ? _value.createdAt
: createdAt // ignore: cast_nullable_to_non_nullable : createdAt // ignore: cast_nullable_to_non_nullable
@@ -134,6 +142,7 @@ abstract class _$$GameDtoImplCopyWith<$Res> implements $GameDtoCopyWith<$Res> {
@JsonKey(name: 'type') String? type, @JsonKey(name: 'type') String? type,
@JsonKey(name: 'is_active') bool? isActive, @JsonKey(name: 'is_active') bool? isActive,
@JsonKey(name: 'metadata') Map<String, dynamic>? metadata, @JsonKey(name: 'metadata') Map<String, dynamic>? metadata,
@JsonKey(name: 'prizes') List<GamePrizeDto>? prizes,
@JsonKey(name: 'created_at') String? createdAt, @JsonKey(name: 'created_at') String? createdAt,
@JsonKey(name: 'updated_at') String? updatedAt, @JsonKey(name: 'updated_at') String? updatedAt,
}); });
@@ -158,6 +167,7 @@ class __$$GameDtoImplCopyWithImpl<$Res>
Object? type = freezed, Object? type = freezed,
Object? isActive = freezed, Object? isActive = freezed,
Object? metadata = freezed, Object? metadata = freezed,
Object? prizes = freezed,
Object? createdAt = freezed, Object? createdAt = freezed,
Object? updatedAt = freezed, Object? updatedAt = freezed,
}) { }) {
@@ -183,6 +193,10 @@ class __$$GameDtoImplCopyWithImpl<$Res>
? _value._metadata ? _value._metadata
: metadata // ignore: cast_nullable_to_non_nullable : metadata // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>?, as Map<String, dynamic>?,
prizes: freezed == prizes
? _value._prizes
: prizes // ignore: cast_nullable_to_non_nullable
as List<GamePrizeDto>?,
createdAt: freezed == createdAt createdAt: freezed == createdAt
? _value.createdAt ? _value.createdAt
: createdAt // ignore: cast_nullable_to_non_nullable : createdAt // ignore: cast_nullable_to_non_nullable
@@ -205,9 +219,11 @@ class _$GameDtoImpl extends _GameDto {
@JsonKey(name: 'type') this.type, @JsonKey(name: 'type') this.type,
@JsonKey(name: 'is_active') this.isActive, @JsonKey(name: 'is_active') this.isActive,
@JsonKey(name: 'metadata') final Map<String, dynamic>? metadata, @JsonKey(name: 'metadata') final Map<String, dynamic>? metadata,
@JsonKey(name: 'prizes') final List<GamePrizeDto>? prizes,
@JsonKey(name: 'created_at') this.createdAt, @JsonKey(name: 'created_at') this.createdAt,
@JsonKey(name: 'updated_at') this.updatedAt, @JsonKey(name: 'updated_at') this.updatedAt,
}) : _metadata = metadata, }) : _metadata = metadata,
_prizes = prizes,
super._(); super._();
factory _$GameDtoImpl.fromJson(Map<String, dynamic> json) => factory _$GameDtoImpl.fromJson(Map<String, dynamic> json) =>
@@ -236,6 +252,17 @@ class _$GameDtoImpl extends _GameDto {
return EqualUnmodifiableMapView(value); return EqualUnmodifiableMapView(value);
} }
final List<GamePrizeDto>? _prizes;
@override
@JsonKey(name: 'prizes')
List<GamePrizeDto>? get prizes {
final value = _prizes;
if (value == null) return null;
if (_prizes is EqualUnmodifiableListView) return _prizes;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(value);
}
@override @override
@JsonKey(name: 'created_at') @JsonKey(name: 'created_at')
final String? createdAt; final String? createdAt;
@@ -245,7 +272,7 @@ class _$GameDtoImpl extends _GameDto {
@override @override
String toString() { String toString() {
return 'GameDto(id: $id, name: $name, type: $type, isActive: $isActive, metadata: $metadata, createdAt: $createdAt, updatedAt: $updatedAt)'; return 'GameDto(id: $id, name: $name, type: $type, isActive: $isActive, metadata: $metadata, prizes: $prizes, createdAt: $createdAt, updatedAt: $updatedAt)';
} }
@override @override
@@ -259,6 +286,7 @@ class _$GameDtoImpl extends _GameDto {
(identical(other.isActive, isActive) || (identical(other.isActive, isActive) ||
other.isActive == isActive) && other.isActive == isActive) &&
const DeepCollectionEquality().equals(other._metadata, _metadata) && const DeepCollectionEquality().equals(other._metadata, _metadata) &&
const DeepCollectionEquality().equals(other._prizes, _prizes) &&
(identical(other.createdAt, createdAt) || (identical(other.createdAt, createdAt) ||
other.createdAt == createdAt) && other.createdAt == createdAt) &&
(identical(other.updatedAt, updatedAt) || (identical(other.updatedAt, updatedAt) ||
@@ -274,6 +302,7 @@ class _$GameDtoImpl extends _GameDto {
type, type,
isActive, isActive,
const DeepCollectionEquality().hash(_metadata), const DeepCollectionEquality().hash(_metadata),
const DeepCollectionEquality().hash(_prizes),
createdAt, createdAt,
updatedAt, updatedAt,
); );
@@ -299,6 +328,7 @@ abstract class _GameDto extends GameDto {
@JsonKey(name: 'type') final String? type, @JsonKey(name: 'type') final String? type,
@JsonKey(name: 'is_active') final bool? isActive, @JsonKey(name: 'is_active') final bool? isActive,
@JsonKey(name: 'metadata') final Map<String, dynamic>? metadata, @JsonKey(name: 'metadata') final Map<String, dynamic>? metadata,
@JsonKey(name: 'prizes') final List<GamePrizeDto>? prizes,
@JsonKey(name: 'created_at') final String? createdAt, @JsonKey(name: 'created_at') final String? createdAt,
@JsonKey(name: 'updated_at') final String? updatedAt, @JsonKey(name: 'updated_at') final String? updatedAt,
}) = _$GameDtoImpl; }) = _$GameDtoImpl;
@@ -322,6 +352,9 @@ abstract class _GameDto extends GameDto {
@JsonKey(name: 'metadata') @JsonKey(name: 'metadata')
Map<String, dynamic>? get metadata; Map<String, dynamic>? get metadata;
@override @override
@JsonKey(name: 'prizes')
List<GamePrizeDto>? get prizes;
@override
@JsonKey(name: 'created_at') @JsonKey(name: 'created_at')
String? get createdAt; String? get createdAt;
@override @override
@@ -348,18 +381,8 @@ mixin _$GamePrizeDto {
String? get gameId => throw _privateConstructorUsedError; String? get gameId => throw _privateConstructorUsedError;
@JsonKey(name: 'name') @JsonKey(name: 'name')
String? get name => throw _privateConstructorUsedError; 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') @JsonKey(name: 'metadata')
Map<String, dynamic>? get metadata => throw _privateConstructorUsedError; Map<String, dynamic>? get metadata => throw _privateConstructorUsedError;
@JsonKey(name: 'game')
GameDto? get game => throw _privateConstructorUsedError;
@JsonKey(name: 'created_at') @JsonKey(name: 'created_at')
String? get createdAt => throw _privateConstructorUsedError; String? get createdAt => throw _privateConstructorUsedError;
@JsonKey(name: 'updated_at') @JsonKey(name: 'updated_at')
@@ -386,17 +409,10 @@ abstract class $GamePrizeDtoCopyWith<$Res> {
@JsonKey(name: 'id') String? id, @JsonKey(name: 'id') String? id,
@JsonKey(name: 'game_id') String? gameId, @JsonKey(name: 'game_id') String? gameId,
@JsonKey(name: 'name') String? name, @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<String, dynamic>? metadata, @JsonKey(name: 'metadata') Map<String, dynamic>? metadata,
@JsonKey(name: 'game') GameDto? game,
@JsonKey(name: 'created_at') String? createdAt, @JsonKey(name: 'created_at') String? createdAt,
@JsonKey(name: 'updated_at') String? updatedAt, @JsonKey(name: 'updated_at') String? updatedAt,
}); });
$GameDtoCopyWith<$Res>? get game;
} }
/// @nodoc /// @nodoc
@@ -417,12 +433,7 @@ class _$GamePrizeDtoCopyWithImpl<$Res, $Val extends GamePrizeDto>
Object? id = freezed, Object? id = freezed,
Object? gameId = freezed, Object? gameId = freezed,
Object? name = freezed, Object? name = freezed,
Object? weight = freezed,
Object? stock = freezed,
Object? maxStock = freezed,
Object? threshold = freezed,
Object? metadata = freezed, Object? metadata = freezed,
Object? game = freezed,
Object? createdAt = freezed, Object? createdAt = freezed,
Object? updatedAt = freezed, Object? updatedAt = freezed,
}) { }) {
@@ -440,30 +451,10 @@ class _$GamePrizeDtoCopyWithImpl<$Res, $Val extends GamePrizeDto>
? _value.name ? _value.name
: name // ignore: cast_nullable_to_non_nullable : name // ignore: cast_nullable_to_non_nullable
as String?, 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 metadata: freezed == metadata
? _value.metadata ? _value.metadata
: metadata // ignore: cast_nullable_to_non_nullable : metadata // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>?, as Map<String, dynamic>?,
game: freezed == game
? _value.game
: game // ignore: cast_nullable_to_non_nullable
as GameDto?,
createdAt: freezed == createdAt createdAt: freezed == createdAt
? _value.createdAt ? _value.createdAt
: createdAt // ignore: cast_nullable_to_non_nullable : createdAt // ignore: cast_nullable_to_non_nullable
@@ -476,20 +467,6 @@ class _$GamePrizeDtoCopyWithImpl<$Res, $Val extends GamePrizeDto>
as $Val, 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 /// @nodoc
@@ -505,18 +482,10 @@ abstract class _$$GamePrizeDtoImplCopyWith<$Res>
@JsonKey(name: 'id') String? id, @JsonKey(name: 'id') String? id,
@JsonKey(name: 'game_id') String? gameId, @JsonKey(name: 'game_id') String? gameId,
@JsonKey(name: 'name') String? name, @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<String, dynamic>? metadata, @JsonKey(name: 'metadata') Map<String, dynamic>? metadata,
@JsonKey(name: 'game') GameDto? game,
@JsonKey(name: 'created_at') String? createdAt, @JsonKey(name: 'created_at') String? createdAt,
@JsonKey(name: 'updated_at') String? updatedAt, @JsonKey(name: 'updated_at') String? updatedAt,
}); });
@override
$GameDtoCopyWith<$Res>? get game;
} }
/// @nodoc /// @nodoc
@@ -536,12 +505,7 @@ class __$$GamePrizeDtoImplCopyWithImpl<$Res>
Object? id = freezed, Object? id = freezed,
Object? gameId = freezed, Object? gameId = freezed,
Object? name = freezed, Object? name = freezed,
Object? weight = freezed,
Object? stock = freezed,
Object? maxStock = freezed,
Object? threshold = freezed,
Object? metadata = freezed, Object? metadata = freezed,
Object? game = freezed,
Object? createdAt = freezed, Object? createdAt = freezed,
Object? updatedAt = freezed, Object? updatedAt = freezed,
}) { }) {
@@ -559,30 +523,10 @@ class __$$GamePrizeDtoImplCopyWithImpl<$Res>
? _value.name ? _value.name
: name // ignore: cast_nullable_to_non_nullable : name // ignore: cast_nullable_to_non_nullable
as String?, 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 metadata: freezed == metadata
? _value._metadata ? _value._metadata
: metadata // ignore: cast_nullable_to_non_nullable : metadata // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>?, as Map<String, dynamic>?,
game: freezed == game
? _value.game
: game // ignore: cast_nullable_to_non_nullable
as GameDto?,
createdAt: freezed == createdAt createdAt: freezed == createdAt
? _value.createdAt ? _value.createdAt
: createdAt // ignore: cast_nullable_to_non_nullable : createdAt // ignore: cast_nullable_to_non_nullable
@@ -603,12 +547,7 @@ class _$GamePrizeDtoImpl extends _GamePrizeDto {
@JsonKey(name: 'id') this.id, @JsonKey(name: 'id') this.id,
@JsonKey(name: 'game_id') this.gameId, @JsonKey(name: 'game_id') this.gameId,
@JsonKey(name: 'name') this.name, @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<String, dynamic>? metadata, @JsonKey(name: 'metadata') final Map<String, dynamic>? metadata,
@JsonKey(name: 'game') this.game,
@JsonKey(name: 'created_at') this.createdAt, @JsonKey(name: 'created_at') this.createdAt,
@JsonKey(name: 'updated_at') this.updatedAt, @JsonKey(name: 'updated_at') this.updatedAt,
}) : _metadata = metadata, }) : _metadata = metadata,
@@ -626,18 +565,6 @@ class _$GamePrizeDtoImpl extends _GamePrizeDto {
@override @override
@JsonKey(name: 'name') @JsonKey(name: 'name')
final String? 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<String, dynamic>? _metadata; final Map<String, dynamic>? _metadata;
@override @override
@JsonKey(name: 'metadata') @JsonKey(name: 'metadata')
@@ -649,9 +576,6 @@ class _$GamePrizeDtoImpl extends _GamePrizeDto {
return EqualUnmodifiableMapView(value); return EqualUnmodifiableMapView(value);
} }
@override
@JsonKey(name: 'game')
final GameDto? game;
@override @override
@JsonKey(name: 'created_at') @JsonKey(name: 'created_at')
final String? createdAt; final String? createdAt;
@@ -661,7 +585,7 @@ class _$GamePrizeDtoImpl extends _GamePrizeDto {
@override @override
String toString() { String toString() {
return 'GamePrizeDto(id: $id, gameId: $gameId, name: $name, weight: $weight, stock: $stock, maxStock: $maxStock, threshold: $threshold, metadata: $metadata, game: $game, createdAt: $createdAt, updatedAt: $updatedAt)'; return 'GamePrizeDto(id: $id, gameId: $gameId, name: $name, metadata: $metadata, createdAt: $createdAt, updatedAt: $updatedAt)';
} }
@override @override
@@ -672,14 +596,7 @@ class _$GamePrizeDtoImpl extends _GamePrizeDto {
(identical(other.id, id) || other.id == id) && (identical(other.id, id) || other.id == id) &&
(identical(other.gameId, gameId) || other.gameId == gameId) && (identical(other.gameId, gameId) || other.gameId == gameId) &&
(identical(other.name, name) || other.name == name) && (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) && const DeepCollectionEquality().equals(other._metadata, _metadata) &&
(identical(other.game, game) || other.game == game) &&
(identical(other.createdAt, createdAt) || (identical(other.createdAt, createdAt) ||
other.createdAt == createdAt) && other.createdAt == createdAt) &&
(identical(other.updatedAt, updatedAt) || (identical(other.updatedAt, updatedAt) ||
@@ -693,12 +610,7 @@ class _$GamePrizeDtoImpl extends _GamePrizeDto {
id, id,
gameId, gameId,
name, name,
weight,
stock,
maxStock,
threshold,
const DeepCollectionEquality().hash(_metadata), const DeepCollectionEquality().hash(_metadata),
game,
createdAt, createdAt,
updatedAt, updatedAt,
); );
@@ -722,12 +634,7 @@ abstract class _GamePrizeDto extends GamePrizeDto {
@JsonKey(name: 'id') final String? id, @JsonKey(name: 'id') final String? id,
@JsonKey(name: 'game_id') final String? gameId, @JsonKey(name: 'game_id') final String? gameId,
@JsonKey(name: 'name') final String? name, @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<String, dynamic>? metadata, @JsonKey(name: 'metadata') final Map<String, dynamic>? metadata,
@JsonKey(name: 'game') final GameDto? game,
@JsonKey(name: 'created_at') final String? createdAt, @JsonKey(name: 'created_at') final String? createdAt,
@JsonKey(name: 'updated_at') final String? updatedAt, @JsonKey(name: 'updated_at') final String? updatedAt,
}) = _$GamePrizeDtoImpl; }) = _$GamePrizeDtoImpl;
@@ -746,24 +653,9 @@ abstract class _GamePrizeDto extends GamePrizeDto {
@JsonKey(name: 'name') @JsonKey(name: 'name')
String? get name; String? get name;
@override @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') @JsonKey(name: 'metadata')
Map<String, dynamic>? get metadata; Map<String, dynamic>? get metadata;
@override @override
@JsonKey(name: 'game')
GameDto? get game;
@override
@JsonKey(name: 'created_at') @JsonKey(name: 'created_at')
String? get createdAt; String? get createdAt;
@override @override
+4 -12
View File
@@ -13,6 +13,9 @@ _$GameDtoImpl _$$GameDtoImplFromJson(Map<String, dynamic> json) =>
type: json['type'] as String?, type: json['type'] as String?,
isActive: json['is_active'] as bool?, isActive: json['is_active'] as bool?,
metadata: json['metadata'] as Map<String, dynamic>?, metadata: json['metadata'] as Map<String, dynamic>?,
prizes: (json['prizes'] as List<dynamic>?)
?.map((e) => GamePrizeDto.fromJson(e as Map<String, dynamic>))
.toList(),
createdAt: json['created_at'] as String?, createdAt: json['created_at'] as String?,
updatedAt: json['updated_at'] as String?, updatedAt: json['updated_at'] as String?,
); );
@@ -24,6 +27,7 @@ Map<String, dynamic> _$$GameDtoImplToJson(_$GameDtoImpl instance) =>
'type': instance.type, 'type': instance.type,
'is_active': instance.isActive, 'is_active': instance.isActive,
'metadata': instance.metadata, 'metadata': instance.metadata,
'prizes': instance.prizes,
'created_at': instance.createdAt, 'created_at': instance.createdAt,
'updated_at': instance.updatedAt, 'updated_at': instance.updatedAt,
}; };
@@ -33,14 +37,7 @@ _$GamePrizeDtoImpl _$$GamePrizeDtoImplFromJson(Map<String, dynamic> json) =>
id: json['id'] as String?, id: json['id'] as String?,
gameId: json['game_id'] as String?, gameId: json['game_id'] as String?,
name: json['name'] 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<String, dynamic>?, metadata: json['metadata'] as Map<String, dynamic>?,
game: json['game'] == null
? null
: GameDto.fromJson(json['game'] as Map<String, dynamic>),
createdAt: json['created_at'] as String?, createdAt: json['created_at'] as String?,
updatedAt: json['updated_at'] as String?, updatedAt: json['updated_at'] as String?,
); );
@@ -50,12 +47,7 @@ Map<String, dynamic> _$$GamePrizeDtoImplToJson(_$GamePrizeDtoImpl instance) =>
'id': instance.id, 'id': instance.id,
'game_id': instance.gameId, 'game_id': instance.gameId,
'name': instance.name, 'name': instance.name,
'weight': instance.weight,
'stock': instance.stock,
'max_stock': instance.maxStock,
'threshold': instance.threshold,
'metadata': instance.metadata, 'metadata': instance.metadata,
'game': instance.game,
'created_at': instance.createdAt, 'created_at': instance.createdAt,
'updated_at': instance.updatedAt, 'updated_at': instance.updatedAt,
}; };
@@ -15,21 +15,19 @@ class GameRepository implements IGameRepository {
GameRepository(this._remoteDataProvider); GameRepository(this._remoteDataProvider);
@override @override
Future<Either<GameFailure, List<GamePrize>>> gamePrizeByGameId({ Future<Either<GameFailure, Game>> ferrisWheel() async {
required String id,
}) async {
try { try {
final result = await _remoteDataProvider.gamePrizeByGameId(id: id); final result = await _remoteDataProvider.ferrisWheel();
if (result.hasError) { if (result.hasError) {
return left(result.error!); return left(result.error!);
} }
final data = result.data!.map((e) => e.toDomain()).toList(); final data = result.data!.toDomain();
return right(data); return right(data);
} catch (e, s) { } catch (e, s) {
log('gamePrizeByGameId', name: _logName, error: e, stackTrace: s); log('ferrisWheel', name: _logName, error: e, stackTrace: s);
return left(const GameFailure.unexpectedError()); return left(const GameFailure.unexpectedError());
} }
} }
+7 -7
View File
@@ -28,8 +28,8 @@ import 'package:enaklo/application/auth/verify_form/verify_form_bloc.dart'
as _i521; as _i521;
import 'package:enaklo/application/customer/customer_point_loader/customer_point_loader_bloc.dart' import 'package:enaklo/application/customer/customer_point_loader/customer_point_loader_bloc.dart'
as _i497; as _i497;
import 'package:enaklo/application/game/game_price_loader/game_prize_loader_bloc.dart' import 'package:enaklo/application/game/ferris_wheel_loader/ferris_wheel_loader_bloc.dart'
as _i925; as _i1013;
import 'package:enaklo/common/api/api_client.dart' as _i842; 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_auto_route.dart' as _i619;
import 'package:enaklo/common/di/di_connectivity.dart' as _i644; import 'package:enaklo/common/di/di_connectivity.dart' as _i644;
@@ -103,8 +103,8 @@ extension GetItInjectableX on _i174.GetIt {
gh.factory<_i96.IGameRepository>( gh.factory<_i96.IGameRepository>(
() => _i547.GameRepository(gh<_i143.GameRemoteDataProvider>()), () => _i547.GameRepository(gh<_i143.GameRemoteDataProvider>()),
); );
gh.factory<_i925.GamePrizeLoaderBloc>( gh.factory<_i1013.FerrisWheelLoaderBloc>(
() => _i925.GamePrizeLoaderBloc(gh<_i96.IGameRepository>()), () => _i1013.FerrisWheelLoaderBloc(gh<_i96.IGameRepository>()),
); );
gh.factory<_i995.IAuthRepository>( gh.factory<_i995.IAuthRepository>(
() => _i879.AuthRepository( () => _i879.AuthRepository(
@@ -124,12 +124,12 @@ extension GetItInjectableX on _i174.GetIt {
gh.factory<_i869.CheckPhoneFormBloc>( gh.factory<_i869.CheckPhoneFormBloc>(
() => _i869.CheckPhoneFormBloc(gh<_i995.IAuthRepository>()), () => _i869.CheckPhoneFormBloc(gh<_i995.IAuthRepository>()),
); );
gh.factory<_i521.VerifyFormBloc>(
() => _i521.VerifyFormBloc(gh<_i995.IAuthRepository>()),
);
gh.factory<_i771.AuthBloc>( gh.factory<_i771.AuthBloc>(
() => _i771.AuthBloc(gh<_i995.IAuthRepository>()), () => _i771.AuthBloc(gh<_i995.IAuthRepository>()),
); );
gh.factory<_i521.VerifyFormBloc>(
() => _i521.VerifyFormBloc(gh<_i995.IAuthRepository>()),
);
gh.factory<_i216.LogoutFormBloc>( gh.factory<_i216.LogoutFormBloc>(
() => _i216.LogoutFormBloc(gh<_i995.IAuthRepository>()), () => _i216.LogoutFormBloc(gh<_i995.IAuthRepository>()),
); );
@@ -11,6 +11,52 @@
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
class $AssetsAudioGen {
const $AssetsAudioGen();
/// File path: assets/audio/bell_ding.mp3
String get bellDing => 'assets/audio/bell_ding.mp3';
/// File path: assets/audio/big_win.mp3
String get bigWin => 'assets/audio/big_win.mp3';
/// File path: assets/audio/button_tap.mp3
String get buttonTap => 'assets/audio/button_tap.mp3';
/// File path: assets/audio/carnaval_main_theme.mp3
String get carnavalMainTheme => 'assets/audio/carnaval_main_theme.mp3';
/// File path: assets/audio/token_sound.mp3
String get tokenSound => 'assets/audio/token_sound.mp3';
/// File path: assets/audio/wheel_spin.mp3
String get wheelSpin => 'assets/audio/wheel_spin.mp3';
/// List of all assets
List<String> get values => [
bellDing,
bigWin,
buttonTap,
carnavalMainTheme,
tokenSound,
wheelSpin,
];
}
class $AssetsIconsGen {
const $AssetsIconsGen();
/// File path: assets/icons/dine_in.png
AssetGenImage get dineIn => const AssetGenImage('assets/icons/dine_in.png');
/// File path: assets/icons/takeaway.png
AssetGenImage get takeaway =>
const AssetGenImage('assets/icons/takeaway.png');
/// List of all assets
List<AssetGenImage> get values => [dineIn, takeaway];
}
class $AssetsImagesGen { class $AssetsImagesGen {
const $AssetsImagesGen(); const $AssetsImagesGen();
@@ -64,6 +110,8 @@ class $AssetsImagesGen {
class Assets { class Assets {
const Assets._(); const Assets._();
static const $AssetsAudioGen audio = $AssetsAudioGen();
static const $AssetsIconsGen icons = $AssetsIconsGen();
static const $AssetsImagesGen images = $AssetsImagesGen(); static const $AssetsImagesGen images = $AssetsImagesGen();
} }
@@ -0,0 +1,39 @@
import 'package:flutter/material.dart';
class DashedDivider extends StatelessWidget {
final double height;
final double dashWidth;
final double dashSpacing;
final Color color;
const DashedDivider({
super.key,
this.height = 1,
this.dashWidth = 5,
this.dashSpacing = 3,
this.color = Colors.grey,
});
@override
Widget build(BuildContext context) {
return SizedBox(
height: height,
child: LayoutBuilder(
builder: (context, constraints) {
final boxWidth = constraints.constrainWidth();
final dashCount = (boxWidth / (dashWidth + dashSpacing)).floor();
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: List.generate(dashCount, (_) {
return SizedBox(
width: dashWidth,
height: height,
child: DecoratedBox(decoration: BoxDecoration(color: color)),
);
}),
);
},
),
);
}
}
@@ -4,3 +4,5 @@ import 'package:flutter_spinkit/flutter_spinkit.dart';
import '../../../common/theme/theme.dart'; import '../../../common/theme/theme.dart';
part 'elevated_button.dart'; part 'elevated_button.dart';
part 'outline_button.dart';
part 'qty_button.dart';
@@ -6,7 +6,7 @@ class AppElevatedButton extends StatelessWidget {
required this.onPressed, required this.onPressed,
required this.title, required this.title,
this.width = double.infinity, this.width = double.infinity,
this.height = 48.0, this.height = 44.0,
this.isLoading = false, this.isLoading = false,
}); });
@@ -41,7 +41,7 @@ class AppElevatedButton extends StatelessWidget {
) )
: Text( : Text(
title, title,
style: AppStyle.lg.copyWith( style: AppStyle.md.copyWith(
color: AppColor.white, color: AppColor.white,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
), ),
@@ -0,0 +1,58 @@
part of 'button.dart';
class AppOutlineButton extends StatelessWidget {
const AppOutlineButton({
super.key,
required this.onPressed,
required this.title,
this.width = double.infinity,
this.height = 44.0,
this.isLoading = false,
this.borderColor,
});
final Function()? onPressed;
final String title;
final double width;
final double height;
final bool isLoading;
final Color? borderColor;
@override
Widget build(BuildContext context) {
return SizedBox(
width: width,
height: height,
child: OutlinedButton(
onPressed: onPressed,
style: OutlinedButton.styleFrom(
padding: EdgeInsets.zero,
side: BorderSide(color: borderColor ?? AppColor.primary),
),
child: isLoading
? Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SpinKitFadingCircle(color: AppColor.white, size: 24),
SizedBox(width: 8),
Text(
'Loading',
style: AppStyle.lg.copyWith(
color: AppColor.white,
fontWeight: FontWeight.w700,
),
),
],
)
: Text(
title,
style: AppStyle.md.copyWith(
color: AppColor.primary,
fontWeight: FontWeight.w700,
),
),
),
);
}
}
@@ -0,0 +1,38 @@
part of 'button.dart';
class QtyButton extends StatelessWidget {
const QtyButton({super.key});
@override
Widget build(BuildContext context) {
return Row(
children: [
InkWell(
child: Container(
height: 35,
width: 35,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
border: Border.all(width: 1, color: AppColor.border),
),
child: Icon(Icons.remove),
),
),
SizedBox(width: 12),
Text('1', style: AppStyle.lg.copyWith(fontWeight: FontWeight.bold)),
SizedBox(width: 12),
InkWell(
child: Container(
height: 35,
width: 35,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
border: Border.all(width: 1, color: AppColor.border),
),
child: Icon(Icons.add),
),
),
],
);
}
}
@@ -0,0 +1,118 @@
import 'package:flutter/material.dart';
import '../../../common/theme/theme.dart';
class GradientCard extends StatelessWidget {
final Widget child;
final List<Color>? gradientColors;
final double borderRadius;
final EdgeInsetsGeometry? padding;
final bool showDecoration;
const GradientCard({
super.key,
required this.child,
this.gradientColors,
this.borderRadius = 16,
this.padding = const EdgeInsets.all(16.0),
this.showDecoration = true,
});
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: gradientColors ?? AppColor.primaryGradient,
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(borderRadius),
),
child: Stack(
children: [
// Background Pattern (optional)
if (showDecoration) ..._buildDecorations(),
// Main Content
Padding(padding: padding ?? EdgeInsets.zero, child: child),
],
),
);
}
List<Widget> _buildDecorations() {
return [
// Top Right Circle
Positioned(
top: -20,
right: -20,
child: Container(
width: 80,
height: 80,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white.withOpacity(0.1),
),
),
),
// Middle Right Circle
Positioned(
top: 30,
right: 20,
child: Container(
width: 40,
height: 40,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white.withOpacity(0.08),
),
),
),
// Bottom Left Circle
Positioned(
bottom: -10,
left: -10,
child: Container(
width: 60,
height: 60,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white.withOpacity(0.06),
),
),
),
// Top Left Decorative Line
Positioned(
top: 10,
left: -5,
child: Transform.rotate(
angle: 0.5,
child: Container(
width: 30,
height: 2,
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.15),
borderRadius: BorderRadius.circular(1),
),
),
),
),
// Bottom Right Decorative Line
Positioned(
bottom: 15,
right: 10,
child: Transform.rotate(
angle: -0.5,
child: Container(
width: 25,
height: 2,
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.15),
borderRadius: BorderRadius.circular(1),
),
),
),
),
];
}
}
@@ -0,0 +1,153 @@
import 'package:flutter/material.dart';
import '../../../common/extension/extension.dart';
import '../../../common/theme/theme.dart';
import '../../../sample/product_sample_data.dart';
class ProductCard extends StatelessWidget {
final Product product;
final Function()? onTap;
const ProductCard({super.key, required this.product, this.onTap});
@override
Widget build(BuildContext context) {
return InkWell(
onTap: onTap,
child: Container(
decoration: BoxDecoration(
color: AppColor.surface,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: AppColor.black.withOpacity(0.06),
blurRadius: 8,
offset: Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Product image
Expanded(
flex: 3,
child: Stack(
children: [
Container(
width: double.infinity,
decoration: BoxDecoration(
color: AppColor.backgroundLight,
borderRadius: BorderRadius.vertical(
top: Radius.circular(12),
),
),
child: Center(
child: Icon(
Icons.fastfood,
size: 40,
color: AppColor.textLight,
),
),
),
// Availability overlay
if (!product.isAvailable)
Container(
width: double.infinity,
height: double.infinity,
decoration: BoxDecoration(
color: AppColor.black.withOpacity(0.6),
borderRadius: BorderRadius.vertical(
top: Radius.circular(12),
),
),
child: Center(
child: Text(
"HABIS",
style: AppStyle.sm.copyWith(
color: AppColor.textWhite,
fontWeight: FontWeight.bold,
),
),
),
),
// Rating badge
Positioned(
top: 8,
right: 8,
child: Container(
padding: EdgeInsets.symmetric(horizontal: 6, vertical: 3),
decoration: BoxDecoration(
color: AppColor.surface,
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.star, size: 12, color: AppColor.warning),
SizedBox(width: 2),
Text(
"${product.rating}",
style: AppStyle.xs.copyWith(
color: AppColor.textPrimary,
fontWeight: FontWeight.w600,
),
),
],
),
),
),
],
),
),
// Product info
Expanded(
flex: 2,
child: Padding(
padding: EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
product.name,
style: AppStyle.md.copyWith(
fontWeight: FontWeight.bold,
color: AppColor.textPrimary,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
Spacer(),
// Price and sold count
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Rp ${product.price.currencyFormatRp}",
style: AppStyle.md.copyWith(
fontWeight: FontWeight.bold,
color: AppColor.primary,
),
),
Text(
"${product.soldCount} terjual",
style: AppStyle.xs.copyWith(
color: AppColor.textSecondary,
),
),
],
),
],
),
),
),
],
),
),
);
}
}
@@ -0,0 +1,36 @@
import 'package:flutter/material.dart';
import '../../../common/theme/theme.dart';
class ProductEmptyCard extends StatelessWidget {
const ProductEmptyCard({super.key});
@override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.symmetric(horizontal: 16),
padding: EdgeInsets.all(24),
decoration: BoxDecoration(
color: AppColor.surface,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: AppColor.borderLight),
),
child: Center(
child: Column(
children: [
Icon(
Icons.inventory_2_outlined,
size: 48,
color: AppColor.textLight,
),
SizedBox(height: 12),
Text(
"Belum ada produk",
style: AppStyle.md.copyWith(color: AppColor.textSecondary),
),
],
),
),
);
}
}
@@ -0,0 +1,63 @@
import 'package:flutter/material.dart';
import '../../../common/data/service_data.dart';
import '../../../common/theme/theme.dart';
import '../image/image.dart';
import 'gradient_card.dart';
class ServiceCard extends StatelessWidget {
final Service service;
const ServiceCard({super.key, required this.service});
@override
Widget build(BuildContext context) {
return GradientCard(
padding: EdgeInsets.symmetric(vertical: 12, horizontal: 16),
child: Row(
children: [
ClipRRect(
borderRadius: BorderRadiusGeometry.circular(8),
child: Image.asset(
service.imagePath,
width: 60,
height: 60,
errorBuilder: (context, error, stackTrace) =>
ImagePlaceholder(width: 60, height: 60),
),
),
SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
service.name,
style: AppStyle.xl.copyWith(
fontWeight: FontWeight.bold,
color: AppColor.white,
),
),
Text(
service.description,
style: AppStyle.md.copyWith(
fontWeight: FontWeight.w500,
color: AppColor.white,
),
),
],
),
),
InkWell(
child: Text(
'Ubah',
style: AppStyle.md.copyWith(
color: AppColor.white,
fontWeight: FontWeight.bold,
),
),
),
],
),
);
}
}
@@ -0,0 +1,43 @@
import 'package:flutter/material.dart';
import '../../../common/extension/extension.dart';
import '../../../common/theme/theme.dart';
class VariantCard extends StatelessWidget {
final String name;
final bool isSelected;
const VariantCard({super.key, required this.name, this.isSelected = false});
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.symmetric(vertical: 16, horizontal: 8),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(AppValue.borderRadius),
color: isSelected ? AppColor.primary.withOpacity(0.1) : AppColor.white,
border: Border.all(
width: isSelected ? 2 : 1,
color: isSelected ? AppColor.primary : AppColor.border,
),
),
child: Row(
children: [
Expanded(
child: Text(
name,
style: AppStyle.md.copyWith(fontWeight: FontWeight.w600),
),
),
SizedBox(width: 12),
Text(
"+${"2000".currencyFormatRp}",
style: AppStyle.md.copyWith(
color: AppColor.primary,
fontWeight: FontWeight.w500,
),
),
],
),
);
}
}
@@ -0,0 +1,106 @@
import 'package:flutter/widgets.dart';
import '../../../common/theme/theme.dart';
import '../../../sample/product_sample_data.dart';
class SliverCategoryDelegate extends SliverPersistentHeaderDelegate {
final List<ProductCategory> categories;
final String selectedCategoryId;
final Function(String) onCategoryTap;
SliverCategoryDelegate({
required this.categories,
required this.selectedCategoryId,
required this.onCategoryTap,
});
@override
double get minExtent => 60;
@override
double get maxExtent => 60;
@override
Widget build(
BuildContext context,
double shrinkOffset,
bool overlapsContent,
) {
return Container(
height: 60,
decoration: BoxDecoration(
color: AppColor.surface,
border: Border(
bottom: BorderSide(color: AppColor.borderLight, width: 1),
),
),
child: ListView.builder(
scrollDirection: Axis.horizontal,
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
itemCount: categories.length,
itemBuilder: (context, index) {
final category = categories[index];
final isSelected = category.id == selectedCategoryId;
return GestureDetector(
onTap: () => onCategoryTap(category.id),
child: Container(
margin: EdgeInsets.only(right: 12),
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: isSelected ? AppColor.primary : AppColor.backgroundLight,
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: isSelected ? AppColor.primary : AppColor.border,
width: 1,
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(category.icon, style: AppStyle.md),
SizedBox(width: 8),
Text(
category.name,
style: AppStyle.md.copyWith(
color: isSelected
? AppColor.textWhite
: AppColor.textPrimary,
fontWeight: FontWeight.w600,
),
),
SizedBox(width: 4),
Container(
padding: EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: isSelected
? AppColor.textWhite.withOpacity(0.2)
: AppColor.primary.withOpacity(0.1),
borderRadius: BorderRadius.circular(10),
),
child: Text(
"${category.productCount}",
style: AppStyle.xs.copyWith(
color: isSelected
? AppColor.textWhite
: AppColor.primary,
fontWeight: FontWeight.bold,
),
),
),
],
),
),
);
},
),
);
}
@override
bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) {
return oldDelegate is SliverCategoryDelegate &&
(oldDelegate.selectedCategoryId != selectedCategoryId ||
oldDelegate.categories != categories);
}
}
@@ -0,0 +1,225 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import '../../../common/data/service_data.dart';
import '../../../common/extension/extension.dart';
import '../../../common/theme/theme.dart';
import '../../../sample/product_sample_data.dart';
import '../../components/border/dashed_border.dart';
import '../../components/button/button.dart';
import '../../components/card/service_card.dart';
import 'widgets/checkout_item.dart';
import 'widgets/merchant.dart';
@RoutePage()
class CheckoutPage extends StatelessWidget {
const CheckoutPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Checkout')),
bottomNavigationBar: Container(
padding: EdgeInsets.all(AppValue.padding).copyWith(bottom: 24),
decoration: BoxDecoration(
color: AppColor.white,
boxShadow: [
BoxShadow(
color: AppColor.black.withOpacity(0.1),
offset: Offset(2, 0),
blurRadius: 10,
),
],
),
child: AppElevatedButton(onPressed: () {}, title: 'Pesan Sekarang'),
),
body: ListView(
children: [
Container(
padding: EdgeInsets.all(AppValue.padding),
child: Column(
children: [
ServiceCard(service: services.first),
SizedBox(height: 16),
CheckoutMerchant(),
],
),
),
Divider(thickness: 4, color: AppColor.borderLight),
Container(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_sectionTitle('Pesananmu'),
SizedBox(height: 16),
CheckoutItem(product: products.first),
CheckoutItem(product: products.first),
Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Ada Tambah lagi?',
style: AppStyle.md.copyWith(
fontWeight: FontWeight.w600,
),
),
SizedBox(height: 4),
Text(
'Kakmu bisa menambah menu lainnya, ya.',
style: AppStyle.md,
),
],
),
),
SizedBox(width: 12),
AppOutlineButton(
width: 100,
onPressed: () {},
title: '+ Tambah',
),
],
),
],
),
),
Divider(thickness: 4, color: AppColor.borderLight),
Container(
padding: EdgeInsets.all(AppValue.padding),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_sectionTitle('Voucher'),
SizedBox(height: 16),
Row(
children: [
Container(
padding: EdgeInsets.all(4),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
color: AppColor.primary.withOpacity(0.1),
),
child: Icon(
Icons.confirmation_number,
color: AppColor.primary,
),
),
SizedBox(width: 12),
Expanded(
child: Text(
'Kamu bisa menggunakan voucher mu disini.',
style: AppStyle.md,
),
),
Icon(Icons.chevron_right, color: AppColor.textSecondary),
],
),
],
),
),
Divider(thickness: 4, color: AppColor.borderLight),
Container(
padding: EdgeInsets.all(AppValue.padding),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_sectionTitle('Metode Pembayaran'),
SizedBox(height: 16),
Row(
children: [
Container(
padding: EdgeInsets.all(4),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
color: AppColor.primary.withOpacity(0.1),
),
child: Icon(Icons.wallet, color: AppColor.primary),
),
SizedBox(width: 12),
Expanded(
child: Text(
'Pilih metode pembayaran disini.',
style: AppStyle.md,
),
),
Icon(Icons.chevron_right, color: AppColor.textSecondary),
],
),
],
),
),
Divider(thickness: 4, color: AppColor.borderLight),
Container(
padding: EdgeInsets.all(AppValue.padding),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_sectionTitle('Rincian Pembayaran'),
SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Harga',
style: AppStyle.md.copyWith(fontWeight: FontWeight.w500),
),
Text(
"40000".currencyFormatRp,
style: AppStyle.md.copyWith(fontWeight: FontWeight.w500),
),
],
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 12),
child: DashedDivider(),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Total Pembayaran',
style: AppStyle.lg.copyWith(fontWeight: FontWeight.bold),
),
Text(
"40000".currencyFormatRp,
style: AppStyle.lg.copyWith(fontWeight: FontWeight.bold),
),
],
),
],
),
),
Divider(thickness: 4, color: AppColor.borderLight),
Container(
padding: EdgeInsets.all(AppValue.padding),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Kebijakan Pembatalan",
style: AppStyle.md.copyWith(fontWeight: FontWeight.bold),
),
SizedBox(height: 4),
Text(
'Kamu tidak dapat melakukan pembatalan atau perubahan apapun pada pesanan setelah melakukan pembayaran.',
style: AppStyle.sm,
),
],
),
),
SizedBox(height: 40),
],
),
);
}
Text _sectionTitle(String title) {
return Text(
title,
style: AppStyle.xl.copyWith(fontWeight: FontWeight.bold),
);
}
}
@@ -0,0 +1,68 @@
import 'package:flutter/material.dart';
import '../../../../common/extension/extension.dart';
import '../../../../common/theme/theme.dart';
import '../../../../sample/product_sample_data.dart';
import '../../../components/button/button.dart';
import '../../../components/image/image.dart';
class CheckoutItem extends StatelessWidget {
final Product product;
const CheckoutItem({super.key, required this.product});
@override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.only(bottom: 16),
decoration: BoxDecoration(),
child: Column(
children: [
Row(
children: [
AppNetworkImage(url: product.imageUrl, width: 60, height: 60),
SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
product.name,
style: AppStyle.md.copyWith(fontWeight: FontWeight.bold),
),
SizedBox(height: 4),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"1x ${product.price.currencyFormatRp}",
style: AppStyle.md,
),
Text("27000".currencyFormatRp, style: AppStyle.md),
],
),
],
),
),
],
),
SizedBox(height: 8),
Row(
children: [
Expanded(
child: Text(
'Catatan',
style: AppStyle.md.copyWith(
color: AppColor.textSecondary,
fontWeight: FontWeight.w600,
),
),
),
SizedBox(width: 12),
QtyButton(),
],
),
],
),
);
}
}
@@ -0,0 +1,44 @@
import 'package:flutter/material.dart';
import '../../../../common/constant/app_constant.dart';
import '../../../../common/theme/theme.dart';
class CheckoutMerchant extends StatelessWidget {
const CheckoutMerchant({super.key});
@override
Widget build(BuildContext context) {
return Row(
children: [
Container(
width: 60,
height: 60,
decoration: BoxDecoration(
color: AppColor.surface,
borderRadius: BorderRadius.circular(12),
),
child: Center(
child: ClipRRect(
borderRadius: BorderRadius.circular(12),
child: Image.asset(merchant.imageUrl),
),
),
),
SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
merchant.name,
style: AppStyle.h6.copyWith(fontWeight: FontWeight.bold),
),
Text(merchant.category, style: AppStyle.md.copyWith()),
Row(children: [SizedBox(width: 12)]),
],
),
),
],
);
}
}
@@ -60,14 +60,14 @@ class Product {
} }
@RoutePage() @RoutePage()
class PoinPage extends StatefulWidget { class CoinPage extends StatefulWidget {
const PoinPage({super.key}); const CoinPage({super.key});
@override @override
State<PoinPage> createState() => _PoinPageState(); State<CoinPage> createState() => _CoinPageState();
} }
class _PoinPageState extends State<PoinPage> { class _CoinPageState extends State<CoinPage> {
final ScrollController _scrollController = ScrollController(); final ScrollController _scrollController = ScrollController();
// Sample data - Indonesian content // Sample data - Indonesian content
@@ -276,7 +276,7 @@ class _PoinPageState extends State<PoinPage> {
actions: [ actions: [
IconButton( IconButton(
icon: Icon(Icons.history), icon: Icon(Icons.history),
onPressed: () => context.router.push(PoinHistoryRoute()), onPressed: () => context.router.push(CoinHistoryRoute()),
), ),
], ],
), ),
@@ -43,14 +43,14 @@ class PointTransaction {
} }
@RoutePage() @RoutePage()
class PoinHistoryPage extends StatefulWidget { class CoinHistoryPage extends StatefulWidget {
const PoinHistoryPage({super.key}); const CoinHistoryPage({super.key});
@override @override
State<PoinHistoryPage> createState() => _PoinHistoryPageState(); State<CoinHistoryPage> createState() => _CoinHistoryPageState();
} }
class _PoinHistoryPageState extends State<PoinHistoryPage> { class _CoinHistoryPageState extends State<CoinHistoryPage> {
TransactionType selectedFilter = TransactionType.all; TransactionType selectedFilter = TransactionType.all;
// Sample transaction data // Sample transaction data
@@ -2,7 +2,7 @@ import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../../../common/theme/theme.dart'; import '../../../../../common/theme/theme.dart';
import '../../poin_page.dart'; import '../../coin_page.dart';
@RoutePage() @RoutePage()
class ProductRedeemPage extends StatefulWidget { class ProductRedeemPage extends StatefulWidget {
+6 -1
View File
@@ -24,7 +24,12 @@ class _MainPageState extends State<MainPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AutoTabsRouter.pageView( return AutoTabsRouter.pageView(
routes: [HomeRoute(), VoucherRoute(), OrderRoute(), ProfileRoute()], routes: [
HomeRoute(),
// VoucherRoute(),
OrderRoute(),
ProfileRoute(),
],
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
builder: (context, child, pageController) => Scaffold( builder: (context, child, pageController) => Scaffold(
body: child, body: child,
@@ -5,13 +5,14 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../../../application/auth/auth_bloc.dart'; import '../../../../../application/auth/auth_bloc.dart';
import '../../../../../application/customer/customer_point_loader/customer_point_loader_bloc.dart'; import '../../../../../application/customer/customer_point_loader/customer_point_loader_bloc.dart';
import '../../../../../common/constant/app_constant.dart';
import '../../../../../common/theme/theme.dart'; import '../../../../../common/theme/theme.dart';
import '../../../../components/image/image.dart'; import '../../../../components/image/image.dart';
import '../../../../router/app_router.gr.dart'; import '../../../../router/app_router.gr.dart';
import 'widgets/feature_section.dart'; import 'widgets/banner_card.dart';
import 'widgets/lottery_card.dart';
import 'widgets/point_card.dart'; import 'widgets/point_card.dart';
import 'widgets/popular_merchant_section.dart'; import 'widgets/popular_merchant_section.dart';
import 'widgets/service_section.dart';
@RoutePage() @RoutePage()
class HomePage extends StatefulWidget { class HomePage extends StatefulWidget {
@@ -52,8 +53,14 @@ class _HomePageState extends State<HomePage> {
children: [ children: [
_buildHeaderSection(), _buildHeaderSection(),
const SizedBox(height: 70), const SizedBox(height: 70),
HomeFeatureSection(), HomeServiceSection(),
HomeLotteryBanner(onTap: () => context.router.push(DrawRoute())), HomeBanner(
title: '🎰 My Rewards',
subtitle:
'Main dan tepat menangkan produk gratis dari ${AppConstant.appName}.',
actionText: 'Mainkan Sekarang',
onTap: () => context.router.push(DrawRoute()),
),
HomePopularMerchantSection(), HomePopularMerchantSection(),
], ],
), ),
@@ -1,8 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../../../../common/theme/theme.dart'; import '../../../../../../common/theme/theme.dart';
class HomeLotteryBanner extends StatefulWidget { class HomeBanner extends StatefulWidget {
const HomeLotteryBanner({ const HomeBanner({
super.key, super.key,
this.onTap, this.onTap,
this.title = "🎰 UNDIAN BERHADIAH", this.title = "🎰 UNDIAN BERHADIAH",
@@ -18,11 +18,10 @@ class HomeLotteryBanner extends StatefulWidget {
final String actionText; final String actionText;
@override @override
State<HomeLotteryBanner> createState() => _HomeLotteryBannerState(); State<HomeBanner> createState() => _HomeBannerState();
} }
class _HomeLotteryBannerState extends State<HomeLotteryBanner> class _HomeBannerState extends State<HomeBanner> with TickerProviderStateMixin {
with TickerProviderStateMixin {
late AnimationController _pulseController; late AnimationController _pulseController;
late AnimationController _shimmerController; late AnimationController _shimmerController;
late AnimationController _floatingController; late AnimationController _floatingController;
@@ -290,7 +289,7 @@ class _HomeLotteryBannerState extends State<HomeLotteryBanner>
children: [ children: [
const Center( const Center(
child: Icon( child: Icon(
Icons.casino, Icons.games,
color: Colors.white, color: Colors.white,
size: 32, size: 32,
shadows: [ shadows: [
@@ -1,52 +1,60 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../../../../application/auth/auth_bloc.dart'; import '../../../../../../common/theme/theme.dart';
import '../../../../../router/app_router.gr.dart'; import '../../../../../components/card/gradient_card.dart';
import 'feature_card.dart';
class HomeFeatureSection extends StatelessWidget { class HomeFeatureSection extends StatelessWidget {
const HomeFeatureSection({super.key}); const HomeFeatureSection({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocBuilder<AuthBloc, AuthState>( return Padding(
builder: (context, state) { padding: const EdgeInsets.all(16),
return Container( child: Row(
padding: const EdgeInsets.all(16), crossAxisAlignment: CrossAxisAlignment.start,
child: Row( children: [
mainAxisAlignment: MainAxisAlignment.spaceEvenly, Expanded(
children: [ child: GradientCard(
HomeFeatureCard( child: _content(
icon: Icons.card_giftcard, 'Dine In',
title: 'Reward', 'Rasakan Sensasi Langsung di Meja Kami!',
iconColor: const Color(0xFF1976D2),
onTap: () => context.router.push(RewardRoute()),
), ),
HomeFeatureCard( ),
icon: Icons.casino,
title: 'Undian',
iconColor: const Color(0xFF7B1FA2),
onTap: () => context.router.push(DrawRoute()),
),
HomeFeatureCard(
icon: Icons.store,
title: 'Merchant',
iconColor: const Color(0xFF388E3C),
onTap: () => context.router.push(MerchantRoute()),
),
HomeFeatureCard(
icon: Icons.blur_circular,
title: 'Wheels',
iconColor: const Color(0xFF388E3C),
onTap: () => state.isAuthenticated
? context.router.push(FerrisWheelRoute())
: context.router.push(OnboardingRoute()),
),
],
), ),
); SizedBox(width: 16),
}, Expanded(
child: GradientCard(
child: _content(
'Take Away',
'Nikmati di Mana Saja, Tetap Mantap!',
),
),
),
],
),
);
}
Column _content(String title, String subtitle) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: AppStyle.lg.copyWith(
color: AppColor.white,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 8),
Text(
subtitle,
style: AppStyle.md.copyWith(
color: AppColor.white,
fontWeight: FontWeight.w500,
),
),
],
); );
} }
} }
@@ -16,7 +16,7 @@ class HomePointCard extends StatelessWidget {
builder: (context, state) { builder: (context, state) {
return GestureDetector( return GestureDetector(
onTap: () => state.isAuthenticated onTap: () => state.isAuthenticated
? context.router.push(PoinRoute()) ? context.router.push(CoinRoute())
: context.router.push(OnboardingRoute()), : context.router.push(OnboardingRoute()),
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
@@ -0,0 +1,71 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import '../../../../../../common/data/service_data.dart';
import '../../../../../../common/theme/theme.dart';
import '../../../../../components/card/gradient_card.dart';
import '../../../../../router/app_router.gr.dart';
class HomeServiceSection extends StatelessWidget {
const HomeServiceSection({super.key});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(16),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: List.generate(services.length, (index) {
return Expanded(
child: Padding(
padding: EdgeInsets.only(right: index == 0 ? 12 : 0),
child: InkWell(
onTap: () =>
context.router.push(MenuRoute(service: services[index])),
child: GradientCard(
padding: EdgeInsets.zero,
child: _content(services[index]),
),
),
),
);
}),
),
);
}
Stack _content(Service service) {
return Stack(
children: [
Positioned(
right: 4,
top: 4,
child: Image.asset(service.imagePath, width: 60, height: 60),
),
Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
service.name,
style: AppStyle.lg.copyWith(
color: AppColor.white,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 8),
Text(
service.description,
style: AppStyle.md.copyWith(
color: AppColor.white,
fontWeight: FontWeight.w500,
),
),
],
),
),
],
);
}
}
@@ -6,6 +6,7 @@ import '../../../../../application/auth/auth_bloc.dart';
import '../../../../../application/auth/logout_form/logout_form_bloc.dart'; import '../../../../../application/auth/logout_form/logout_form_bloc.dart';
import '../../../../../common/theme/theme.dart'; import '../../../../../common/theme/theme.dart';
import '../../../../../injection.dart'; import '../../../../../injection.dart';
import '../../../../components/card/gradient_card.dart';
import '../../../../components/toast/flushbar.dart'; import '../../../../components/toast/flushbar.dart';
import '../../../../router/app_router.gr.dart'; import '../../../../router/app_router.gr.dart';
@@ -46,165 +47,81 @@ class ProfilePage extends StatelessWidget implements AutoRouteWrapper {
onTap: () => state.isAuthenticated onTap: () => state.isAuthenticated
? context.router.push(AccountMyRoute()) ? context.router.push(AccountMyRoute())
: context.router.push(OnboardingRoute()), : context.router.push(OnboardingRoute()),
child: Container( child: GradientCard(
decoration: BoxDecoration( child: !state.isAuthenticated
gradient: LinearGradient( ? Row(
colors: AppColor.primaryGradient, children: [
begin: Alignment.topLeft, Expanded(
end: Alignment.bottomRight, child: Text(
), 'Silahkan Masuk',
borderRadius: BorderRadius.circular(16), style: AppStyle.lg.copyWith(
), fontWeight: FontWeight.bold,
child: Stack( color: AppColor.white,
children: [ letterSpacing: 0.5,
// 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: Container(
width: 30,
height: 2,
decoration: BoxDecoration(
color: AppColor.white.withOpacity(0.15),
borderRadius: BorderRadius.circular(1),
), ),
), ],
), )
), : Row(
Positioned( children: [
bottom: 15, // Avatar
right: 10, Container(
child: Transform.rotate( width: 60,
angle: -0.5, height: 60,
child: Container( decoration: BoxDecoration(
width: 25, color: AppColor.white,
height: 2, shape: BoxShape.circle,
decoration: BoxDecoration( boxShadow: [
color: AppColor.white.withOpacity(0.15), BoxShadow(
borderRadius: BorderRadius.circular(1), color: AppColor.black.withOpacity(
), 0.1,
),
),
),
// Main Content
Padding(
padding: const EdgeInsets.all(16.0),
child: !state.isAuthenticated
? Row(
children: [
Expanded(
child: Text(
'Silahkan Masuk',
style: AppStyle.lg.copyWith(
fontWeight: FontWeight.bold,
color: AppColor.white,
letterSpacing: 0.5,
),
), ),
), blurRadius: 8,
], offset: const Offset(0, 2),
)
: 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),
),
],
),
child: Icon(
Icons.person,
size: 30,
color: AppColor.primary,
),
),
const SizedBox(width: 16),
// User Info
Expanded(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
state.user.name,
style: AppStyle.lg.copyWith(
fontWeight:
FontWeight.bold,
color: AppColor.white,
letterSpacing: 0.5,
),
),
const SizedBox(height: 4),
Text(
state.user.phoneNumber,
style: AppStyle.sm.copyWith(
color: AppColor.white
.withOpacity(0.9),
),
),
],
),
),
// Arrow Icon
Icon(
Icons.arrow_forward_ios,
color: AppColor.white,
size: 14,
), ),
], ],
), ),
), child: Icon(
], Icons.person,
), size: 30,
color: AppColor.primary,
),
),
const SizedBox(width: 16),
// User Info
Expanded(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
state.user.name,
style: AppStyle.lg.copyWith(
fontWeight: FontWeight.bold,
color: AppColor.white,
letterSpacing: 0.5,
),
),
const SizedBox(height: 4),
Text(
state.user.phoneNumber,
style: AppStyle.sm.copyWith(
color: AppColor.white
.withOpacity(0.9),
),
),
],
),
),
// Arrow Icon
Icon(
Icons.arrow_forward_ios,
color: AppColor.white,
size: 14,
),
],
),
), ),
), ),
], ],
@@ -18,11 +18,11 @@ class MainBottomNavbar extends StatelessWidget {
label: 'Home', label: 'Home',
tooltip: 'Home', tooltip: 'Home',
), ),
BottomNavigationBarItem( // BottomNavigationBarItem(
icon: Icon(Icons.discount), // icon: Icon(Icons.discount),
label: 'Voucher', // label: 'Voucher',
tooltip: 'Voucher', // tooltip: 'Voucher',
), // ),
BottomNavigationBarItem( BottomNavigationBarItem(
icon: Icon(Icons.list), icon: Icon(Icons.list),
label: 'Pesanan', label: 'Pesanan',
+132
View File
@@ -0,0 +1,132 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import '../../../common/data/service_data.dart';
import '../../../common/theme/theme.dart';
import '../../../sample/product_sample_data.dart';
import '../../components/delegate/category_delegate.dart';
import 'widgets/header.dart';
import 'widgets/product_section.dart';
@RoutePage()
class MenuPage extends StatefulWidget {
final Service service;
const MenuPage({super.key, required this.service});
@override
State<MenuPage> createState() => _MenuPageState();
}
class _MenuPageState extends State<MenuPage> {
final ScrollController _scrollController = ScrollController();
final List<GlobalKey> _productSectionKeys = [];
String _selectedCategoryId = "";
@override
void initState() {
super.initState();
_selectedCategoryId = categories.isNotEmpty ? categories.first.id : "";
// Initialize keys for each category
for (int i = 0; i < categories.length; i++) {
_productSectionKeys.add(GlobalKey());
}
}
void _scrollToCategory(String categoryId) {
setState(() {
_selectedCategoryId = categoryId;
});
final categoryIndex = categories.indexWhere((cat) => cat.id == categoryId);
if (categoryIndex >= 0 && categoryIndex < _productSectionKeys.length) {
final key = _productSectionKeys[categoryIndex];
final context = key.currentContext;
if (context != null) {
Future.delayed(Duration(milliseconds: 100), () {
if (mounted) {
Scrollable.ensureVisible(
context,
duration: Duration(milliseconds: 500),
curve: Curves.easeInOut,
alignment: 0.1,
);
}
});
}
}
}
List<Product> _getProductsByCategory(String categoryId) {
return products
.where((product) => product.categoryId == categoryId)
.toList();
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColor.background,
body: CustomScrollView(
controller: _scrollController,
slivers: [
// App Bar with merchant info
SliverAppBar(
expandedHeight: 200,
pinned: true,
backgroundColor: AppColor.primary,
leading: IconButton(
icon: Icon(Icons.arrow_back, color: AppColor.textWhite),
onPressed: () => Navigator.of(context).pop(),
),
actions: [
IconButton(
icon: Icon(Icons.share, color: AppColor.textWhite),
onPressed: () {},
),
IconButton(
icon: Icon(Icons.favorite_border, color: AppColor.textWhite),
onPressed: () {},
),
],
flexibleSpace: FlexibleSpaceBar(
background: MenuHeader(service: widget.service),
),
),
// Categories (will be pinned)
SliverPersistentHeader(
pinned: true,
delegate: SliverCategoryDelegate(
categories: categories,
selectedCategoryId: _selectedCategoryId,
onCategoryTap: _scrollToCategory,
),
),
// Product sections by category
...categories.map((category) {
final categoryProducts = _getProductsByCategory(category.id);
final categoryIndex = categories.indexOf(category);
return SliverToBoxAdapter(
key: _productSectionKeys[categoryIndex],
child: MenuProductSection(
category: category,
categoryProducts: categoryProducts,
),
);
}),
],
),
);
}
}
@@ -0,0 +1,166 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import '../../../../../common/extension/extension.dart';
import '../../../../../common/theme/theme.dart';
import '../../../../../sample/product_sample_data.dart';
import '../../../../components/button/button.dart';
import '../../../../components/card/variant_card.dart';
import '../../../../components/image/image.dart';
import '../../../../router/app_router.gr.dart';
@RoutePage()
class MenuDetailPage extends StatefulWidget {
final Product product;
const MenuDetailPage({super.key, required this.product});
@override
State<MenuDetailPage> createState() => _MenuDetailPageState();
}
class _MenuDetailPageState extends State<MenuDetailPage> {
final ScrollController _scrollController = ScrollController();
double _titleOpacity = 0.0;
@override
void initState() {
super.initState();
_scrollController.addListener(_onScroll);
}
void _onScroll() {
// Hitung opacity berdasarkan scroll offset
// Mulai muncul dari offset 150, full opacity di offset 220
double offset = _scrollController.offset;
double newOpacity = ((offset - 150) / 70).clamp(0.0, 1.0);
if (newOpacity != _titleOpacity) {
setState(() => _titleOpacity = newOpacity);
}
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
bottomNavigationBar: Container(
padding: EdgeInsets.all(AppValue.padding).copyWith(bottom: 24),
decoration: BoxDecoration(
color: AppColor.white,
boxShadow: [
BoxShadow(
color: AppColor.black.withOpacity(0.1),
offset: Offset(2, 0),
blurRadius: 10,
),
],
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Expanded(flex: 1, child: QtyButton()),
SizedBox(width: 16),
Expanded(
flex: 2,
child: AppElevatedButton(
onPressed: () => context.router.push(CheckoutRoute()),
title: '+ Keranjang ${"27000".currencyFormatRp}',
),
),
],
),
),
body: CustomScrollView(
controller: _scrollController,
slivers: [
SliverAppBar(
expandedHeight: 240,
pinned: true,
backgroundColor: Colors.white,
title: AnimatedOpacity(
opacity: _titleOpacity,
duration: Duration(milliseconds: 200),
child: Text(
widget.product.name,
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.bold,
),
),
),
flexibleSpace: FlexibleSpaceBar(
background: Stack(
children: [
ImagePlaceholder(width: double.infinity, height: 240),
],
),
),
),
SliverToBoxAdapter(
child: Container(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.product.name,
style: AppStyle.xl.copyWith(fontWeight: FontWeight.bold),
),
SizedBox(height: 4),
Text(
widget.product.description,
style: AppStyle.md.copyWith(fontWeight: FontWeight.w500),
),
SizedBox(height: 8),
Align(
alignment: Alignment.centerRight,
child: Text(
widget.product.price.currencyFormatRp,
style: AppStyle.xxl.copyWith(
color: AppColor.primary,
fontWeight: FontWeight.w700,
),
),
),
],
),
),
),
SliverToBoxAdapter(
child: Container(
width: double.infinity,
height: 6,
decoration: BoxDecoration(color: AppColor.borderLight),
),
),
SliverToBoxAdapter(
child: Container(
padding: EdgeInsets.all(16),
decoration: BoxDecoration(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Pilih Varian',
style: AppStyle.lg.copyWith(fontWeight: FontWeight.bold),
),
SizedBox(height: 12),
VariantCard(name: 'Small'),
SizedBox(height: 8),
VariantCard(name: 'Normal', isSelected: true),
SizedBox(height: 8),
VariantCard(name: 'Large'),
],
),
),
),
],
),
);
}
}
@@ -0,0 +1,142 @@
import 'package:flutter/material.dart';
import '../../../../common/constant/app_constant.dart';
import '../../../../common/data/service_data.dart';
import '../../../../common/theme/theme.dart';
class MenuHeader extends StatelessWidget {
final Service service;
const MenuHeader({super.key, required this.service});
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [AppColor.primary, AppColor.primary.withOpacity(0.8)],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
),
child: Stack(
children: [
// Background decoration
Positioned(
right: 20,
top: 60,
child: Opacity(
opacity: 0.1,
child: Icon(Icons.store, size: 100, color: AppColor.textWhite),
),
),
// Content
Positioned(
left: 20,
right: 20,
bottom: 20,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Service
Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
service.name,
style: AppStyle.lg.copyWith(
color: AppColor.white,
fontWeight: FontWeight.bold,
),
),
Text(
service.description,
style: AppStyle.md.copyWith(
color: AppColor.white,
fontWeight: FontWeight.w500,
),
),
],
),
),
SizedBox(width: 12),
Image.asset(service.imagePath, width: 60, height: 60),
],
),
SizedBox(height: 16),
// Merchant
Row(
children: [
Container(
width: 60,
height: 60,
decoration: BoxDecoration(
color: AppColor.surface,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: AppColor.black.withOpacity(0.2),
blurRadius: 8,
offset: Offset(0, 4),
),
],
),
child: Center(
child: ClipRRect(
borderRadius: BorderRadius.circular(12),
child: Image.asset(merchant.imageUrl),
),
),
),
SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
merchant.name,
style: AppStyle.h6.copyWith(
color: AppColor.textWhite,
fontWeight: FontWeight.bold,
),
),
Text(
merchant.category,
style: AppStyle.md.copyWith(
color: AppColor.textWhite.withOpacity(0.9),
),
),
Row(children: [SizedBox(width: 12)]),
],
),
),
Container(
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: merchant.isOpen
? AppColor.success
: AppColor.error,
borderRadius: BorderRadius.circular(6),
),
child: Text(
merchant.isOpen ? "BUKA" : "TUTUP",
style: AppStyle.xs.copyWith(
color: AppColor.textWhite,
fontWeight: FontWeight.bold,
),
),
),
],
),
],
),
),
],
),
);
}
}
@@ -0,0 +1,77 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import '../../../../common/theme/theme.dart';
import '../../../../sample/product_sample_data.dart';
import '../../../components/card/product_card.dart';
import '../../../components/card/product_empty_card.dart';
import '../../../router/app_router.gr.dart';
class MenuProductSection extends StatelessWidget {
final ProductCategory category;
final List<Product> categoryProducts;
const MenuProductSection({
super.key,
required this.category,
required this.categoryProducts,
});
@override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.only(bottom: 24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Section header
Padding(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 16),
child: Row(
children: [
Text(category.icon, style: AppStyle.h5),
SizedBox(width: 8),
Text(
category.name,
style: AppStyle.lg.copyWith(
fontWeight: FontWeight.bold,
color: AppColor.textPrimary,
),
),
SizedBox(width: 8),
Text(
"(${categoryProducts.length})",
style: AppStyle.md.copyWith(color: AppColor.textSecondary),
),
],
),
),
// Products grid
if (categoryProducts.isEmpty)
ProductEmptyCard()
else
GridView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
padding: EdgeInsets.symmetric(horizontal: 16),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 12,
mainAxisSpacing: 12,
childAspectRatio: 0.75,
),
itemCount: categoryProducts.length,
itemBuilder: (context, index) {
return ProductCard(
product: categoryProducts[index],
onTap: () => context.router.push(
MenuDetailRoute(product: categoryProducts[index]),
),
);
},
),
],
),
);
}
}
@@ -1,47 +1,9 @@
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../../../common/theme/theme.dart'; import '../../../../../common/theme/theme.dart';
import '../../../../../sample/product_sample_data.dart';
import '../../../../../sample/sample_data.dart'; import '../../../../../sample/sample_data.dart';
// Models
class ProductCategory {
final String id;
final String name;
final String icon;
final int productCount;
ProductCategory({
required this.id,
required this.name,
required this.icon,
required this.productCount,
});
}
class Product {
final String id;
final String name;
final String description;
final int price;
final String categoryId;
final String imageUrl;
final bool isAvailable;
final double rating;
final int soldCount;
Product({
required this.id,
required this.name,
required this.description,
required this.price,
required this.categoryId,
required this.imageUrl,
required this.isAvailable,
required this.rating,
required this.soldCount,
});
}
@RoutePage() @RoutePage()
class MerchantDetailPage extends StatefulWidget { class MerchantDetailPage extends StatefulWidget {
final MerchantModel merchant; final MerchantModel merchant;
@@ -58,89 +20,6 @@ class _MerchantDetailPageState extends State<MerchantDetailPage> {
String _selectedCategoryId = ""; String _selectedCategoryId = "";
// Sample data
final List<ProductCategory> categories = [
ProductCategory(id: "1", name: "Makanan", icon: "🍽️", productCount: 8),
ProductCategory(id: "2", name: "Minuman", icon: "🥤", productCount: 6),
ProductCategory(id: "3", name: "Snack", icon: "🍿", productCount: 5),
ProductCategory(id: "4", name: "Es Krim", icon: "🍦", productCount: 4),
ProductCategory(id: "5", name: "Paket", icon: "📦", productCount: 3),
];
final List<Product> products = [
// Makanan
Product(
id: "1",
name: "Nasi Gudeg",
description: "Gudeg khas Yogyakarta dengan ayam dan telur",
price: 25000,
categoryId: "1",
imageUrl: "https://via.placeholder.com/150",
isAvailable: true,
rating: 4.5,
soldCount: 50,
),
Product(
id: "2",
name: "Soto Ayam",
description: "Soto ayam kuning dengan nasi dan kerupuk",
price: 18000,
categoryId: "1",
imageUrl: "https://via.placeholder.com/150",
isAvailable: true,
rating: 4.3,
soldCount: 75,
),
Product(
id: "3",
name: "Gado-gado",
description: "Gado-gado segar dengan bumbu kacang",
price: 15000,
categoryId: "1",
imageUrl: "https://via.placeholder.com/150",
isAvailable: false,
rating: 4.2,
soldCount: 30,
),
// Minuman
Product(
id: "4",
name: "Es Teh Manis",
description: "Es teh manis segar",
price: 5000,
categoryId: "2",
imageUrl: "https://via.placeholder.com/150",
isAvailable: true,
rating: 4.0,
soldCount: 120,
),
Product(
id: "5",
name: "Jus Jeruk",
description: "Jus jeruk segar tanpa gula tambahan",
price: 12000,
categoryId: "2",
imageUrl: "https://via.placeholder.com/150",
isAvailable: true,
rating: 4.4,
soldCount: 45,
),
// Snack
Product(
id: "6",
name: "Keripik Pisang",
description: "Keripik pisang renyah dan manis",
price: 8000,
categoryId: "3",
imageUrl: "https://via.placeholder.com/150",
isAvailable: true,
rating: 4.1,
soldCount: 25,
),
];
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@@ -6,12 +6,30 @@ import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'dart:math' as math; import 'dart:math' as math;
import '../../../../application/auth/auth_bloc.dart'; import '../../../../application/auth/auth_bloc.dart';
import '../../../../application/game/game_price_loader/game_prize_loader_bloc.dart'; import '../../../../application/game/ferris_wheel_loader/ferris_wheel_loader_bloc.dart';
import '../../../../common/theme/theme.dart'; import '../../../../common/theme/theme.dart';
import '../../../../common/painter/wheel_painter.dart'; import '../../../../common/painter/wheel_painter.dart';
import '../../../../injection.dart'; import '../../../../injection.dart';
import '../../../../domain/game/game.dart'; import '../../../../domain/game/game.dart';
import 'data/model.dart';
// Simple models for UI state
class PrizeHistory {
final String prize;
final DateTime dateTime;
final int value;
final Color color;
final IconData icon;
final String? gamePrizeId;
PrizeHistory({
required this.prize,
required this.dateTime,
required this.value,
required this.color,
required this.icon,
this.gamePrizeId,
});
}
@RoutePage() @RoutePage()
class FerrisWheelPage extends StatefulWidget implements AutoRouteWrapper { class FerrisWheelPage extends StatefulWidget implements AutoRouteWrapper {
@@ -22,12 +40,9 @@ class FerrisWheelPage extends StatefulWidget implements AutoRouteWrapper {
@override @override
Widget wrappedRoute(BuildContext context) => BlocProvider( Widget wrappedRoute(BuildContext context) => BlocProvider(
create: (context) => getIt<GamePrizeLoaderBloc>() create: (context) =>
..add( getIt<FerrisWheelLoaderBloc>()
const GamePrizeLoaderEvent.fetched( ..add(const FerrisWheelLoaderEvent.fetched()),
'28d5aed3-4c1b-4b7b-bad5-67cdd4919dc2',
),
),
child: this, child: this,
); );
} }
@@ -84,12 +99,10 @@ class _FerrisWheelPageState extends State<FerrisWheelPage>
void _initializeAudio() async { void _initializeAudio() async {
try { try {
await _bgmPlayer.setSource( await _bgmPlayer.setSource(AssetSource('audio/carnaval_main_theme.mp3'));
AssetSource('audio/carnival/bgm/carnival_main_theme.mp3'),
);
await _bgmPlayer.setReleaseMode(ReleaseMode.loop); await _bgmPlayer.setReleaseMode(ReleaseMode.loop);
await _bgmPlayer.setVolume(0.5); await _bgmPlayer.setVolume(0.5);
if (_isMusicEnabled) _bgmPlayer.resume(); if (_isMusicEnabled) await _bgmPlayer.resume();
} catch (e) { } catch (e) {
print('Error initializing audio: $e'); print('Error initializing audio: $e');
} }
@@ -107,23 +120,10 @@ class _FerrisWheelPageState extends State<FerrisWheelPage>
} }
} }
void _playButtonTap() => void _playButtonTap() => _playSound('audio/button_tap.mp3', volume: 0.3);
_playSound('audio/carnival/sfx/button_tap.mp3', volume: 0.3); void _playTokenSound() => _playSound('audio/token_sound.mp3', volume: 0.5);
void _playTokenSound() => void _playWheelSpin() => _playSound('audio/wheel_spin.mp3', volume: 0.8);
_playSound('audio/carnival/sfx/token_sound.mp3', volume: 0.5); void _playWinSound() => _playSound('audio/win_medium.mp3', volume: 0.8);
void _playWheelSpin() =>
_playSound('audio/carnival/sfx/wheel_spin.mp3', volume: 0.8);
void _playWinSound(GamePrize prize) {
int prizeValue = prize.metadata['value'] ?? prize.weight;
if (prizeValue >= 1000000) {
_playSound('audio/carnival/sfx/win_big.mp3', volume: 0.9);
} else if (prizeValue >= 5000) {
_playSound('audio/carnival/sfx/win_medium.mp3', volume: 0.8);
} else {
_playSound('audio/carnival/sfx/win_small.mp3', volume: 0.7);
}
}
void _toggleSound() { void _toggleSound() {
setState(() => _isSoundEnabled = !_isSoundEnabled); setState(() => _isSoundEnabled = !_isSoundEnabled);
@@ -170,66 +170,48 @@ class _FerrisWheelPageState extends State<FerrisWheelPage>
int _selectPrizeWithWeight() { int _selectPrizeWithWeight() {
if (gamePrizes.isEmpty) return 0; if (gamePrizes.isEmpty) return 0;
final availablePrizes = gamePrizes // Jika GamePrize memiliki properti weight, gunakan ini:
.asMap() // List<int> weights = gamePrizes.map((prize) => prize.weight ?? 1).toList();
.entries
.where((entry) => entry.value.stock > 0)
.toList();
if (availablePrizes.isEmpty) { // Untuk sementara, gunakan bobot default yang berbeda
return math.Random().nextInt(gamePrizes.length); // Hadiah langka (index awal) memiliki bobot lebih kecil
List<int> weights = [];
for (int i = 0; i < gamePrizes.length; i++) {
// Bobot menurun: hadiah pertama langka, hadiah terakhir mudah didapat
int weight = gamePrizes.length - i;
weights.add(weight);
} }
int totalWeight = availablePrizes // Hitung total bobot
.map((entry) => entry.value.weight) int totalWeight = weights.reduce((a, b) => a + b);
.reduce((a, b) => a + b);
if (totalWeight <= 0) { // Generate random number
final randomEntry = int randomNum = math.Random().nextInt(totalWeight);
availablePrizes[math.Random().nextInt(availablePrizes.length)];
return randomEntry.key;
}
int randomWeight = math.Random().nextInt(totalWeight); // Tentukan hadiah berdasarkan bobot
int currentWeight = 0; int currentWeight = 0;
for (int i = 0; i < gamePrizes.length; i++) {
for (final entry in availablePrizes) { currentWeight += weights[i];
currentWeight += entry.value.weight; if (randomNum < currentWeight) {
if (randomWeight < currentWeight) { return i;
return entry.key;
} }
} }
return availablePrizes.last.key; // Fallback (seharusnya tidak pernah terjadi)
return gamePrizes.length - 1;
} }
Color _getPrizeColor(GamePrize prize, int index) { Color _getPrizeColor(GamePrize prize, int index) {
final colorName = prize.metadata['color'] as String?; final colors = [
switch (colorName?.toLowerCase()) { AppColor.primary,
case 'primary': AppColor.info,
return AppColor.primary; AppColor.warning,
case 'info': AppColor.success,
return AppColor.info; AppColor.primaryDark,
case 'warning': AppColor.secondary,
return AppColor.warning; AppColor.error,
case 'success': ];
return AppColor.success; return colors[index % colors.length];
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() { void _spinWheel() {
@@ -245,23 +227,25 @@ class _FerrisWheelPageState extends State<FerrisWheelPage>
}); });
_idleRotationController.stop(); _idleRotationController.stop();
_idleRotationController.reset();
int targetSection = _selectPrizeWithWeight(); int selectedPrizeIndex = _selectPrizeWithWeight();
double sectionAngle = (2 * math.pi) / gamePrizes.length; double sectionAngle = (2 * math.pi) / gamePrizes.length;
double targetAngle = (targetSection * sectionAngle) + (sectionAngle / 2); double currentPos = currentRotation;
double baseRotations = 4 + math.Random().nextDouble() * 3;
double currentIdleRotation = _idleRotationAnimation?.value ?? 0.0;
double finalRotation =
currentRotation +
currentIdleRotation +
(baseRotations * 2 * math.pi) +
targetAngle;
_spinAnimation = // TAMBAH offset ke tengah section (bukan garis)
Tween<double>( double targetForSelectedSection =
begin: currentRotation + currentIdleRotation, -(selectedPrizeIndex * sectionAngle) - (sectionAngle / 2);
end: finalRotation,
).animate( double spins = 6 * 2 * math.pi;
double finalRotation = currentPos + spins + targetForSelectedSection;
while (finalRotation <= currentPos + spins) {
finalRotation += 2 * math.pi;
}
_spinAnimation = Tween<double>(begin: currentPos, end: finalRotation)
.animate(
CurvedAnimation( CurvedAnimation(
parent: _rotationController, parent: _rotationController,
curve: Curves.easeOutCubic, curve: Curves.easeOutCubic,
@@ -270,27 +254,20 @@ class _FerrisWheelPageState extends State<FerrisWheelPage>
_rotationController.reset(); _rotationController.reset();
_rotationController.animateTo(1.0).then((_) { _rotationController.animateTo(1.0).then((_) {
final wonPrize = gamePrizes[targetSection]; final wonPrize = gamePrizes[selectedPrizeIndex];
_playWinSound(wonPrize); _playWinSound();
setState(() { setState(() {
currentRotation = finalRotation; currentRotation = finalRotation;
isSpinning = false; isSpinning = false;
resultText = 'Selamat! Anda mendapat ${wonPrize.name}!'; resultText = 'Selamat! Anda mendapat ${wonPrize.name}!';
if (wonPrize.stock > 0) {
gamePrizes[targetSection] = wonPrize.copyWith(
stock: wonPrize.stock - 1,
);
}
prizeHistory.insert( prizeHistory.insert(
0, 0,
PrizeHistory( PrizeHistory(
prize: wonPrize.name, prize: wonPrize.name,
dateTime: DateTime.now(), dateTime: DateTime.now(),
value: wonPrize.metadata['value'] ?? wonPrize.weight, value: 100,
color: _getPrizeColor(wonPrize, targetSection), color: _getPrizeColor(wonPrize, selectedPrizeIndex),
icon: Icons.card_giftcard, icon: Icons.card_giftcard,
gamePrizeId: wonPrize.id, gamePrizeId: wonPrize.id,
), ),
@@ -298,7 +275,6 @@ class _FerrisWheelPageState extends State<FerrisWheelPage>
}); });
_showWinDialog(wonPrize); _showWinDialog(wonPrize);
_idleRotationController.reset();
_idleRotationController.repeat(); _idleRotationController.repeat();
}); });
} }
@@ -363,15 +339,6 @@ class _FerrisWheelPageState extends State<FerrisWheelPage>
color: AppColor.textWhite, 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), const SizedBox(height: 20),
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () {
@@ -406,7 +373,7 @@ class _FerrisWheelPageState extends State<FerrisWheelPage>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocBuilder<GamePrizeLoaderBloc, GamePrizeLoaderState>( return BlocBuilder<FerrisWheelLoaderBloc, FerrisWheelLoaderState>(
builder: (context, state) { builder: (context, state) {
if (state.isFetching) { if (state.isFetching) {
return Scaffold( return Scaffold(
@@ -425,10 +392,10 @@ class _FerrisWheelPageState extends State<FerrisWheelPage>
); );
} }
if (gamePrizes.isEmpty && state.gamePrize.isNotEmpty) { if (gamePrizes.isEmpty && state.ferrisWheel.prizes.isNotEmpty) {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() { setState(() {
gamePrizes = state.gamePrize; gamePrizes = state.ferrisWheel.prizes;
}); });
}); });
} }
@@ -445,100 +412,9 @@ class _FerrisWheelPageState extends State<FerrisWheelPage>
child: SafeArea( child: SafeArea(
child: Column( child: Column(
children: [ children: [
// Header _buildHeader(),
Container( _buildTabSelector(),
padding: const EdgeInsets.all(16),
child: Row(
children: [
IconButton(
onPressed: () {
_playButtonTap();
context.router.back();
},
icon: Icon(
Icons.close,
color: AppColor.textWhite,
size: 28,
),
),
Expanded(
child: Text(
'SPIN & WIN',
style: AppStyle.h6.copyWith(
fontWeight: FontWeight.bold,
color: AppColor.textWhite,
letterSpacing: 2,
),
),
),
IconButton(
onPressed: _toggleMusic,
icon: Icon(
_isMusicEnabled
? Icons.volume_up
: Icons.volume_off,
color: AppColor.textWhite,
),
),
IconButton(
onPressed: _toggleSound,
icon: Icon(
_isSoundEnabled
? Icons.graphic_eq
: Icons.volume_mute,
color: AppColor.textWhite,
),
),
],
),
),
// Tab Selector
Container(
margin: const EdgeInsets.symmetric(horizontal: 20),
decoration: BoxDecoration(
color: AppColor.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(25),
),
child: Row(
children: [
for (int i = 0; i < 3; i++)
Expanded(
child: GestureDetector(
onTap: () {
_playButtonTap();
setState(() => currentTabIndex = i);
},
child: Container(
padding: const EdgeInsets.symmetric(
vertical: 12,
),
decoration: BoxDecoration(
color: currentTabIndex == i
? AppColor.white
: Colors.transparent,
borderRadius: BorderRadius.circular(25),
),
child: Text(
['Spin Wheel', 'Daftar Hadiah', 'Riwayat'][i],
textAlign: TextAlign.center,
style: AppStyle.md.copyWith(
fontWeight: FontWeight.bold,
color: currentTabIndex == i
? AppColor.primary
: AppColor.textWhite,
),
),
),
),
),
],
),
),
const SizedBox(height: 20), const SizedBox(height: 20),
// Content Area
Expanded( Expanded(
child: currentTabIndex == 0 child: currentTabIndex == 0
? _buildMainContent() ? _buildMainContent()
@@ -555,254 +431,256 @@ class _FerrisWheelPageState extends State<FerrisWheelPage>
); );
} }
Widget _buildHeader() {
return Container(
padding: const EdgeInsets.all(16),
child: Row(
children: [
IconButton(
onPressed: () {
_playButtonTap();
context.router.back();
},
icon: Icon(Icons.close, color: AppColor.textWhite, size: 28),
),
Expanded(
child: Text(
'SPIN & WIN',
style: AppStyle.h6.copyWith(
fontWeight: FontWeight.bold,
color: AppColor.textWhite,
letterSpacing: 2,
),
),
),
IconButton(
onPressed: _toggleMusic,
icon: Icon(
_isMusicEnabled ? Icons.volume_up : Icons.volume_off,
color: AppColor.textWhite,
),
),
IconButton(
onPressed: _toggleSound,
icon: Icon(
_isSoundEnabled ? Icons.graphic_eq : Icons.volume_mute,
color: AppColor.textWhite,
),
),
],
),
);
}
Widget _buildTabSelector() {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 20),
decoration: BoxDecoration(
color: AppColor.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(25),
),
child: Row(
children: [
for (int i = 0; i < 3; i++)
Expanded(
child: GestureDetector(
onTap: () {
_playButtonTap();
setState(() => currentTabIndex = i);
},
child: Container(
padding: const EdgeInsets.symmetric(vertical: 12),
decoration: BoxDecoration(
color: currentTabIndex == i
? AppColor.white
: Colors.transparent,
borderRadius: BorderRadius.circular(25),
),
child: Text(
['Spin Wheel', 'Daftar Hadiah', 'Riwayat'][i],
textAlign: TextAlign.center,
style: AppStyle.md.copyWith(
fontWeight: FontWeight.bold,
color: currentTabIndex == i
? AppColor.primary
: AppColor.textWhite,
),
),
),
),
),
],
),
);
}
Widget _buildMainContent() { Widget _buildMainContent() {
return Column( return Column(
children: [ children: [
// User Info Card _buildUserInfoCard(),
Container(
margin: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColor.white,
borderRadius: BorderRadius.circular(15),
boxShadow: [
BoxShadow(
color: AppColor.black.withOpacity(0.1),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
BlocBuilder<AuthBloc, AuthState>(
builder: (context, auth) {
return Row(
children: [
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: AppColor.primary,
borderRadius: BorderRadius.circular(8),
),
child: Center(
child: Text(
'G',
style: AppStyle.lg.copyWith(
color: AppColor.textWhite,
fontWeight: FontWeight.bold,
),
),
),
),
const SizedBox(width: 12),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
auth.user.name,
style: AppStyle.lg.copyWith(
fontWeight: FontWeight.bold,
color: AppColor.textPrimary,
),
),
Text(
auth.user.phoneNumber,
style: AppStyle.sm.copyWith(
color: AppColor.textSecondary,
),
),
],
),
],
);
},
),
Row(
children: [
Container(
width: 20,
height: 20,
decoration: BoxDecoration(
color: AppColor.warning,
shape: BoxShape.circle,
),
child: Icon(
Icons.circle,
size: 12,
color: AppColor.textWhite,
),
),
const SizedBox(width: 8),
Text(
'Token: $tokens',
style: AppStyle.lg.copyWith(
fontWeight: FontWeight.bold,
color: AppColor.textPrimary,
),
),
],
),
],
),
),
Text( Text(
resultText, resultText,
style: AppStyle.md.copyWith(color: AppColor.textWhite), style: AppStyle.md.copyWith(color: AppColor.textWhite),
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
// Wheel Section
Expanded( Expanded(
child: Center( child: Center(
child: gamePrizes.isEmpty child: gamePrizes.isEmpty
? SpinKitFadingCircle(color: AppColor.textWhite, size: 36) ? SpinKitFadingCircle(color: AppColor.textWhite, size: 36)
: Stack( : _buildWheelSection(),
alignment: Alignment.center, ),
),
_buildBottomBanner(),
],
);
}
Widget _buildUserInfoCard() {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColor.white,
borderRadius: BorderRadius.circular(15),
boxShadow: [
BoxShadow(
color: AppColor.black.withOpacity(0.1),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
BlocBuilder<AuthBloc, AuthState>(
builder: (context, auth) {
return Row(
children: [
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: AppColor.primary,
borderRadius: BorderRadius.circular(8),
),
child: Center(
child: Text(
'G',
style: AppStyle.lg.copyWith(
color: AppColor.textWhite,
fontWeight: FontWeight.bold,
),
),
),
),
const SizedBox(width: 12),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// Glow Effect Text(
AnimatedBuilder( auth.user.name,
animation: _glowController, style: AppStyle.lg.copyWith(
builder: (context, child) => Container( fontWeight: FontWeight.bold,
width: 340, color: AppColor.textPrimary,
height: 340,
decoration: BoxDecoration(
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: AppColor.white.withOpacity(
0.3 + 0.2 * _glowController.value,
),
blurRadius: 40,
spreadRadius: 10,
),
],
),
), ),
), ),
Text(
// Spinning Wheel auth.user.phoneNumber,
AnimatedBuilder( style: AppStyle.sm.copyWith(
animation: isSpinning color: AppColor.textSecondary,
? _rotationController
: _idleRotationController,
builder: (context, child) {
double rotationAngle = isSpinning
? (_spinAnimation?.value ?? currentRotation)
: (currentRotation +
(_idleRotationAnimation?.value ?? 0.0));
return Transform.rotate(
angle: rotationAngle,
child: CustomPaint(
size: const Size(320, 320),
painter: WheelPainter(
gamePrizes: gamePrizes,
getPrizeColor: _getPrizeColor,
),
),
);
},
),
// Spin Button
AnimatedBuilder(
animation:
_pulseAnimation ??
const AlwaysStoppedAnimation(1.0),
builder: (context, child) => Transform.scale(
scale: _pulseAnimation?.value ?? 1.0,
child: Container(
width: 100,
height: 100,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [AppColor.warning, AppColor.warning],
),
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: AppColor.black.withOpacity(0.3),
blurRadius: 15,
offset: const Offset(0, 6),
),
BoxShadow(
color: AppColor.warning.withOpacity(0.5),
blurRadius: 20,
spreadRadius:
(_pulseAnimation?.value ?? 1.0) * 5,
),
],
),
child: Material(
color: Colors.transparent,
child: InkWell(
borderRadius: BorderRadius.circular(50),
onTap: _spinWheel,
child: Center(
child: Text(
'SPIN',
style: AppStyle.lg.copyWith(
color: AppColor.textWhite,
fontWeight: FontWeight.bold,
letterSpacing: 1,
),
),
),
),
),
),
),
),
// Pointer
Positioned(
top: 30,
child: Container(
width: 0,
height: 0,
decoration: BoxDecoration(
border: Border(
left: BorderSide(
width: 15,
color: Colors.transparent,
),
right: BorderSide(
width: 15,
color: Colors.transparent,
),
bottom: BorderSide(
width: 30,
color: AppColor.error,
),
),
),
), ),
), ),
], ],
), ),
],
);
},
),
Row(
children: [
Container(
width: 20,
height: 20,
decoration: BoxDecoration(
color: AppColor.warning,
shape: BoxShape.circle,
),
child: Icon(Icons.circle, size: 12, color: AppColor.textWhite),
),
const SizedBox(width: 8),
Text(
'Token: $tokens',
style: AppStyle.lg.copyWith(
fontWeight: FontWeight.bold,
color: AppColor.textPrimary,
),
),
],
),
],
),
);
}
Widget _buildWheelSection() {
return Stack(
alignment: Alignment.center,
children: [
// Glow Effect
AnimatedBuilder(
animation: _glowController,
builder: (context, child) => Container(
width: 340,
height: 340,
decoration: BoxDecoration(
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: AppColor.white.withOpacity(
0.3 + 0.2 * _glowController.value,
),
blurRadius: 40,
spreadRadius: 10,
),
],
),
), ),
), ),
// Spinning Wheel
AnimatedBuilder(
animation: isSpinning ? _rotationController : _idleRotationController,
builder: (context, child) {
double rotationAngle = isSpinning
? (_spinAnimation?.value ?? currentRotation)
: (currentRotation + (_idleRotationAnimation?.value ?? 0.0));
// Bottom Banner return Transform.rotate(
Container( angle: rotationAngle,
margin: const EdgeInsets.all(20), child: CustomPaint(
padding: const EdgeInsets.all(16), size: const Size(320, 320),
decoration: BoxDecoration( painter: WheelPainter(
gradient: LinearGradient( gamePrizes: gamePrizes,
colors: [AppColor.warning, AppColor.warning], getPrizeColor: _getPrizeColor,
), ),
borderRadius: BorderRadius.circular(15), ),
), );
child: Text( },
'Spin 30x lagi buat mainin spesial spin', ),
textAlign: TextAlign.center, // Spin Button
style: AppStyle.lg.copyWith( _buildSpinButton(),
color: AppColor.textWhite, // Pointer
fontWeight: FontWeight.bold, Positioned(
top: 30,
child: Container(
width: 0,
height: 0,
decoration: BoxDecoration(
border: Border(
left: BorderSide(width: 15, color: Colors.transparent),
right: BorderSide(width: 15, color: Colors.transparent),
bottom: BorderSide(width: 30, color: AppColor.error),
),
), ),
), ),
), ),
@@ -810,6 +688,73 @@ class _FerrisWheelPageState extends State<FerrisWheelPage>
); );
} }
Widget _buildSpinButton() {
return AnimatedBuilder(
animation: _pulseAnimation ?? const AlwaysStoppedAnimation(1.0),
builder: (context, child) => Transform.scale(
scale: _pulseAnimation?.value ?? 1.0,
child: Container(
width: 100,
height: 100,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [AppColor.warning, AppColor.warning],
),
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: AppColor.black.withOpacity(0.3),
blurRadius: 15,
offset: const Offset(0, 6),
),
BoxShadow(
color: AppColor.warning.withOpacity(0.5),
blurRadius: 20,
spreadRadius: (_pulseAnimation?.value ?? 1.0) * 5,
),
],
),
child: Material(
color: Colors.transparent,
child: InkWell(
borderRadius: BorderRadius.circular(50),
onTap: _spinWheel,
child: Center(
child: Text(
'SPIN',
style: AppStyle.lg.copyWith(
color: AppColor.textWhite,
fontWeight: FontWeight.bold,
letterSpacing: 1,
),
),
),
),
),
),
),
);
}
Widget _buildBottomBanner() {
return Container(
margin: const EdgeInsets.all(20),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
gradient: LinearGradient(colors: [AppColor.warning, AppColor.warning]),
borderRadius: BorderRadius.circular(15),
),
child: Text(
'Spin 30x lagi buat mainin spesial spin',
textAlign: TextAlign.center,
style: AppStyle.lg.copyWith(
color: AppColor.textWhite,
fontWeight: FontWeight.bold,
),
),
);
}
Widget _buildPrizeListContent() { Widget _buildPrizeListContent() {
return Container( return Container(
margin: const EdgeInsets.all(20), margin: const EdgeInsets.all(20),
@@ -892,17 +837,15 @@ class _FerrisWheelPageState extends State<FerrisWheelPage>
), ),
), ),
Text( Text(
'Nilai: ${prize.metadata['value'] ?? prize.weight}', 'ID: ${prize.id}',
style: AppStyle.sm.copyWith( style: AppStyle.sm.copyWith(
color: AppColor.textSecondary, color: AppColor.textSecondary,
), ),
), ),
Text( Text(
'Stok: ${prize.stock}/${prize.maxStock}', 'Game ID: ${prize.gameId}',
style: AppStyle.xs.copyWith( style: AppStyle.xs.copyWith(
color: prize.stock > 0 color: AppColor.textSecondary,
? AppColor.success
: AppColor.error,
), ),
), ),
], ],
@@ -921,7 +864,7 @@ class _FerrisWheelPageState extends State<FerrisWheelPage>
borderRadius: BorderRadius.circular(15), borderRadius: BorderRadius.circular(15),
), ),
child: Text( child: Text(
'Weight: ${prize.weight}', 'Hadiah',
style: AppStyle.xs.copyWith( style: AppStyle.xs.copyWith(
color: _getPrizeColor(prize, index), color: _getPrizeColor(prize, index),
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
@@ -0,0 +1,880 @@
import 'dart:math';
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import '../../../../common/theme/theme.dart';
@RoutePage()
class MisteryBoxPage extends StatefulWidget {
const MisteryBoxPage({super.key});
@override
State<MisteryBoxPage> createState() => _MisteryBoxPageState();
}
class _MisteryBoxPageState extends State<MisteryBoxPage>
with TickerProviderStateMixin {
int coins = 100;
int totalWins = 0;
List<Prize> prizes = [
Prize(
name: 'Voucher Rp 100K',
type: 'Voucher',
value: 100000,
rarity: 'Legendary',
chance: 5,
icon: '🎁',
),
Prize(
name: 'Cashback 50%',
type: 'Cashback',
value: 50,
rarity: 'Epic',
chance: 10,
icon: '💰',
),
Prize(
name: '500 Poin Reward',
type: 'Poin',
value: 500,
rarity: 'Rare',
chance: 15,
icon: '🏆',
),
Prize(
name: 'Voucher Rp 25K',
type: 'Voucher',
value: 25000,
rarity: 'Uncommon',
chance: 25,
icon: '🎫',
),
Prize(
name: '100 Poin Reward',
type: 'Poin',
value: 100,
rarity: 'Common',
chance: 30,
icon: '',
),
Prize(
name: 'Diskon 10%',
type: 'Diskon',
value: 10,
rarity: 'Common',
chance: 15,
icon: '🍕',
),
];
bool isOpening = false;
late AnimationController _shakeController;
late AnimationController _rotateController;
late Animation<double> _shakeAnimation;
late Animation<double> _rotateAnimation;
@override
void initState() {
super.initState();
_shakeController = AnimationController(
duration: const Duration(milliseconds: 600),
vsync: this,
);
_rotateController = AnimationController(
duration: const Duration(milliseconds: 600),
vsync: this,
);
_shakeAnimation = Tween<double>(begin: 0, end: 15).animate(
CurvedAnimation(parent: _shakeController, curve: Curves.elasticIn),
);
_rotateAnimation = Tween<double>(begin: 0, end: 2 * pi).animate(
CurvedAnimation(parent: _rotateController, curve: Curves.easeInOut),
);
}
@override
void dispose() {
_shakeController.dispose();
_rotateController.dispose();
super.dispose();
}
void openBox() async {
if (coins < 10 || isOpening) return;
setState(() {
isOpening = true;
coins -= 10;
});
// Animasi shake dan rotate
_shakeController.repeat(reverse: true);
_rotateController.forward();
await Future.delayed(const Duration(milliseconds: 1200));
_shakeController.stop();
_rotateController.reset();
// Generate prize
Prize prize = _generatePrize();
setState(() {
totalWins++;
isOpening = false;
});
// Show dialog
if (mounted) {
_showPrizeDialog(prize);
}
}
Prize _generatePrize() {
int random = Random().nextInt(100);
int cumulativeChance = 0;
for (var prize in prizes) {
cumulativeChance += prize.chance;
if (random < cumulativeChance) {
return prize;
}
}
return prizes.last;
}
void _showPrizeDialog(Prize prize) {
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => PrizeDialog(prize: prize),
);
}
Color _getRarityColor(String rarity) {
switch (rarity) {
case 'Legendary':
return const Color(0xFFFFD700);
case 'Epic':
return const Color(0xFFB429F9);
case 'Rare':
return const Color(0xFF3B82F6);
case 'Uncommon':
return const Color(0xFF10B981);
default:
return const Color(0xFF9CA3AF);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
AppColor.primary,
AppColor.primaryDark,
const Color(0xFF4A0000),
],
),
),
child: SafeArea(
child: Column(
children: [
_buildHeader(),
Expanded(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_buildMysteryBox(),
const SizedBox(height: 40),
_buildOpenButton(),
const SizedBox(height: 24),
_buildPrizeListButton(),
],
),
),
),
],
),
),
),
);
}
Widget _buildHeader() {
return Padding(
padding: const EdgeInsets.all(16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'🎁 Mystery Box',
style: AppStyle.h5.copyWith(
color: AppColor.white,
fontWeight: FontWeight.bold,
),
),
Text(
'Win Amazing Rewards!',
style: AppStyle.xs.copyWith(
color: AppColor.white.withOpacity(0.8),
),
),
],
),
),
Row(
children: [
_buildStatCard('🪙', coins.toString()),
const SizedBox(width: 8),
_buildStatCard('🏆', totalWins.toString()),
],
),
],
),
);
}
Widget _buildStatCard(String icon, String value) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration(
color: AppColor.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: AppColor.white.withOpacity(0.3), width: 1.5),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(icon, style: const TextStyle(fontSize: 16)),
const SizedBox(width: 6),
Text(
value,
style: AppStyle.md.copyWith(
color: AppColor.white,
fontWeight: FontWeight.bold,
),
),
],
),
);
}
Widget _buildMysteryBox() {
return AnimatedBuilder(
animation: Listenable.merge([_shakeAnimation, _rotateAnimation]),
builder: (context, child) {
return Transform.translate(
offset: Offset(
sin(_shakeAnimation.value) * 10,
cos(_shakeAnimation.value) * 5,
),
child: Transform.rotate(
angle: _rotateAnimation.value,
child: Container(
width: 200,
height: 200,
decoration: BoxDecoration(
color: AppColor.white,
borderRadius: BorderRadius.circular(24),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.3),
blurRadius: 30,
offset: const Offset(0, 15),
),
BoxShadow(
color: AppColor.warning.withOpacity(0.5),
blurRadius: 50,
spreadRadius: isOpening ? 8 : 0,
),
],
),
child: Stack(
children: [
Positioned(
top: 15,
left: 15,
child: Container(
width: 30,
height: 30,
decoration: BoxDecoration(
color: AppColor.warning.withOpacity(0.2),
shape: BoxShape.circle,
),
),
),
Positioned(
bottom: 20,
right: 20,
child: Container(
width: 25,
height: 25,
decoration: BoxDecoration(
color: AppColor.success.withOpacity(0.2),
shape: BoxShape.circle,
),
),
),
Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('🎁', style: TextStyle(fontSize: 70)),
const SizedBox(height: 8),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 4,
),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: AppColor.primaryGradient,
),
borderRadius: BorderRadius.circular(16),
),
child: Text(
'Mystery Box',
style: AppStyle.sm.copyWith(
color: AppColor.white,
fontWeight: FontWeight.bold,
),
),
),
],
),
),
if (isOpening) ...[
const Positioned(
top: 25,
right: 25,
child: Text('', style: TextStyle(fontSize: 24)),
),
const Positioned(
bottom: 30,
left: 25,
child: Text('', style: TextStyle(fontSize: 24)),
),
const Positioned(
top: 50,
left: 30,
child: Text('', style: TextStyle(fontSize: 20)),
),
],
],
),
),
),
);
},
);
}
Widget _buildOpenButton() {
bool canOpen = coins >= 10 && !isOpening;
return GestureDetector(
onTap: canOpen ? openBox : null,
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 16),
decoration: BoxDecoration(
color: canOpen ? AppColor.white : AppColor.white.withOpacity(0.3),
borderRadius: BorderRadius.circular(25),
boxShadow: canOpen
? [
BoxShadow(
color: AppColor.white.withOpacity(0.3),
blurRadius: 15,
offset: const Offset(0, 6),
),
]
: [],
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
isOpening ? Icons.refresh : Icons.card_giftcard,
color: canOpen ? AppColor.primary : AppColor.textLight,
size: 24,
),
const SizedBox(width: 10),
Text(
isOpening ? 'Membuka...' : 'Buka Box',
style: AppStyle.lg.copyWith(
color: canOpen ? AppColor.primary : AppColor.textLight,
fontWeight: FontWeight.bold,
),
),
const SizedBox(width: 8),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
decoration: BoxDecoration(
color: canOpen
? AppColor.warning.withOpacity(0.2)
: AppColor.textLight.withOpacity(0.2),
borderRadius: BorderRadius.circular(10),
),
child: Text(
'10 🪙',
style: AppStyle.xs.copyWith(
color: canOpen ? AppColor.warning : AppColor.textLight,
fontWeight: FontWeight.bold,
),
),
),
],
),
),
);
}
Widget _buildPrizeListButton() {
return GestureDetector(
onTap: () {
showModalBottomSheet(
context: context,
backgroundColor: Colors.transparent,
isScrollControlled: true,
builder: (context) => _buildPrizeListModal(),
);
},
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 12),
decoration: BoxDecoration(
color: AppColor.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(20),
border: Border.all(color: AppColor.white.withOpacity(0.3), width: 2),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.emoji_events, color: AppColor.white, size: 20),
const SizedBox(width: 8),
Text(
'Lihat Hadiah Tersedia',
style: AppStyle.sm.copyWith(
color: AppColor.white,
fontWeight: FontWeight.bold,
),
),
const SizedBox(width: 4),
const Icon(
Icons.arrow_forward_ios,
color: AppColor.white,
size: 14,
),
],
),
),
);
}
Widget _buildPrizeListModal() {
return Container(
height: MediaQuery.of(context).size.height * 0.7,
decoration: const BoxDecoration(
color: AppColor.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
),
child: Column(
children: [
// Handle bar
Container(
margin: const EdgeInsets.only(top: 12),
width: 40,
height: 4,
decoration: BoxDecoration(
color: AppColor.textLight,
borderRadius: BorderRadius.circular(2),
),
),
// Header
Padding(
padding: const EdgeInsets.all(20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Hadiah Tersedia',
style: AppStyle.h5.copyWith(
fontWeight: FontWeight.bold,
color: AppColor.textPrimary,
),
),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 4,
),
decoration: BoxDecoration(
color: AppColor.primary.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: Text(
'${prizes.length} Items',
style: AppStyle.xs.copyWith(
color: AppColor.primary,
fontWeight: FontWeight.bold,
),
),
),
],
),
),
// Prize Grid
Expanded(
child: GridView.builder(
padding: const EdgeInsets.symmetric(horizontal: 20),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 0.70,
crossAxisSpacing: 12,
mainAxisSpacing: 12,
),
itemCount: prizes.length,
itemBuilder: (context, index) {
final prize = prizes[index];
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
_getRarityColor(prize.rarity).withOpacity(0.1),
_getRarityColor(prize.rarity).withOpacity(0.05),
],
),
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: _getRarityColor(prize.rarity).withOpacity(0.4),
width: 2,
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(prize.icon, style: const TextStyle(fontSize: 40)),
const SizedBox(height: 12),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 4,
),
decoration: BoxDecoration(
color: _getRarityColor(prize.rarity),
borderRadius: BorderRadius.circular(12),
),
child: Text(
prize.rarity,
style: AppStyle.xs.copyWith(
color: AppColor.white,
fontWeight: FontWeight.bold,
fontSize: 9,
),
),
),
const SizedBox(height: 8),
Text(
prize.name,
style: AppStyle.sm.copyWith(
fontWeight: FontWeight.bold,
color: AppColor.textPrimary,
),
textAlign: TextAlign.center,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 3,
),
decoration: BoxDecoration(
color: AppColor.primary.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: Text(
prize.type,
style: AppStyle.xs.copyWith(
color: AppColor.primary,
fontWeight: FontWeight.w600,
fontSize: 10,
),
),
),
const SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 3,
),
decoration: BoxDecoration(
color: _getRarityColor(
prize.rarity,
).withOpacity(0.2),
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.percent,
size: 10,
color: _getRarityColor(prize.rarity),
),
const SizedBox(width: 2),
Text(
'${prize.chance}%',
style: AppStyle.xs.copyWith(
color: _getRarityColor(prize.rarity),
fontWeight: FontWeight.bold,
fontSize: 10,
),
),
],
),
),
],
),
],
),
);
},
),
),
const SizedBox(height: 20),
],
),
);
}
}
class PrizeDialog extends StatefulWidget {
final Prize prize;
const PrizeDialog({Key? key, required this.prize}) : super(key: key);
@override
State<PrizeDialog> createState() => _PrizeDialogState();
}
class _PrizeDialogState extends State<PrizeDialog>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _scaleAnimation;
late Animation<double> _fadeAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 500),
vsync: this,
);
_scaleAnimation = Tween<double>(
begin: 0.5,
end: 1.0,
).animate(CurvedAnimation(parent: _controller, curve: Curves.elasticOut));
_fadeAnimation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(CurvedAnimation(parent: _controller, curve: Curves.easeIn));
_controller.forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
Color _getRarityColor(String rarity) {
switch (rarity) {
case 'Legendary':
return const Color(0xFFFFD700);
case 'Epic':
return const Color(0xFFB429F9);
case 'Rare':
return const Color(0xFF3B82F6);
case 'Uncommon':
return const Color(0xFF10B981);
default:
return const Color(0xFF9CA3AF);
}
}
@override
Widget build(BuildContext context) {
return FadeTransition(
opacity: _fadeAnimation,
child: Dialog(
backgroundColor: Colors.transparent,
child: ScaleTransition(
scale: _scaleAnimation,
child: Container(
constraints: const BoxConstraints(maxWidth: 320),
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: AppColor.white,
borderRadius: BorderRadius.circular(24),
border: Border.all(
color: _getRarityColor(widget.prize.rarity),
width: 4,
),
boxShadow: [
BoxShadow(
color: _getRarityColor(widget.prize.rarity).withOpacity(0.5),
blurRadius: 30,
offset: const Offset(0, 10),
spreadRadius: 2,
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('🎉', style: const TextStyle(fontSize: 50)),
const SizedBox(height: 12),
Text(
'Selamat!',
style: AppStyle.h4.copyWith(
fontWeight: FontWeight.bold,
color: AppColor.textPrimary,
),
),
const SizedBox(height: 8),
Text(
'Kamu mendapatkan',
style: AppStyle.sm.copyWith(color: AppColor.textSecondary),
),
const SizedBox(height: 16),
Text(widget.prize.icon, style: const TextStyle(fontSize: 60)),
const SizedBox(height: 12),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 14,
vertical: 6,
),
decoration: BoxDecoration(
color: _getRarityColor(widget.prize.rarity),
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: _getRarityColor(
widget.prize.rarity,
).withOpacity(0.4),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: Text(
widget.prize.rarity.toUpperCase(),
style: AppStyle.xs.copyWith(
color: AppColor.white,
fontWeight: FontWeight.bold,
letterSpacing: 1.2,
),
),
),
const SizedBox(height: 16),
Text(
widget.prize.name,
style: AppStyle.h5.copyWith(
fontWeight: FontWeight.bold,
color: AppColor.textPrimary,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 8),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 4,
),
decoration: BoxDecoration(
color: AppColor.primary.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: Text(
widget.prize.type,
style: AppStyle.xs.copyWith(
color: AppColor.primary,
fontWeight: FontWeight.w600,
),
),
),
const SizedBox(height: 24),
GestureDetector(
onTap: () => Navigator.pop(context),
child: Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(vertical: 14),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: AppColor.primaryGradient,
),
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: AppColor.primary.withOpacity(0.4),
blurRadius: 12,
offset: const Offset(0, 4),
),
],
),
child: Text(
'Tutup',
style: AppStyle.md.copyWith(
color: AppColor.white,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
),
),
],
),
),
),
),
);
}
}
class Prize {
final String name;
final String type;
final int value;
final String rarity;
final int chance;
final String icon;
Prize({
required this.name,
required this.type,
required this.value,
required this.rarity,
required this.chance,
required this.icon,
});
}
@@ -0,0 +1,541 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'dart:math' as math;
import '../../../common/constant/app_constant.dart';
import '../../../common/theme/theme.dart';
@RoutePage()
class PointPage extends StatefulWidget {
const PointPage({super.key});
@override
State<PointPage> createState() => _PointPageState();
}
class _PointPageState extends State<PointPage> {
int _currentPage = 0;
final PageController _pageController = PageController(viewportFraction: 1.0);
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: SingleChildScrollView(
child: Column(
children: [
// Pink Header with overlapping elements
Stack(
clipBehavior: Clip.none,
children: [
// Pink Background
Container(
height: 320,
width: double.infinity,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
AppColor.primary.withOpacity(0.8),
AppColor.primary,
],
),
),
child: SafeArea(
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Back Button
Container(
width: 56,
height: 56,
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.3),
shape: BoxShape.circle,
),
child: IconButton(
icon: const Icon(
Icons.arrow_back,
color: Colors.white,
size: 28,
),
onPressed: () => Navigator.pop(context),
),
),
const SizedBox(height: 32),
// Title
Text(
'Kelola ${AppConstant.poinName} kamu!',
style: AppStyle.h3.copyWith(
color: AppColor.white,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
// Subtitle
Text(
'Bisa kumpulin dan tukar dari sini.',
style: AppStyle.md.copyWith(
color: AppColor.white.withOpacity(0.9),
),
),
],
),
),
),
),
// Point Card - Overlapping
Positioned(
top: 280,
left: 24,
right: 24,
child: Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: AppColor.white,
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 56,
height: 56,
decoration: BoxDecoration(
color: AppColor.primary,
shape: BoxShape.circle,
),
child: const Icon(
Icons.card_giftcard,
color: Colors.white,
size: 32,
),
),
const SizedBox(width: 16),
Text(
'50 ${AppConstant.poinName}',
style: AppStyle.h4.copyWith(
color: AppColor.primary,
fontWeight: FontWeight.bold,
),
),
],
),
),
),
],
),
// Black Section
const SizedBox(height: 100),
Container(
color: Colors.white,
width: double.infinity,
child: Column(
children: [
// Title Section
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24.0),
child: Column(
children: [
Text(
'Tukar ${AppConstant.poinName} buat seru-seruan',
style: AppStyle.h4.copyWith(
color: AppColor.textPrimary,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
],
),
),
const SizedBox(height: 40),
// PageView - Show 2 items per page
SizedBox(
height: 340,
child: PageView(
controller: _pageController,
onPageChanged: (index) {
setState(() {
_currentPage = index;
});
},
children: [
// Page 1 - 2 cards
Row(
children: [
Expanded(
child: _buildRewardCard(
title: 'Main Gift Arena\nsekarang!',
subtitle: 'Dapetin s.d. 1jt Coins!',
buttonText: 'Pakai 50 💎',
isWheel: true,
),
),
Expanded(
child: _buildRewardCard(
title: 'Putar untuk\nHarapan',
subtitle: 'GoPay Pet',
buttonText: 'Pakai 5 💎',
isWheel: false,
),
),
],
),
// Page 2 - 2 cards
Row(
children: [
Expanded(
child: _buildRewardCard(
title: 'Spin & Win\nHadiah!',
subtitle: 'Kesempatan menang besar!',
buttonText: 'Pakai 30 💎',
isWheel: true,
),
),
Expanded(
child: _buildRewardCard(
title: 'Lucky Draw\nBerhadiah',
subtitle: 'Coba keberuntungan!',
buttonText: 'Pakai 20 💎',
isWheel: false,
),
),
],
),
// Page 3 - 1 card
Row(
children: [
Expanded(
child: _buildRewardCard(
title: 'Mega Prize\nWheel',
subtitle: 'Hadiah hingga 10jt!',
buttonText: 'Pakai 100 💎',
isWheel: true,
),
),
const Spacer(), // Empty space for alignment
],
),
],
),
),
const SizedBox(height: 20),
// Indicators - 3 pages
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(3, (index) {
return AnimatedContainer(
duration: const Duration(milliseconds: 300),
margin: const EdgeInsets.symmetric(horizontal: 4),
width: _currentPage == index ? 28 : 8,
height: 8,
decoration: BoxDecoration(
color: _currentPage == index
? AppColor.primary
: Colors.grey.withOpacity(0.3),
borderRadius: BorderRadius.circular(4),
),
);
}),
),
const SizedBox(height: 40),
// Bottom Text
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24.0),
child: Column(
children: [
Text(
'Ada banyak cara dapet ${AppConstant.poinName}',
style: AppStyle.h5.copyWith(
color: AppColor.textPrimary,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 8),
Text(
'Mulai dari main game sampai nonton video.',
style: AppStyle.sm.copyWith(
color: AppColor.textLight,
),
textAlign: TextAlign.center,
),
],
),
),
const SizedBox(height: 60),
],
),
),
],
),
),
);
}
Widget _buildRewardCard({
required String title,
required String subtitle,
required String buttonText,
required bool isWheel,
}) {
return Padding(
padding: const EdgeInsets.only(left: 16),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 20),
decoration: BoxDecoration(
color: const Color(0xFF2D2D2D),
borderRadius: BorderRadius.circular(24),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Icon
Container(
width: 120,
height: 120,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: isWheel
? const Color(0xFF4FC3F7)
: const Color(0xFFFFC107),
boxShadow: [
BoxShadow(
color:
(isWheel
? const Color(0xFF4FC3F7)
: const Color(0xFFFFC107))
.withOpacity(0.3),
blurRadius: 20,
spreadRadius: 5,
),
],
),
child: Center(
child: Container(
width: 120,
height: 120,
decoration: const BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
),
child: isWheel
? CustomPaint(painter: WheelPainter())
: CustomPaint(painter: CoinPainter()),
),
),
),
const SizedBox(height: 20),
Text(
title,
style: AppStyle.md.copyWith(
color: AppColor.white,
fontWeight: FontWeight.bold,
height: 1.3,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 6),
Text(
subtitle,
style: AppStyle.sm.copyWith(color: AppColor.textLight),
textAlign: TextAlign.center,
),
const SizedBox(height: 18),
ElevatedButton(
onPressed: () {},
style: ElevatedButton.styleFrom(
backgroundColor: AppColor.primary,
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 12,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
elevation: 0,
),
child: Text(
buttonText,
style: AppStyle.sm.copyWith(
color: AppColor.white,
fontWeight: FontWeight.bold,
),
),
),
],
),
),
);
}
}
class WheelPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final center = Offset(size.width / 2, size.height / 2);
final radius = size.width / 2;
// Draw wheel segments
final paint = Paint()..style = PaintingStyle.fill;
const segments = 8;
final sweepAngle = (2 * math.pi) / segments;
for (int i = 0; i < segments; i++) {
paint.color = i.isEven
? const Color(0xFF2196F3)
: const Color(0xFF90CAF9);
canvas.drawArc(
Rect.fromCircle(center: center, radius: radius * 0.9),
i * sweepAngle,
sweepAngle,
true,
paint,
);
}
// Draw center circle
paint.color = const Color(0xFFFFEB3B);
canvas.drawCircle(center, radius * 0.25, paint);
// Draw pointer at top
final pointerPaint = Paint()
..color = const Color(0xFFE91E63)
..style = PaintingStyle.fill;
final pointerPath = Path();
pointerPath.moveTo(center.dx, radius * 0.15);
pointerPath.lineTo(center.dx - radius * 0.15, 0);
pointerPath.lineTo(center.dx + radius * 0.15, 0);
pointerPath.close();
canvas.drawPath(pointerPath, pointerPaint);
// Draw white border on pointer
final borderPaint = Paint()
..color = Colors.white
..style = PaintingStyle.stroke
..strokeWidth = 3;
canvas.drawPath(pointerPath, borderPaint);
// Draw dots around edge
paint.color = const Color(0xFF4FC3F7);
for (int i = 0; i < 12; i++) {
final angle = (i * 2 * math.pi) / 12;
final x = center.dx + radius * 0.95 * math.cos(angle);
final y = center.dy + radius * 0.95 * math.sin(angle);
canvas.drawCircle(Offset(x, y), 4, paint);
}
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
class CoinPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final center = Offset(size.width / 2, size.height / 2);
final radius = size.width / 2;
// Draw outer ring
final ringPaint = Paint()
..color = const Color(0xFFD4A418)
..style = PaintingStyle.stroke
..strokeWidth = 8;
canvas.drawCircle(center, radius * 0.85, ringPaint);
// Draw inner circle
final innerPaint = Paint()
..color = const Color(0xFFFFC107)
..style = PaintingStyle.fill;
canvas.drawCircle(center, radius * 0.75, innerPaint);
// Draw center ring symbol
final centerRingPaint = Paint()
..color = const Color(0xFFD4A418)
..style = PaintingStyle.stroke
..strokeWidth = 6;
canvas.drawCircle(center, radius * 0.35, centerRingPaint);
// Draw small colored dots around
final colors = [
Colors.red,
Colors.blue,
Colors.green,
Colors.purple,
Colors.orange,
Colors.pink,
];
for (int i = 0; i < 6; i++) {
final angle = (i * 2 * math.pi) / 6;
final x = center.dx + radius * 0.6 * math.cos(angle);
final y = center.dy + radius * 0.6 * math.sin(angle);
final dotPaint = Paint()
..color = colors[i]
..style = PaintingStyle.fill;
canvas.drawCircle(Offset(x, y), 6, dotPaint);
}
// Draw numbers around edge
final textPainter = TextPainter(
textDirection: TextDirection.ltr,
textAlign: TextAlign.center,
);
for (int i = 0; i < 12; i++) {
final angle = (i * 2 * math.pi) / 12 - math.pi / 2;
final x = center.dx + radius * 0.85 * math.cos(angle);
final y = center.dy + radius * 0.85 * math.sin(angle);
textPainter.text = TextSpan(
text: '${(i + 1) * 10}',
style: const TextStyle(
color: Color(0xFFD4A418),
fontSize: 10,
fontWeight: FontWeight.bold,
),
);
textPainter.layout();
textPainter.paint(
canvas,
Offset(x - textPainter.width / 2, y - textPainter.height / 2),
);
}
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
+13 -2
View File
@@ -35,8 +35,11 @@ class AppRouter extends RootStackRouter {
AutoRoute(page: MerchantDetailRoute.page), AutoRoute(page: MerchantDetailRoute.page),
// Point // Point
AutoRoute(page: PoinRoute.page), AutoRoute(page: PointRoute.page),
AutoRoute(page: PoinHistoryRoute.page),
// Coint
AutoRoute(page: CoinRoute.page),
AutoRoute(page: CoinHistoryRoute.page),
AutoRoute(page: ProductRedeemRoute.page), AutoRoute(page: ProductRedeemRoute.page),
// Draw // Draw
@@ -70,5 +73,13 @@ class AppRouter extends RootStackRouter {
// Mini Games // Mini Games
AutoRoute(page: FerrisWheelRoute.page), AutoRoute(page: FerrisWheelRoute.page),
AutoRoute(page: MisteryBoxRoute.page),
// Menu
AutoRoute(page: MenuRoute.page),
AutoRoute(page: MenuDetailRoute.page),
// Checkout
AutoRoute(page: CheckoutRoute.page),
]; ];
} }
File diff suppressed because it is too large Load Diff
+121
View File
@@ -0,0 +1,121 @@
// Models
class ProductCategory {
final String id;
final String name;
final String icon;
final int productCount;
ProductCategory({
required this.id,
required this.name,
required this.icon,
required this.productCount,
});
}
class Product {
final String id;
final String name;
final String description;
final int price;
final String categoryId;
final String imageUrl;
final bool isAvailable;
final double rating;
final int soldCount;
Product({
required this.id,
required this.name,
required this.description,
required this.price,
required this.categoryId,
required this.imageUrl,
required this.isAvailable,
required this.rating,
required this.soldCount,
});
}
// Sample data
final List<ProductCategory> categories = [
ProductCategory(id: "1", name: "Makanan", icon: "🍽️", productCount: 8),
ProductCategory(id: "2", name: "Minuman", icon: "🥤", productCount: 6),
ProductCategory(id: "3", name: "Snack", icon: "🍿", productCount: 5),
ProductCategory(id: "4", name: "Es Krim", icon: "🍦", productCount: 4),
ProductCategory(id: "5", name: "Paket", icon: "📦", productCount: 3),
];
final List<Product> products = [
// Makanan
Product(
id: "1",
name: "Nasi Gudeg",
description: "Gudeg khas Yogyakarta dengan ayam dan telur",
price: 25000,
categoryId: "1",
imageUrl: "https://via.placeholder.com/150",
isAvailable: true,
rating: 4.5,
soldCount: 50,
),
Product(
id: "2",
name: "Soto Ayam",
description: "Soto ayam kuning dengan nasi dan kerupuk",
price: 18000,
categoryId: "1",
imageUrl: "https://via.placeholder.com/150",
isAvailable: true,
rating: 4.3,
soldCount: 75,
),
Product(
id: "3",
name: "Gado-gado",
description: "Gado-gado segar dengan bumbu kacang",
price: 15000,
categoryId: "1",
imageUrl: "https://via.placeholder.com/150",
isAvailable: false,
rating: 4.2,
soldCount: 30,
),
// Minuman
Product(
id: "4",
name: "Es Teh Manis",
description: "Es teh manis segar",
price: 5000,
categoryId: "2",
imageUrl: "https://via.placeholder.com/150",
isAvailable: true,
rating: 4.0,
soldCount: 120,
),
Product(
id: "5",
name: "Jus Jeruk",
description: "Jus jeruk segar tanpa gula tambahan",
price: 12000,
categoryId: "2",
imageUrl: "https://via.placeholder.com/150",
isAvailable: true,
rating: 4.4,
soldCount: 45,
),
// Snack
Product(
id: "6",
name: "Keripik Pisang",
description: "Keripik pisang renyah dan manis",
price: 8000,
categoryId: "3",
imageUrl: "https://via.placeholder.com/150",
isAvailable: true,
rating: 4.1,
soldCount: 25,
),
];
+1 -1
View File
@@ -1,4 +1,4 @@
platform :osx, '10.14' platform :osx, '10.15'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency. # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true' ENV['COCOAPODS_DISABLE_STATS'] = 'true'
+3 -3
View File
@@ -461,7 +461,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.14; MACOSX_DEPLOYMENT_TARGET = 10.15;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx; SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule; SWIFT_COMPILATION_MODE = wholemodule;
@@ -543,7 +543,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.14; MACOSX_DEPLOYMENT_TARGET = 10.15;
MTL_ENABLE_DEBUG_INFO = YES; MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx; SDKROOT = macosx;
@@ -593,7 +593,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.14; MACOSX_DEPLOYMENT_TARGET = 10.15;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx; SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule; SWIFT_COMPILATION_MODE = wholemodule;
+12 -12
View File
@@ -668,26 +668,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: leak_tracker name: leak_tracker
sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "10.0.9" version: "11.0.2"
leak_tracker_flutter_testing: leak_tracker_flutter_testing:
dependency: transitive dependency: transitive
description: description:
name: leak_tracker_flutter_testing name: leak_tracker_flutter_testing
sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.9" version: "3.0.10"
leak_tracker_testing: leak_tracker_testing:
dependency: transitive dependency: transitive
description: description:
name: leak_tracker_testing name: leak_tracker_testing
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.1" version: "3.0.2"
lints: lints:
dependency: transitive dependency: transitive
description: description:
@@ -724,10 +724,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.16.0" version: "1.17.0"
mime: mime:
dependency: transitive dependency: transitive
description: description:
@@ -1121,10 +1121,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.7.4" version: "0.7.7"
time: time:
dependency: transitive dependency: transitive
description: description:
@@ -1249,10 +1249,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: vector_math name: vector_math
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.4" version: "2.2.0"
vm_service: vm_service:
dependency: transitive dependency: transitive
description: description: