This commit is contained in:
efrilm 2025-10-24 01:16:50 +07:00
parent 5d9197c986
commit ca5d2a58e7
45 changed files with 3820 additions and 50 deletions

View File

@ -13,6 +13,7 @@ analyzer:
sort_unnamed_constructors_first: ignore sort_unnamed_constructors_first: ignore
invalid_annotation_target: ignore invalid_annotation_target: ignore
use_build_context_synchronously: ignore use_build_context_synchronously: ignore
deprecated_member_use: ignore
exclude: exclude:
- test/generated/** - test/generated/**
- "**/**.g.dart" - "**/**.g.dart"

View File

@ -0,0 +1,62 @@
import 'dart:developer';
import 'package:bloc/bloc.dart';
import 'package:dartz/dartz.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:injectable/injectable.dart';
import '../../../domain/auth/auth.dart';
part 'login_form_event.dart';
part 'login_form_state.dart';
part 'login_form_bloc.freezed.dart';
@injectable
class LoginFormBloc extends Bloc<LoginFormEvent, LoginFormState> {
final IAuthRepository _authRepository;
LoginFormBloc(this._authRepository) : super(LoginFormState.initial()) {
on<LoginFormEvent>(_onLoginFormEvent);
}
Future<void> _onLoginFormEvent(
LoginFormEvent event,
Emitter<LoginFormState> emit,
) {
return event.map(
emailChanged: (e) async {
emit(state.copyWith(email: e.email, failureOrLoginOption: none()));
},
passwordChanged: (e) async {
emit(
state.copyWith(password: e.password, failureOrLoginOption: none()),
);
},
submitted: (e) async {
Either<AuthFailure, Login>? failureOrLogin;
emit(state.copyWith(isSubmitting: true, failureOrLoginOption: none()));
final emailValid = state.email.isNotEmpty;
final passwordValid = state.password.isNotEmpty;
log(
'emailValid: $emailValid, passwordValid: $passwordValid, email: ${state.email}, password: ${state.password}',
);
if (emailValid && passwordValid) {
failureOrLogin = await _authRepository.login(
email: state.email,
password: state.password,
);
emit(
state.copyWith(
isSubmitting: false,
failureOrLoginOption: optionOf(failureOrLogin),
),
);
}
emit(state.copyWith(showErrorMessages: true, isSubmitting: false));
},
);
}
}

View File

@ -0,0 +1,734 @@
// 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 'login_form_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 _$LoginFormEvent {
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function(String email) emailChanged,
required TResult Function(String password) passwordChanged,
required TResult Function() submitted,
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function(String email)? emailChanged,
TResult? Function(String password)? passwordChanged,
TResult? Function()? submitted,
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(String email)? emailChanged,
TResult Function(String password)? passwordChanged,
TResult Function()? submitted,
required TResult orElse(),
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_EmailChanged value) emailChanged,
required TResult Function(_PasswordChanged value) passwordChanged,
required TResult Function(_Submitted value) submitted,
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_EmailChanged value)? emailChanged,
TResult? Function(_PasswordChanged value)? passwordChanged,
TResult? Function(_Submitted value)? submitted,
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_EmailChanged value)? emailChanged,
TResult Function(_PasswordChanged value)? passwordChanged,
TResult Function(_Submitted value)? submitted,
required TResult orElse(),
}) => throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $LoginFormEventCopyWith<$Res> {
factory $LoginFormEventCopyWith(
LoginFormEvent value,
$Res Function(LoginFormEvent) then,
) = _$LoginFormEventCopyWithImpl<$Res, LoginFormEvent>;
}
/// @nodoc
class _$LoginFormEventCopyWithImpl<$Res, $Val extends LoginFormEvent>
implements $LoginFormEventCopyWith<$Res> {
_$LoginFormEventCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of LoginFormEvent
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
abstract class _$$EmailChangedImplCopyWith<$Res> {
factory _$$EmailChangedImplCopyWith(
_$EmailChangedImpl value,
$Res Function(_$EmailChangedImpl) then,
) = __$$EmailChangedImplCopyWithImpl<$Res>;
@useResult
$Res call({String email});
}
/// @nodoc
class __$$EmailChangedImplCopyWithImpl<$Res>
extends _$LoginFormEventCopyWithImpl<$Res, _$EmailChangedImpl>
implements _$$EmailChangedImplCopyWith<$Res> {
__$$EmailChangedImplCopyWithImpl(
_$EmailChangedImpl _value,
$Res Function(_$EmailChangedImpl) _then,
) : super(_value, _then);
/// Create a copy of LoginFormEvent
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({Object? email = null}) {
return _then(
_$EmailChangedImpl(
null == email
? _value.email
: email // ignore: cast_nullable_to_non_nullable
as String,
),
);
}
}
/// @nodoc
class _$EmailChangedImpl implements _EmailChanged {
const _$EmailChangedImpl(this.email);
@override
final String email;
@override
String toString() {
return 'LoginFormEvent.emailChanged(email: $email)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$EmailChangedImpl &&
(identical(other.email, email) || other.email == email));
}
@override
int get hashCode => Object.hash(runtimeType, email);
/// Create a copy of LoginFormEvent
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$EmailChangedImplCopyWith<_$EmailChangedImpl> get copyWith =>
__$$EmailChangedImplCopyWithImpl<_$EmailChangedImpl>(this, _$identity);
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function(String email) emailChanged,
required TResult Function(String password) passwordChanged,
required TResult Function() submitted,
}) {
return emailChanged(email);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function(String email)? emailChanged,
TResult? Function(String password)? passwordChanged,
TResult? Function()? submitted,
}) {
return emailChanged?.call(email);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(String email)? emailChanged,
TResult Function(String password)? passwordChanged,
TResult Function()? submitted,
required TResult orElse(),
}) {
if (emailChanged != null) {
return emailChanged(email);
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_EmailChanged value) emailChanged,
required TResult Function(_PasswordChanged value) passwordChanged,
required TResult Function(_Submitted value) submitted,
}) {
return emailChanged(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_EmailChanged value)? emailChanged,
TResult? Function(_PasswordChanged value)? passwordChanged,
TResult? Function(_Submitted value)? submitted,
}) {
return emailChanged?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_EmailChanged value)? emailChanged,
TResult Function(_PasswordChanged value)? passwordChanged,
TResult Function(_Submitted value)? submitted,
required TResult orElse(),
}) {
if (emailChanged != null) {
return emailChanged(this);
}
return orElse();
}
}
abstract class _EmailChanged implements LoginFormEvent {
const factory _EmailChanged(final String email) = _$EmailChangedImpl;
String get email;
/// Create a copy of LoginFormEvent
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
_$$EmailChangedImplCopyWith<_$EmailChangedImpl> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class _$$PasswordChangedImplCopyWith<$Res> {
factory _$$PasswordChangedImplCopyWith(
_$PasswordChangedImpl value,
$Res Function(_$PasswordChangedImpl) then,
) = __$$PasswordChangedImplCopyWithImpl<$Res>;
@useResult
$Res call({String password});
}
/// @nodoc
class __$$PasswordChangedImplCopyWithImpl<$Res>
extends _$LoginFormEventCopyWithImpl<$Res, _$PasswordChangedImpl>
implements _$$PasswordChangedImplCopyWith<$Res> {
__$$PasswordChangedImplCopyWithImpl(
_$PasswordChangedImpl _value,
$Res Function(_$PasswordChangedImpl) _then,
) : super(_value, _then);
/// Create a copy of LoginFormEvent
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({Object? password = null}) {
return _then(
_$PasswordChangedImpl(
null == password
? _value.password
: password // ignore: cast_nullable_to_non_nullable
as String,
),
);
}
}
/// @nodoc
class _$PasswordChangedImpl implements _PasswordChanged {
const _$PasswordChangedImpl(this.password);
@override
final String password;
@override
String toString() {
return 'LoginFormEvent.passwordChanged(password: $password)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$PasswordChangedImpl &&
(identical(other.password, password) ||
other.password == password));
}
@override
int get hashCode => Object.hash(runtimeType, password);
/// Create a copy of LoginFormEvent
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$PasswordChangedImplCopyWith<_$PasswordChangedImpl> get copyWith =>
__$$PasswordChangedImplCopyWithImpl<_$PasswordChangedImpl>(
this,
_$identity,
);
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function(String email) emailChanged,
required TResult Function(String password) passwordChanged,
required TResult Function() submitted,
}) {
return passwordChanged(password);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function(String email)? emailChanged,
TResult? Function(String password)? passwordChanged,
TResult? Function()? submitted,
}) {
return passwordChanged?.call(password);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(String email)? emailChanged,
TResult Function(String password)? passwordChanged,
TResult Function()? submitted,
required TResult orElse(),
}) {
if (passwordChanged != null) {
return passwordChanged(password);
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_EmailChanged value) emailChanged,
required TResult Function(_PasswordChanged value) passwordChanged,
required TResult Function(_Submitted value) submitted,
}) {
return passwordChanged(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_EmailChanged value)? emailChanged,
TResult? Function(_PasswordChanged value)? passwordChanged,
TResult? Function(_Submitted value)? submitted,
}) {
return passwordChanged?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_EmailChanged value)? emailChanged,
TResult Function(_PasswordChanged value)? passwordChanged,
TResult Function(_Submitted value)? submitted,
required TResult orElse(),
}) {
if (passwordChanged != null) {
return passwordChanged(this);
}
return orElse();
}
}
abstract class _PasswordChanged implements LoginFormEvent {
const factory _PasswordChanged(final String password) = _$PasswordChangedImpl;
String get password;
/// Create a copy of LoginFormEvent
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
_$$PasswordChangedImplCopyWith<_$PasswordChangedImpl> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class _$$SubmittedImplCopyWith<$Res> {
factory _$$SubmittedImplCopyWith(
_$SubmittedImpl value,
$Res Function(_$SubmittedImpl) then,
) = __$$SubmittedImplCopyWithImpl<$Res>;
}
/// @nodoc
class __$$SubmittedImplCopyWithImpl<$Res>
extends _$LoginFormEventCopyWithImpl<$Res, _$SubmittedImpl>
implements _$$SubmittedImplCopyWith<$Res> {
__$$SubmittedImplCopyWithImpl(
_$SubmittedImpl _value,
$Res Function(_$SubmittedImpl) _then,
) : super(_value, _then);
/// Create a copy of LoginFormEvent
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
class _$SubmittedImpl implements _Submitted {
const _$SubmittedImpl();
@override
String toString() {
return 'LoginFormEvent.submitted()';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType && other is _$SubmittedImpl);
}
@override
int get hashCode => runtimeType.hashCode;
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function(String email) emailChanged,
required TResult Function(String password) passwordChanged,
required TResult Function() submitted,
}) {
return submitted();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function(String email)? emailChanged,
TResult? Function(String password)? passwordChanged,
TResult? Function()? submitted,
}) {
return submitted?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(String email)? emailChanged,
TResult Function(String password)? passwordChanged,
TResult Function()? submitted,
required TResult orElse(),
}) {
if (submitted != null) {
return submitted();
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_EmailChanged value) emailChanged,
required TResult Function(_PasswordChanged value) passwordChanged,
required TResult Function(_Submitted value) submitted,
}) {
return submitted(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_EmailChanged value)? emailChanged,
TResult? Function(_PasswordChanged value)? passwordChanged,
TResult? Function(_Submitted value)? submitted,
}) {
return submitted?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_EmailChanged value)? emailChanged,
TResult Function(_PasswordChanged value)? passwordChanged,
TResult Function(_Submitted value)? submitted,
required TResult orElse(),
}) {
if (submitted != null) {
return submitted(this);
}
return orElse();
}
}
abstract class _Submitted implements LoginFormEvent {
const factory _Submitted() = _$SubmittedImpl;
}
/// @nodoc
mixin _$LoginFormState {
String get email => throw _privateConstructorUsedError;
String get password => throw _privateConstructorUsedError;
Option<Either<AuthFailure, Login>> get failureOrLoginOption =>
throw _privateConstructorUsedError;
bool get isSubmitting => throw _privateConstructorUsedError;
bool get showErrorMessages => throw _privateConstructorUsedError;
/// Create a copy of LoginFormState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$LoginFormStateCopyWith<LoginFormState> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $LoginFormStateCopyWith<$Res> {
factory $LoginFormStateCopyWith(
LoginFormState value,
$Res Function(LoginFormState) then,
) = _$LoginFormStateCopyWithImpl<$Res, LoginFormState>;
@useResult
$Res call({
String email,
String password,
Option<Either<AuthFailure, Login>> failureOrLoginOption,
bool isSubmitting,
bool showErrorMessages,
});
}
/// @nodoc
class _$LoginFormStateCopyWithImpl<$Res, $Val extends LoginFormState>
implements $LoginFormStateCopyWith<$Res> {
_$LoginFormStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of LoginFormState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? email = null,
Object? password = null,
Object? failureOrLoginOption = null,
Object? isSubmitting = null,
Object? showErrorMessages = null,
}) {
return _then(
_value.copyWith(
email: null == email
? _value.email
: email // ignore: cast_nullable_to_non_nullable
as String,
password: null == password
? _value.password
: password // ignore: cast_nullable_to_non_nullable
as String,
failureOrLoginOption: null == failureOrLoginOption
? _value.failureOrLoginOption
: failureOrLoginOption // ignore: cast_nullable_to_non_nullable
as Option<Either<AuthFailure, Login>>,
isSubmitting: null == isSubmitting
? _value.isSubmitting
: isSubmitting // ignore: cast_nullable_to_non_nullable
as bool,
showErrorMessages: null == showErrorMessages
? _value.showErrorMessages
: showErrorMessages // ignore: cast_nullable_to_non_nullable
as bool,
)
as $Val,
);
}
}
/// @nodoc
abstract class _$$LoginFormStateImplCopyWith<$Res>
implements $LoginFormStateCopyWith<$Res> {
factory _$$LoginFormStateImplCopyWith(
_$LoginFormStateImpl value,
$Res Function(_$LoginFormStateImpl) then,
) = __$$LoginFormStateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({
String email,
String password,
Option<Either<AuthFailure, Login>> failureOrLoginOption,
bool isSubmitting,
bool showErrorMessages,
});
}
/// @nodoc
class __$$LoginFormStateImplCopyWithImpl<$Res>
extends _$LoginFormStateCopyWithImpl<$Res, _$LoginFormStateImpl>
implements _$$LoginFormStateImplCopyWith<$Res> {
__$$LoginFormStateImplCopyWithImpl(
_$LoginFormStateImpl _value,
$Res Function(_$LoginFormStateImpl) _then,
) : super(_value, _then);
/// Create a copy of LoginFormState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? email = null,
Object? password = null,
Object? failureOrLoginOption = null,
Object? isSubmitting = null,
Object? showErrorMessages = null,
}) {
return _then(
_$LoginFormStateImpl(
email: null == email
? _value.email
: email // ignore: cast_nullable_to_non_nullable
as String,
password: null == password
? _value.password
: password // ignore: cast_nullable_to_non_nullable
as String,
failureOrLoginOption: null == failureOrLoginOption
? _value.failureOrLoginOption
: failureOrLoginOption // ignore: cast_nullable_to_non_nullable
as Option<Either<AuthFailure, Login>>,
isSubmitting: null == isSubmitting
? _value.isSubmitting
: isSubmitting // ignore: cast_nullable_to_non_nullable
as bool,
showErrorMessages: null == showErrorMessages
? _value.showErrorMessages
: showErrorMessages // ignore: cast_nullable_to_non_nullable
as bool,
),
);
}
}
/// @nodoc
class _$LoginFormStateImpl implements _LoginFormState {
const _$LoginFormStateImpl({
required this.email,
required this.password,
required this.failureOrLoginOption,
this.isSubmitting = false,
this.showErrorMessages = false,
});
@override
final String email;
@override
final String password;
@override
final Option<Either<AuthFailure, Login>> failureOrLoginOption;
@override
@JsonKey()
final bool isSubmitting;
@override
@JsonKey()
final bool showErrorMessages;
@override
String toString() {
return 'LoginFormState(email: $email, password: $password, failureOrLoginOption: $failureOrLoginOption, isSubmitting: $isSubmitting, showErrorMessages: $showErrorMessages)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$LoginFormStateImpl &&
(identical(other.email, email) || other.email == email) &&
(identical(other.password, password) ||
other.password == password) &&
(identical(other.failureOrLoginOption, failureOrLoginOption) ||
other.failureOrLoginOption == failureOrLoginOption) &&
(identical(other.isSubmitting, isSubmitting) ||
other.isSubmitting == isSubmitting) &&
(identical(other.showErrorMessages, showErrorMessages) ||
other.showErrorMessages == showErrorMessages));
}
@override
int get hashCode => Object.hash(
runtimeType,
email,
password,
failureOrLoginOption,
isSubmitting,
showErrorMessages,
);
/// Create a copy of LoginFormState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$LoginFormStateImplCopyWith<_$LoginFormStateImpl> get copyWith =>
__$$LoginFormStateImplCopyWithImpl<_$LoginFormStateImpl>(
this,
_$identity,
);
}
abstract class _LoginFormState implements LoginFormState {
const factory _LoginFormState({
required final String email,
required final String password,
required final Option<Either<AuthFailure, Login>> failureOrLoginOption,
final bool isSubmitting,
final bool showErrorMessages,
}) = _$LoginFormStateImpl;
@override
String get email;
@override
String get password;
@override
Option<Either<AuthFailure, Login>> get failureOrLoginOption;
@override
bool get isSubmitting;
@override
bool get showErrorMessages;
/// Create a copy of LoginFormState
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$LoginFormStateImplCopyWith<_$LoginFormStateImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@ -0,0 +1,9 @@
part of 'login_form_bloc.dart';
@freezed
class LoginFormEvent with _$LoginFormEvent {
const factory LoginFormEvent.emailChanged(String email) = _EmailChanged;
const factory LoginFormEvent.passwordChanged(String password) =
_PasswordChanged;
const factory LoginFormEvent.submitted() = _Submitted;
}

View File

@ -0,0 +1,15 @@
part of 'login_form_bloc.dart';
@freezed
class LoginFormState with _$LoginFormState {
const factory LoginFormState({
required String email,
required String password,
required Option<Either<AuthFailure, Login>> failureOrLoginOption,
@Default(false) bool isSubmitting,
@Default(false) bool showErrorMessages,
}) = _LoginFormState;
factory LoginFormState.initial() =>
LoginFormState(email: '', password: '', failureOrLoginOption: none());
}

View File

@ -28,6 +28,8 @@ class ApiClient {
ApiClient(this._dio, this._env) { ApiClient(this._dio, this._env) {
_dio.options.baseUrl = _env.baseUrl; _dio.options.baseUrl = _env.baseUrl;
_dio.options.connectTimeout = const Duration(seconds: 20); _dio.options.connectTimeout = const Duration(seconds: 20);
_dio.options.validateStatus = (status) =>
status != null && status >= 200 && status < 500;
_dio.interceptors.add(BadNetworkErrorInterceptor()); _dio.interceptors.add(BadNetworkErrorInterceptor());
_dio.interceptors.add(BadRequestErrorInterceptor()); _dio.interceptors.add(BadRequestErrorInterceptor());
_dio.interceptors.add(InternalServerErrorInterceptor()); _dio.interceptors.add(InternalServerErrorInterceptor());

View File

@ -1,3 +1,3 @@
class AppConstant { class AppConstant {
static const String appName = ""; static const String appName = "Apskel POS";
} }

View File

@ -0,0 +1,4 @@
class LocalStorageKey {
static const String token = 'token';
static const String user = 'user';
}

View File

@ -1,46 +1,66 @@
part of 'theme.dart'; part of 'theme.dart';
class AppColor { class AppColor {
/// primary = #3949AB // Primary Colors
static const Color primary = Color(0xff36175e); static const Color primary = Color(0xFF36175e);
static const Color primaryLight = Color(0xFF5a2d85);
static const Color primaryDark = Color(0xFF1e0d35);
/// grey = #B7B7B7 // Secondary Colors
static const Color grey = Color(0xffB7B7B7); static const Color secondary = Color(0xFF4CAF50);
static const Color secondaryLight = Color(0xFF81C784);
static const Color secondaryDark = Color(0xFF388E3C);
/// light = #F8F5FF // Background Colors
static const Color light = Color(0xffF8F5FF); static const Color background = Color(0xFFF8F9FA);
static const Color backgroundLight = Color(0xFFFFFFFF);
static const Color backgroundDark = Color(0xFF1A1A1A);
static const Color surface = Color(0xFFFFFFFF);
static const Color surfaceDark = Color(0xFF2D2D2D);
/// blueLight = #C7D0EB // Text Colors
static const Color blueLight = Color(0xffC7D0EB); static const Color textPrimary = Color(0xFF212121);
static const Color textSecondary = Color(0xFF757575);
static const Color textLight = Color(0xFFBDBDBD);
static const Color textWhite = Color(0xFFFFFFFF);
/// black = #000000 // Status Colors
static const Color black = Color(0xff000000); static const Color success = Color(0xFF4CAF50);
static const Color error = Color(0xFFE53E3E);
static const Color warning = Color(0xFFFF9800);
static const Color info = Color(0xFF2196F3);
/// white = #FFFFFF // Border Colors
static const Color white = Color(0xffFFFFFF); static const Color border = Color(0xFFE0E0E0);
static const Color whiteText = Color(0xfff1eaf9); static const Color borderLight = Color(0xFFF0F0F0);
static const Color borderDark = Color(0xFFBDBDBD);
/// green = #50C474 // Basic Color
static const Color green = Color(0xff50C474); static const Color white = Color(0xFFFFFFFF);
static const Color black = Color(0xFF000000);
/// red = #F4261A // Gradient Colors
static const Color red = Color(0xffF4261A); static const List<Color> primaryGradient = [
Color(0xFF36175e),
Color(0xFF5a2d85),
];
/// card = #E5E5E5 static const List<Color> successGradient = [
static const Color card = Color(0xffE5E5E5); Color(0xFF4CAF50),
Color(0xFF81C784),
];
/// disabled = #C8D1E1 static const List<Color> backgroundGradient = [
static const Color disabled = Color(0xffC8D1E1); Color(0xFFF5F5F5),
Color(0xFFE8E8E8),
];
/// subtitle = #7890CD // Opacity Variations
static const Color subtitle = Color(0xff7890CD); static Color primaryWithOpacity(double opacity) =>
primary.withOpacity(opacity);
/// stroke = #EFF0F6 static Color successWithOpacity(double opacity) =>
static const Color stroke = Color(0xffEFF0F6); success.withOpacity(opacity);
static Color errorWithOpacity(double opacity) => error.withOpacity(opacity);
static const Color background = Color.fromARGB(255, 241, 241, 241); static Color warningWithOpacity(double opacity) =>
warning.withOpacity(opacity);
static const Color primaryLight = Color(0xFF5A3E8A);
static const Color greyLight = Color(0xFFE0E0E0);
static const Color greyDark = Color(0xFF707070);
} }

View File

@ -1,5 +1,27 @@
part of 'theme.dart'; part of 'theme.dart';
class AppStyle { class AppStyle {
// TODO: define style static TextStyle xs = TextStyle(color: AppColor.black, fontSize: 11);
static TextStyle sm = TextStyle(color: AppColor.black, fontSize: 12);
static TextStyle md = TextStyle(color: AppColor.black, fontSize: 14);
static TextStyle lg = TextStyle(color: AppColor.black, fontSize: 16);
static TextStyle xl = TextStyle(color: AppColor.black, fontSize: 18);
static TextStyle xxl = TextStyle(color: AppColor.black, fontSize: 20);
static TextStyle h6 = TextStyle(color: AppColor.black, fontSize: 22);
static TextStyle h5 = TextStyle(color: AppColor.black, fontSize: 24);
static TextStyle h4 = TextStyle(color: AppColor.black, fontSize: 26);
static TextStyle h3 = TextStyle(color: AppColor.black, fontSize: 28);
static TextStyle h2 = TextStyle(color: AppColor.black, fontSize: 30);
static TextStyle h1 = TextStyle(color: AppColor.black, fontSize: 32);
} }

View File

@ -25,10 +25,10 @@ class ThemeApp {
colorScheme: ColorScheme.fromSeed(seedColor: AppColor.primary), colorScheme: ColorScheme.fromSeed(seedColor: AppColor.primary),
inputDecorationTheme: InputDecorationTheme( inputDecorationTheme: InputDecorationTheme(
contentPadding: const EdgeInsets.symmetric(horizontal: 16.0), contentPadding: const EdgeInsets.symmetric(horizontal: 16.0),
hintStyle: const TextStyle(color: AppColor.grey), hintStyle: const TextStyle(color: AppColor.textSecondary),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.0), borderRadius: BorderRadius.circular(8.0),
borderSide: BorderSide(color: AppColor.primary), borderSide: BorderSide(color: AppColor.borderDark),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.0), borderRadius: BorderRadius.circular(8.0),
@ -36,7 +36,11 @@ class ThemeApp {
), ),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.0), borderRadius: BorderRadius.circular(8.0),
borderSide: BorderSide(color: AppColor.primary), borderSide: BorderSide(color: AppColor.borderDark),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.0),
borderSide: BorderSide(color: AppColor.error),
), ),
), ),
); );

View File

@ -0,0 +1,3 @@
class ApiPath {
static const String login = '/api/v1/auth/login';
}

View File

@ -0,0 +1,28 @@
class AppValidator {
static String? validateEmail(String? value) {
if (value == null || value.isEmpty) {
return 'Email wajib diisi';
}
final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$');
if (!emailRegex.hasMatch(value)) {
return 'Format email tidak valid';
}
return null;
}
static String? validatePassword(String? value) {
if (value == null || value.isEmpty) {
return 'Password wajib diisi';
}
if (value.length < 8) {
return 'Password minimal 8 karakter';
}
// if (!RegExp(r'[A-Z]').hasMatch(value)) {
// return 'Password harus mengandung huruf besar';
// }
// if (!RegExp(r'[0-9]').hasMatch(value)) {
// return 'Password harus mengandung angka';
// }
return null;
}
}

11
lib/domain/auth/auth.dart Normal file
View File

@ -0,0 +1,11 @@
import 'package:dartz/dartz.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import '../../common/api/api_failure.dart';
part 'auth.freezed.dart';
part 'entities/login_entity.dart';
part 'entities/user_entity.dart';
part 'failures/auth_failure.dart';
part 'repositories/i_auth_repository.dart';

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,20 @@
part of '../auth.dart';
@freezed
class Login with _$Login {
const factory Login({
required String token,
required String refreshToken,
required String expiresAt,
required String refreshExpiresAt,
required User user,
}) = _Login;
factory Login.empty() => Login(
token: '',
refreshToken: '',
expiresAt: '',
refreshExpiresAt: '',
user: User.empty(),
);
}

View File

@ -0,0 +1,30 @@
part of '../auth.dart';
@freezed
class User with _$User {
const factory User({
required String id,
required String organizationId,
required String outletId,
required String name,
required String email,
required String role,
required Map<String, dynamic> permissions,
required bool isActive,
required String createdAt,
required String updatedAt,
}) = _User;
factory User.empty() => const User(
id: '',
organizationId: '',
outletId: '',
name: '',
email: '',
role: '',
permissions: {},
isActive: false,
createdAt: '',
updatedAt: '',
);
}

View File

@ -0,0 +1,9 @@
part of '../auth.dart';
@freezed
sealed class AuthFailure with _$AuthFailure {
const factory AuthFailure.serverError(ApiFailure failure) = _ServerError;
const factory AuthFailure.unexpectedError() = _UnexpectedError;
const factory AuthFailure.dynamicErrorMessage(String erroMessage) =
_DynamicErrorMessage;
}

View File

@ -0,0 +1,8 @@
part of '../auth.dart';
abstract class IAuthRepository {
Future<Either<AuthFailure, Login>> login({
required String email,
required String password,
});
}

View File

@ -9,12 +9,12 @@ abstract class Env {
@dev @dev
class DevEnv implements Env { class DevEnv implements Env {
@override @override
String get baseUrl => ''; // 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 => ''; String get baseUrl => 'https://api-pos.apskel.id';
} }

View File

@ -0,0 +1,9 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import '../../domain/auth/auth.dart';
part 'auth_dtos.freezed.dart';
part 'auth_dtos.g.dart';
part 'dto/login_dto.dart';
part 'dto/user_dto.dart';

View File

@ -0,0 +1,700 @@
// 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 'auth_dtos.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',
);
LoginDto _$LoginDtoFromJson(Map<String, dynamic> json) {
return _LoginDto.fromJson(json);
}
/// @nodoc
mixin _$LoginDto {
@JsonKey(name: "token")
String? get token => throw _privateConstructorUsedError;
@JsonKey(name: "refresh_token")
String? get refreshToken => throw _privateConstructorUsedError;
@JsonKey(name: "expires_at")
String? get expiresAt => throw _privateConstructorUsedError;
@JsonKey(name: "refresh_expires_at")
String? get refreshExpiresAt => throw _privateConstructorUsedError;
@JsonKey(name: "user")
UserDto? get user => throw _privateConstructorUsedError;
/// Serializes this LoginDto to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
/// Create a copy of LoginDto
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$LoginDtoCopyWith<LoginDto> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $LoginDtoCopyWith<$Res> {
factory $LoginDtoCopyWith(LoginDto value, $Res Function(LoginDto) then) =
_$LoginDtoCopyWithImpl<$Res, LoginDto>;
@useResult
$Res call({
@JsonKey(name: "token") String? token,
@JsonKey(name: "refresh_token") String? refreshToken,
@JsonKey(name: "expires_at") String? expiresAt,
@JsonKey(name: "refresh_expires_at") String? refreshExpiresAt,
@JsonKey(name: "user") UserDto? user,
});
$UserDtoCopyWith<$Res>? get user;
}
/// @nodoc
class _$LoginDtoCopyWithImpl<$Res, $Val extends LoginDto>
implements $LoginDtoCopyWith<$Res> {
_$LoginDtoCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of LoginDto
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? token = freezed,
Object? refreshToken = freezed,
Object? expiresAt = freezed,
Object? refreshExpiresAt = freezed,
Object? user = freezed,
}) {
return _then(
_value.copyWith(
token: freezed == token
? _value.token
: token // ignore: cast_nullable_to_non_nullable
as String?,
refreshToken: freezed == refreshToken
? _value.refreshToken
: refreshToken // ignore: cast_nullable_to_non_nullable
as String?,
expiresAt: freezed == expiresAt
? _value.expiresAt
: expiresAt // ignore: cast_nullable_to_non_nullable
as String?,
refreshExpiresAt: freezed == refreshExpiresAt
? _value.refreshExpiresAt
: refreshExpiresAt // ignore: cast_nullable_to_non_nullable
as String?,
user: freezed == user
? _value.user
: user // ignore: cast_nullable_to_non_nullable
as UserDto?,
)
as $Val,
);
}
/// Create a copy of LoginDto
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$UserDtoCopyWith<$Res>? get user {
if (_value.user == null) {
return null;
}
return $UserDtoCopyWith<$Res>(_value.user!, (value) {
return _then(_value.copyWith(user: value) as $Val);
});
}
}
/// @nodoc
abstract class _$$LoginDtoImplCopyWith<$Res>
implements $LoginDtoCopyWith<$Res> {
factory _$$LoginDtoImplCopyWith(
_$LoginDtoImpl value,
$Res Function(_$LoginDtoImpl) then,
) = __$$LoginDtoImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({
@JsonKey(name: "token") String? token,
@JsonKey(name: "refresh_token") String? refreshToken,
@JsonKey(name: "expires_at") String? expiresAt,
@JsonKey(name: "refresh_expires_at") String? refreshExpiresAt,
@JsonKey(name: "user") UserDto? user,
});
@override
$UserDtoCopyWith<$Res>? get user;
}
/// @nodoc
class __$$LoginDtoImplCopyWithImpl<$Res>
extends _$LoginDtoCopyWithImpl<$Res, _$LoginDtoImpl>
implements _$$LoginDtoImplCopyWith<$Res> {
__$$LoginDtoImplCopyWithImpl(
_$LoginDtoImpl _value,
$Res Function(_$LoginDtoImpl) _then,
) : super(_value, _then);
/// Create a copy of LoginDto
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? token = freezed,
Object? refreshToken = freezed,
Object? expiresAt = freezed,
Object? refreshExpiresAt = freezed,
Object? user = freezed,
}) {
return _then(
_$LoginDtoImpl(
token: freezed == token
? _value.token
: token // ignore: cast_nullable_to_non_nullable
as String?,
refreshToken: freezed == refreshToken
? _value.refreshToken
: refreshToken // ignore: cast_nullable_to_non_nullable
as String?,
expiresAt: freezed == expiresAt
? _value.expiresAt
: expiresAt // ignore: cast_nullable_to_non_nullable
as String?,
refreshExpiresAt: freezed == refreshExpiresAt
? _value.refreshExpiresAt
: refreshExpiresAt // ignore: cast_nullable_to_non_nullable
as String?,
user: freezed == user
? _value.user
: user // ignore: cast_nullable_to_non_nullable
as UserDto?,
),
);
}
}
/// @nodoc
@JsonSerializable()
class _$LoginDtoImpl extends _LoginDto {
const _$LoginDtoImpl({
@JsonKey(name: "token") this.token,
@JsonKey(name: "refresh_token") this.refreshToken,
@JsonKey(name: "expires_at") this.expiresAt,
@JsonKey(name: "refresh_expires_at") this.refreshExpiresAt,
@JsonKey(name: "user") this.user,
}) : super._();
factory _$LoginDtoImpl.fromJson(Map<String, dynamic> json) =>
_$$LoginDtoImplFromJson(json);
@override
@JsonKey(name: "token")
final String? token;
@override
@JsonKey(name: "refresh_token")
final String? refreshToken;
@override
@JsonKey(name: "expires_at")
final String? expiresAt;
@override
@JsonKey(name: "refresh_expires_at")
final String? refreshExpiresAt;
@override
@JsonKey(name: "user")
final UserDto? user;
@override
String toString() {
return 'LoginDto(token: $token, refreshToken: $refreshToken, expiresAt: $expiresAt, refreshExpiresAt: $refreshExpiresAt, user: $user)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$LoginDtoImpl &&
(identical(other.token, token) || other.token == token) &&
(identical(other.refreshToken, refreshToken) ||
other.refreshToken == refreshToken) &&
(identical(other.expiresAt, expiresAt) ||
other.expiresAt == expiresAt) &&
(identical(other.refreshExpiresAt, refreshExpiresAt) ||
other.refreshExpiresAt == refreshExpiresAt) &&
(identical(other.user, user) || other.user == user));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType,
token,
refreshToken,
expiresAt,
refreshExpiresAt,
user,
);
/// Create a copy of LoginDto
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$LoginDtoImplCopyWith<_$LoginDtoImpl> get copyWith =>
__$$LoginDtoImplCopyWithImpl<_$LoginDtoImpl>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$LoginDtoImplToJson(this);
}
}
abstract class _LoginDto extends LoginDto {
const factory _LoginDto({
@JsonKey(name: "token") final String? token,
@JsonKey(name: "refresh_token") final String? refreshToken,
@JsonKey(name: "expires_at") final String? expiresAt,
@JsonKey(name: "refresh_expires_at") final String? refreshExpiresAt,
@JsonKey(name: "user") final UserDto? user,
}) = _$LoginDtoImpl;
const _LoginDto._() : super._();
factory _LoginDto.fromJson(Map<String, dynamic> json) =
_$LoginDtoImpl.fromJson;
@override
@JsonKey(name: "token")
String? get token;
@override
@JsonKey(name: "refresh_token")
String? get refreshToken;
@override
@JsonKey(name: "expires_at")
String? get expiresAt;
@override
@JsonKey(name: "refresh_expires_at")
String? get refreshExpiresAt;
@override
@JsonKey(name: "user")
UserDto? get user;
/// Create a copy of LoginDto
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$LoginDtoImplCopyWith<_$LoginDtoImpl> get copyWith =>
throw _privateConstructorUsedError;
}
UserDto _$UserDtoFromJson(Map<String, dynamic> json) {
return _UserDto.fromJson(json);
}
/// @nodoc
mixin _$UserDto {
@JsonKey(name: "id")
String? get id => throw _privateConstructorUsedError;
@JsonKey(name: "organization_id")
String? get organizationId => throw _privateConstructorUsedError;
@JsonKey(name: "outlet_id")
String? get outletId => throw _privateConstructorUsedError;
@JsonKey(name: "name")
String? get name => throw _privateConstructorUsedError;
@JsonKey(name: "email")
String? get email => throw _privateConstructorUsedError;
@JsonKey(name: "role")
String? get role => throw _privateConstructorUsedError;
@JsonKey(name: "permissions")
Map<String, dynamic>? get permissions => throw _privateConstructorUsedError;
@JsonKey(name: "is_active")
bool? get isActive => throw _privateConstructorUsedError;
@JsonKey(name: "created_at")
String? get createdAt => throw _privateConstructorUsedError;
@JsonKey(name: "updated_at")
String? get updatedAt => throw _privateConstructorUsedError;
/// Serializes this UserDto to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
/// Create a copy of UserDto
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$UserDtoCopyWith<UserDto> get copyWith => throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $UserDtoCopyWith<$Res> {
factory $UserDtoCopyWith(UserDto value, $Res Function(UserDto) then) =
_$UserDtoCopyWithImpl<$Res, UserDto>;
@useResult
$Res call({
@JsonKey(name: "id") String? id,
@JsonKey(name: "organization_id") String? organizationId,
@JsonKey(name: "outlet_id") String? outletId,
@JsonKey(name: "name") String? name,
@JsonKey(name: "email") String? email,
@JsonKey(name: "role") String? role,
@JsonKey(name: "permissions") Map<String, dynamic>? permissions,
@JsonKey(name: "is_active") bool? isActive,
@JsonKey(name: "created_at") String? createdAt,
@JsonKey(name: "updated_at") String? updatedAt,
});
}
/// @nodoc
class _$UserDtoCopyWithImpl<$Res, $Val extends UserDto>
implements $UserDtoCopyWith<$Res> {
_$UserDtoCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of UserDto
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? id = freezed,
Object? organizationId = freezed,
Object? outletId = freezed,
Object? name = freezed,
Object? email = freezed,
Object? role = freezed,
Object? permissions = freezed,
Object? isActive = freezed,
Object? createdAt = freezed,
Object? updatedAt = freezed,
}) {
return _then(
_value.copyWith(
id: freezed == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as String?,
organizationId: freezed == organizationId
? _value.organizationId
: organizationId // ignore: cast_nullable_to_non_nullable
as String?,
outletId: freezed == outletId
? _value.outletId
: outletId // ignore: cast_nullable_to_non_nullable
as String?,
name: freezed == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String?,
email: freezed == email
? _value.email
: email // ignore: cast_nullable_to_non_nullable
as String?,
role: freezed == role
? _value.role
: role // ignore: cast_nullable_to_non_nullable
as String?,
permissions: freezed == permissions
? _value.permissions
: permissions // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>?,
isActive: freezed == isActive
? _value.isActive
: isActive // ignore: cast_nullable_to_non_nullable
as bool?,
createdAt: freezed == createdAt
? _value.createdAt
: createdAt // ignore: cast_nullable_to_non_nullable
as String?,
updatedAt: freezed == updatedAt
? _value.updatedAt
: updatedAt // ignore: cast_nullable_to_non_nullable
as String?,
)
as $Val,
);
}
}
/// @nodoc
abstract class _$$UserDtoImplCopyWith<$Res> implements $UserDtoCopyWith<$Res> {
factory _$$UserDtoImplCopyWith(
_$UserDtoImpl value,
$Res Function(_$UserDtoImpl) then,
) = __$$UserDtoImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({
@JsonKey(name: "id") String? id,
@JsonKey(name: "organization_id") String? organizationId,
@JsonKey(name: "outlet_id") String? outletId,
@JsonKey(name: "name") String? name,
@JsonKey(name: "email") String? email,
@JsonKey(name: "role") String? role,
@JsonKey(name: "permissions") Map<String, dynamic>? permissions,
@JsonKey(name: "is_active") bool? isActive,
@JsonKey(name: "created_at") String? createdAt,
@JsonKey(name: "updated_at") String? updatedAt,
});
}
/// @nodoc
class __$$UserDtoImplCopyWithImpl<$Res>
extends _$UserDtoCopyWithImpl<$Res, _$UserDtoImpl>
implements _$$UserDtoImplCopyWith<$Res> {
__$$UserDtoImplCopyWithImpl(
_$UserDtoImpl _value,
$Res Function(_$UserDtoImpl) _then,
) : super(_value, _then);
/// Create a copy of UserDto
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? id = freezed,
Object? organizationId = freezed,
Object? outletId = freezed,
Object? name = freezed,
Object? email = freezed,
Object? role = freezed,
Object? permissions = freezed,
Object? isActive = freezed,
Object? createdAt = freezed,
Object? updatedAt = freezed,
}) {
return _then(
_$UserDtoImpl(
id: freezed == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as String?,
organizationId: freezed == organizationId
? _value.organizationId
: organizationId // ignore: cast_nullable_to_non_nullable
as String?,
outletId: freezed == outletId
? _value.outletId
: outletId // ignore: cast_nullable_to_non_nullable
as String?,
name: freezed == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String?,
email: freezed == email
? _value.email
: email // ignore: cast_nullable_to_non_nullable
as String?,
role: freezed == role
? _value.role
: role // ignore: cast_nullable_to_non_nullable
as String?,
permissions: freezed == permissions
? _value._permissions
: permissions // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>?,
isActive: freezed == isActive
? _value.isActive
: isActive // ignore: cast_nullable_to_non_nullable
as bool?,
createdAt: freezed == createdAt
? _value.createdAt
: createdAt // ignore: cast_nullable_to_non_nullable
as String?,
updatedAt: freezed == updatedAt
? _value.updatedAt
: updatedAt // ignore: cast_nullable_to_non_nullable
as String?,
),
);
}
}
/// @nodoc
@JsonSerializable()
class _$UserDtoImpl extends _UserDto {
const _$UserDtoImpl({
@JsonKey(name: "id") this.id,
@JsonKey(name: "organization_id") this.organizationId,
@JsonKey(name: "outlet_id") this.outletId,
@JsonKey(name: "name") this.name,
@JsonKey(name: "email") this.email,
@JsonKey(name: "role") this.role,
@JsonKey(name: "permissions") final Map<String, dynamic>? permissions,
@JsonKey(name: "is_active") this.isActive,
@JsonKey(name: "created_at") this.createdAt,
@JsonKey(name: "updated_at") this.updatedAt,
}) : _permissions = permissions,
super._();
factory _$UserDtoImpl.fromJson(Map<String, dynamic> json) =>
_$$UserDtoImplFromJson(json);
@override
@JsonKey(name: "id")
final String? id;
@override
@JsonKey(name: "organization_id")
final String? organizationId;
@override
@JsonKey(name: "outlet_id")
final String? outletId;
@override
@JsonKey(name: "name")
final String? name;
@override
@JsonKey(name: "email")
final String? email;
@override
@JsonKey(name: "role")
final String? role;
final Map<String, dynamic>? _permissions;
@override
@JsonKey(name: "permissions")
Map<String, dynamic>? get permissions {
final value = _permissions;
if (value == null) return null;
if (_permissions is EqualUnmodifiableMapView) return _permissions;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(value);
}
@override
@JsonKey(name: "is_active")
final bool? isActive;
@override
@JsonKey(name: "created_at")
final String? createdAt;
@override
@JsonKey(name: "updated_at")
final String? updatedAt;
@override
String toString() {
return 'UserDto(id: $id, organizationId: $organizationId, outletId: $outletId, name: $name, email: $email, role: $role, permissions: $permissions, isActive: $isActive, createdAt: $createdAt, updatedAt: $updatedAt)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$UserDtoImpl &&
(identical(other.id, id) || other.id == id) &&
(identical(other.organizationId, organizationId) ||
other.organizationId == organizationId) &&
(identical(other.outletId, outletId) ||
other.outletId == outletId) &&
(identical(other.name, name) || other.name == name) &&
(identical(other.email, email) || other.email == email) &&
(identical(other.role, role) || other.role == role) &&
const DeepCollectionEquality().equals(
other._permissions,
_permissions,
) &&
(identical(other.isActive, isActive) ||
other.isActive == isActive) &&
(identical(other.createdAt, createdAt) ||
other.createdAt == createdAt) &&
(identical(other.updatedAt, updatedAt) ||
other.updatedAt == updatedAt));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType,
id,
organizationId,
outletId,
name,
email,
role,
const DeepCollectionEquality().hash(_permissions),
isActive,
createdAt,
updatedAt,
);
/// Create a copy of UserDto
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$UserDtoImplCopyWith<_$UserDtoImpl> get copyWith =>
__$$UserDtoImplCopyWithImpl<_$UserDtoImpl>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$UserDtoImplToJson(this);
}
}
abstract class _UserDto extends UserDto {
const factory _UserDto({
@JsonKey(name: "id") final String? id,
@JsonKey(name: "organization_id") final String? organizationId,
@JsonKey(name: "outlet_id") final String? outletId,
@JsonKey(name: "name") final String? name,
@JsonKey(name: "email") final String? email,
@JsonKey(name: "role") final String? role,
@JsonKey(name: "permissions") final Map<String, dynamic>? permissions,
@JsonKey(name: "is_active") final bool? isActive,
@JsonKey(name: "created_at") final String? createdAt,
@JsonKey(name: "updated_at") final String? updatedAt,
}) = _$UserDtoImpl;
const _UserDto._() : super._();
factory _UserDto.fromJson(Map<String, dynamic> json) = _$UserDtoImpl.fromJson;
@override
@JsonKey(name: "id")
String? get id;
@override
@JsonKey(name: "organization_id")
String? get organizationId;
@override
@JsonKey(name: "outlet_id")
String? get outletId;
@override
@JsonKey(name: "name")
String? get name;
@override
@JsonKey(name: "email")
String? get email;
@override
@JsonKey(name: "role")
String? get role;
@override
@JsonKey(name: "permissions")
Map<String, dynamic>? get permissions;
@override
@JsonKey(name: "is_active")
bool? get isActive;
@override
@JsonKey(name: "created_at")
String? get createdAt;
@override
@JsonKey(name: "updated_at")
String? get updatedAt;
/// Create a copy of UserDto
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$UserDtoImplCopyWith<_$UserDtoImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@ -0,0 +1,55 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'auth_dtos.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_$LoginDtoImpl _$$LoginDtoImplFromJson(Map<String, dynamic> json) =>
_$LoginDtoImpl(
token: json['token'] as String?,
refreshToken: json['refresh_token'] as String?,
expiresAt: json['expires_at'] as String?,
refreshExpiresAt: json['refresh_expires_at'] as String?,
user: json['user'] == null
? null
: UserDto.fromJson(json['user'] as Map<String, dynamic>),
);
Map<String, dynamic> _$$LoginDtoImplToJson(_$LoginDtoImpl instance) =>
<String, dynamic>{
'token': instance.token,
'refresh_token': instance.refreshToken,
'expires_at': instance.expiresAt,
'refresh_expires_at': instance.refreshExpiresAt,
'user': instance.user,
};
_$UserDtoImpl _$$UserDtoImplFromJson(Map<String, dynamic> json) =>
_$UserDtoImpl(
id: json['id'] as String?,
organizationId: json['organization_id'] as String?,
outletId: json['outlet_id'] as String?,
name: json['name'] as String?,
email: json['email'] as String?,
role: json['role'] as String?,
permissions: json['permissions'] as Map<String, dynamic>?,
isActive: json['is_active'] as bool?,
createdAt: json['created_at'] as String?,
updatedAt: json['updated_at'] as String?,
);
Map<String, dynamic> _$$UserDtoImplToJson(_$UserDtoImpl instance) =>
<String, dynamic>{
'id': instance.id,
'organization_id': instance.organizationId,
'outlet_id': instance.outletId,
'name': instance.name,
'email': instance.email,
'role': instance.role,
'permissions': instance.permissions,
'is_active': instance.isActive,
'created_at': instance.createdAt,
'updated_at': instance.updatedAt,
};

View File

@ -0,0 +1,60 @@
import 'dart:convert';
import 'dart:developer';
import 'package:injectable/injectable.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../../common/constant/local_storage_key.dart';
import '../../../domain/auth/auth.dart';
import '../auth_dtos.dart';
@injectable
class AuthLocalDataProvider {
final SharedPreferences _sharedPreferences;
final String _logName = 'AuthLocalDataProvider';
AuthLocalDataProvider(this._sharedPreferences);
Future<void> saveToken(String token) async {
await _sharedPreferences.setString(LocalStorageKey.token, token);
}
Future<String?> getToken() async {
return _sharedPreferences.getString(LocalStorageKey.token);
}
Future<void> deleteToken() async {
await _sharedPreferences.remove(LocalStorageKey.token);
}
Future<bool> hasToken() async {
return _sharedPreferences.containsKey(LocalStorageKey.token);
}
Future<void> saveCurrentUser(UserDto user) async {
final userJsonString = jsonEncode(user.toJson());
await _sharedPreferences.setString(LocalStorageKey.user, userJsonString);
}
Future<User> currentUser() async {
final userString = _sharedPreferences.getString(LocalStorageKey.user);
if (userString == null) return User.empty();
final Map<String, dynamic> userMap = jsonDecode(userString);
final userDto = UserDto.fromJson(userMap);
return userDto.toDomain();
}
Future<void> deleteCurrentUser() async {
await _sharedPreferences.remove(LocalStorageKey.user);
}
Future<void> deleteAllAuth() async {
try {
await _sharedPreferences.remove(LocalStorageKey.token);
await _sharedPreferences.remove(LocalStorageKey.user);
} catch (e) {
log('deleteAllAuthError', name: _logName, error: e);
}
}
}

View File

@ -0,0 +1,44 @@
import 'dart:developer';
import 'package:data_channel/data_channel.dart';
import 'package:injectable/injectable.dart';
import '../../../common/api/api_client.dart';
import '../../../common/api/api_failure.dart';
import '../../../common/url/api_path.dart';
import '../../../domain/auth/auth.dart';
import '../auth_dtos.dart';
@injectable
class AuthRemoteDataProvider {
final ApiClient _apiClient;
final String _logName = 'AuthRemoteDataProvider';
AuthRemoteDataProvider(this._apiClient);
Future<DC<AuthFailure, LoginDto>> login({
required String email,
required String password,
}) async {
try {
final response = await _apiClient.post(
ApiPath.login,
data: {'email': email, 'password': password},
);
if (response.data['code'] == 401) {
return DC.error(
AuthFailure.serverError(
ApiFailure.unauthorized('Incorrect email or password'),
),
);
}
final dto = LoginDto.fromJson(response.data['data']);
return DC.data(dto);
} on ApiFailure catch (e, s) {
log('login', name: _logName, error: e, stackTrace: s);
return DC.error(AuthFailure.serverError(e));
}
}
}

View File

@ -0,0 +1,39 @@
part of '../auth_dtos.dart';
@freezed
class LoginDto with _$LoginDto {
const LoginDto._();
const factory LoginDto({
@JsonKey(name: "token") String? token,
@JsonKey(name: "refresh_token") String? refreshToken,
@JsonKey(name: "expires_at") String? expiresAt,
@JsonKey(name: "refresh_expires_at") String? refreshExpiresAt,
@JsonKey(name: "user") UserDto? user,
}) = _LoginDto;
factory LoginDto.fromJson(Map<String, dynamic> json) =>
_$LoginDtoFromJson(json);
/// mapping ke domain
Login toDomain() => Login(
token: token ?? '',
refreshToken: refreshToken ?? '',
expiresAt: expiresAt ?? '',
refreshExpiresAt: refreshExpiresAt ?? '',
user:
user?.toDomain() ??
User(
id: '',
organizationId: '',
outletId: '',
name: '',
email: '',
role: '',
permissions: {},
isActive: false,
createdAt: '',
updatedAt: '',
),
);
}

View File

@ -0,0 +1,35 @@
part of '../auth_dtos.dart';
@freezed
class UserDto with _$UserDto {
const UserDto._();
const factory UserDto({
@JsonKey(name: "id") String? id,
@JsonKey(name: "organization_id") String? organizationId,
@JsonKey(name: "outlet_id") String? outletId,
@JsonKey(name: "name") String? name,
@JsonKey(name: "email") String? email,
@JsonKey(name: "role") String? role,
@JsonKey(name: "permissions") Map<String, dynamic>? permissions,
@JsonKey(name: "is_active") bool? isActive,
@JsonKey(name: "created_at") String? createdAt,
@JsonKey(name: "updated_at") String? updatedAt,
}) = _UserDto;
factory UserDto.fromJson(Map<String, dynamic> json) =>
_$UserDtoFromJson(json);
User toDomain() => User(
id: id ?? '',
organizationId: organizationId ?? '',
outletId: outletId ?? '',
name: name ?? '',
email: email ?? '',
role: role ?? '',
permissions: permissions ?? {},
isActive: isActive ?? false,
createdAt: createdAt ?? '',
updatedAt: updatedAt ?? '',
);
}

View File

@ -0,0 +1,44 @@
import 'dart:developer';
import 'package:dartz/dartz.dart';
import 'package:injectable/injectable.dart';
import '../../../domain/auth/auth.dart';
import '../datasources/local_data_provider.dart';
import '../datasources/remote_data_provider.dart';
@Injectable(as: IAuthRepository)
class AuthRepository implements IAuthRepository {
final AuthRemoteDataProvider _dataProvider;
final AuthLocalDataProvider _localDataProvider;
final String _logName = 'AuthRepository';
AuthRepository(this._dataProvider, this._localDataProvider);
@override
Future<Either<AuthFailure, Login>> login({
required String email,
required String password,
}) async {
try {
final result = await _dataProvider.login(
email: email,
password: password,
);
if (result.hasError) {
return left(result.error!);
}
final auth = result.data!.toDomain();
await _localDataProvider.saveToken(auth.token);
await _localDataProvider.saveCurrentUser(result.data!.user!);
return right(auth);
} catch (e, s) {
log('loginError', name: _logName, error: e, stackTrace: s);
return left(const AuthFailure.unexpectedError());
}
}
}

View File

@ -9,6 +9,8 @@
// coverage:ignore-file // coverage:ignore-file
// ignore_for_file: no_leading_underscores_for_library_prefixes // ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:apskel_pos_flutter_v2/application/auth/bloc/login_form_bloc.dart'
as _i185;
import 'package:apskel_pos_flutter_v2/common/api/api_client.dart' as _i457; import 'package:apskel_pos_flutter_v2/common/api/api_client.dart' as _i457;
import 'package:apskel_pos_flutter_v2/common/di/di_auto_route.dart' as _i729; import 'package:apskel_pos_flutter_v2/common/di/di_auto_route.dart' as _i729;
import 'package:apskel_pos_flutter_v2/common/di/di_connectivity.dart' as _i807; import 'package:apskel_pos_flutter_v2/common/di/di_connectivity.dart' as _i807;
@ -17,7 +19,14 @@ import 'package:apskel_pos_flutter_v2/common/di/di_shared_preferences.dart'
as _i135; as _i135;
import 'package:apskel_pos_flutter_v2/common/network/network_client.dart' import 'package:apskel_pos_flutter_v2/common/network/network_client.dart'
as _i171; as _i171;
import 'package:apskel_pos_flutter_v2/domain/auth/auth.dart' as _i776;
import 'package:apskel_pos_flutter_v2/env.dart' as _i923; import 'package:apskel_pos_flutter_v2/env.dart' as _i923;
import 'package:apskel_pos_flutter_v2/infrastructure/auth/datasources/local_data_provider.dart'
as _i204;
import 'package:apskel_pos_flutter_v2/infrastructure/auth/datasources/remote_data_provider.dart'
as _i370;
import 'package:apskel_pos_flutter_v2/infrastructure/auth/repositories/auth_repository.dart'
as _i941;
import 'package:apskel_pos_flutter_v2/presentation/router/app_router.dart' import 'package:apskel_pos_flutter_v2/presentation/router/app_router.dart'
as _i800; as _i800;
import 'package:connectivity_plus/connectivity_plus.dart' as _i895; import 'package:connectivity_plus/connectivity_plus.dart' as _i895;
@ -51,10 +60,25 @@ extension GetItInjectableX on _i174.GetIt {
() => _i171.NetworkClient(gh<_i895.Connectivity>()), () => _i171.NetworkClient(gh<_i895.Connectivity>()),
); );
gh.factory<_i923.Env>(() => _i923.DevEnv(), registerFor: {_dev}); gh.factory<_i923.Env>(() => _i923.DevEnv(), registerFor: {_dev});
gh.factory<_i204.AuthLocalDataProvider>(
() => _i204.AuthLocalDataProvider(gh<_i460.SharedPreferences>()),
);
gh.lazySingleton<_i457.ApiClient>( gh.lazySingleton<_i457.ApiClient>(
() => _i457.ApiClient(gh<_i361.Dio>(), gh<_i923.Env>()), () => _i457.ApiClient(gh<_i361.Dio>(), gh<_i923.Env>()),
); );
gh.factory<_i923.Env>(() => _i923.ProdEnv(), registerFor: {_prod}); gh.factory<_i923.Env>(() => _i923.ProdEnv(), registerFor: {_prod});
gh.factory<_i370.AuthRemoteDataProvider>(
() => _i370.AuthRemoteDataProvider(gh<_i457.ApiClient>()),
);
gh.factory<_i776.IAuthRepository>(
() => _i941.AuthRepository(
gh<_i370.AuthRemoteDataProvider>(),
gh<_i204.AuthLocalDataProvider>(),
),
);
gh.factory<_i185.LoginFormBloc>(
() => _i185.LoginFormBloc(gh<_i776.IAuthRepository>()),
);
return this; return this;
} }
} }

View File

@ -34,6 +34,11 @@ void main() async {
), ),
); );
await SystemChrome.setPreferredOrientations([
DeviceOrientation.landscapeLeft,
DeviceOrientation.landscapeRight,
]);
if (kReleaseMode) { if (kReleaseMode) {
debugPrint = (message, {wrapWidth}) => ''; debugPrint = (message, {wrapWidth}) => '';
} }

View File

@ -0,0 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
import '../../../common/theme/theme.dart';
import '../spaces/space.dart';
part 'elevated_button.dart';

View File

@ -0,0 +1,180 @@
part of 'button.dart';
enum ButtonStyle { filled, outlined }
class AppElevatedButton extends StatelessWidget {
const AppElevatedButton.filled({
super.key,
required this.onPressed,
required this.label,
this.style = ButtonStyle.filled,
this.color = AppColor.primary,
this.textColor = Colors.white,
this.width,
this.height = 40.0,
this.borderRadius = 16.0,
this.icon,
this.disabled = false,
this.fontSize = 16.0,
this.elevation,
this.labelStyle,
this.mainAxisAlignment = MainAxisAlignment.center,
this.crossAxisAlignment = CrossAxisAlignment.center,
this.isLoading = false,
});
const AppElevatedButton.outlined({
super.key,
required this.onPressed,
required this.label,
this.style = ButtonStyle.outlined,
this.color = Colors.transparent,
this.textColor = AppColor.primary,
this.width,
this.height = 40.0,
this.borderRadius = 16.0,
this.icon,
this.disabled = false,
this.fontSize = 16.0,
this.elevation,
this.labelStyle,
this.mainAxisAlignment = MainAxisAlignment.center,
this.crossAxisAlignment = CrossAxisAlignment.center,
this.isLoading = false,
});
final Function()? onPressed;
final String label;
final ButtonStyle style;
final Color color;
final Color textColor;
final double? width;
final double height;
final double borderRadius;
final double? elevation;
final Widget? icon;
final bool disabled;
final double fontSize;
final TextStyle? labelStyle;
final MainAxisAlignment mainAxisAlignment;
final CrossAxisAlignment crossAxisAlignment;
final bool isLoading;
@override
Widget build(BuildContext context) {
return SizedBox(
height: height,
width: width,
child: style == ButtonStyle.filled
? ElevatedButton(
onPressed: disabled ? null : onPressed,
style: ElevatedButton.styleFrom(
backgroundColor: color,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(borderRadius),
),
elevation: elevation,
padding: const EdgeInsets.symmetric(horizontal: 16.0),
),
child: isLoading
? Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SpinKitFadingCircle(color: textColor, size: fontSize),
const SpaceWidth(10.0),
Text(
'Loading...',
style:
labelStyle ??
TextStyle(
color: disabled ? Colors.grey : textColor,
fontSize: fontSize,
fontWeight: FontWeight.bold,
),
),
],
)
: Row(
mainAxisAlignment: mainAxisAlignment,
crossAxisAlignment: crossAxisAlignment,
children: [
icon ?? const SizedBox.shrink(),
if (icon != null) const SizedBox(width: 10.0),
Flexible(
child: FittedBox(
fit: BoxFit.scaleDown,
child: Text(
label,
style:
labelStyle ??
TextStyle(
color: disabled ? Colors.grey : textColor,
fontSize: fontSize,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
),
),
],
),
)
: OutlinedButton(
onPressed: disabled ? null : onPressed,
style: OutlinedButton.styleFrom(
backgroundColor: color,
side: const BorderSide(color: AppColor.primary),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(borderRadius),
),
padding: const EdgeInsets.symmetric(horizontal: 16.0),
),
child: isLoading
? Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SpinKitFadingCircle(color: textColor, size: fontSize),
const SpaceWidth(10.0),
Text(
'Loading...',
style:
labelStyle ??
TextStyle(
color: disabled ? Colors.grey : textColor,
fontSize: fontSize,
fontWeight: FontWeight.bold,
),
),
],
)
: Row(
mainAxisAlignment: mainAxisAlignment,
crossAxisAlignment: crossAxisAlignment,
mainAxisSize: MainAxisSize.min,
children: [
icon ?? const SizedBox.shrink(),
if (icon != null) const SizedBox(width: 10.0),
Flexible(
child: FittedBox(
fit: BoxFit.scaleDown,
child: Text(
label,
style:
labelStyle ??
TextStyle(
color: disabled ? Colors.grey : textColor,
fontSize: fontSize,
fontWeight: FontWeight.w600,
),
textAlign: TextAlign.center,
),
),
),
],
),
),
);
}
}

View File

@ -0,0 +1,7 @@
import 'package:flutter/material.dart';
import '../../../common/theme/theme.dart';
import '../spaces/space.dart';
part 'password_text_field.dart';
part 'text_field.dart';

View File

@ -0,0 +1,100 @@
part of 'field.dart';
class AppPasswordTextFormField extends StatefulWidget {
final TextEditingController? controller;
final String label;
final Function(String value)? onChanged;
final TextInputType? keyboardType;
final TextInputAction? textInputAction;
final TextCapitalization? textCapitalization;
final bool showLabel;
final Widget? prefixIcon;
final Widget? suffixIcon;
final bool readOnly;
final int maxLines;
final String? Function(String?)? validator;
const AppPasswordTextFormField({
super.key,
this.controller,
required this.label,
this.onChanged,
this.keyboardType,
this.textInputAction,
this.textCapitalization,
this.showLabel = true,
this.prefixIcon,
this.suffixIcon,
this.readOnly = false,
this.maxLines = 1,
this.validator,
});
@override
State<AppPasswordTextFormField> createState() =>
_AppPasswordTextFormFieldState();
}
class _AppPasswordTextFormFieldState extends State<AppPasswordTextFormField> {
bool isPasswordVisible = true;
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (widget.showLabel) ...[
Text(
widget.label,
style: AppStyle.md.copyWith(fontWeight: FontWeight.w700),
),
const SpaceHeight(12.0),
],
TextFormField(
controller: widget.controller,
onChanged: widget.onChanged,
obscureText: isPasswordVisible,
keyboardType: widget.keyboardType,
textInputAction: widget.textInputAction,
textCapitalization:
widget.textCapitalization ?? TextCapitalization.none,
readOnly: widget.readOnly,
maxLines: widget.maxLines,
validator: widget.validator,
decoration: InputDecoration(
prefixIcon: widget.prefixIcon,
suffixIcon: isPasswordVisible
? IconButton(
icon: Icon(
Icons.visibility,
color: AppColor.textSecondary,
size: 20,
),
constraints: const BoxConstraints(),
padding: const EdgeInsets.all(8),
onPressed: () {
setState(() {
isPasswordVisible = !isPasswordVisible;
});
},
)
: IconButton(
icon: Icon(
Icons.visibility_off,
color: AppColor.textSecondary,
size: 20,
),
constraints: const BoxConstraints(),
padding: const EdgeInsets.all(8),
onPressed: () {
setState(() {
isPasswordVisible = !isPasswordVisible;
});
},
),
hintText: widget.label,
),
),
],
);
}
}

View File

@ -0,0 +1,63 @@
part of 'field.dart';
class AppTextFormField extends StatelessWidget {
final TextEditingController? controller;
final String label;
final Function(String value)? onChanged;
final bool obscureText;
final TextInputType? keyboardType;
final TextInputAction? textInputAction;
final TextCapitalization? textCapitalization;
final bool showLabel;
final Widget? prefixIcon;
final Widget? suffixIcon;
final bool readOnly;
final int maxLines;
final String? Function(String?)? validator;
const AppTextFormField({
super.key,
this.controller,
required this.label,
this.onChanged,
this.obscureText = false,
this.keyboardType,
this.textInputAction,
this.textCapitalization,
this.showLabel = true,
this.prefixIcon,
this.suffixIcon,
this.readOnly = false,
this.maxLines = 1,
this.validator,
});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (showLabel) ...[
Text(label, style: AppStyle.md.copyWith(fontWeight: FontWeight.w700)),
const SpaceHeight(12.0),
],
TextFormField(
controller: controller,
onChanged: onChanged,
obscureText: obscureText,
keyboardType: keyboardType,
textInputAction: textInputAction,
textCapitalization: textCapitalization ?? TextCapitalization.none,
readOnly: readOnly,
maxLines: maxLines,
validator: validator,
decoration: InputDecoration(
prefixIcon: prefixIcon,
suffixIcon: suffixIcon,
hintText: label,
),
),
],
);
}
}

View File

@ -0,0 +1,17 @@
import 'package:flutter/material.dart';
class SpaceHeight extends StatelessWidget {
final double height;
const SpaceHeight(this.height, {super.key});
@override
Widget build(BuildContext context) => SizedBox(height: height);
}
class SpaceWidth extends StatelessWidget {
final double width;
const SpaceWidth(this.width, {super.key});
@override
Widget build(BuildContext context) => SizedBox(width: width);
}

View File

@ -0,0 +1,53 @@
import 'package:another_flushbar/flushbar.dart';
import 'package:flutter/material.dart';
import '../../../common/theme/theme.dart';
import '../../../domain/auth/auth.dart';
class AppFlushbar {
static void showSuccess(BuildContext context, String message) {
Flushbar(
messageText: Text(
message,
style: AppStyle.md.copyWith(
color: Colors.white,
fontWeight: FontWeight.w700,
),
),
icon: const Icon(Icons.check_circle, color: Colors.white),
duration: const Duration(seconds: 2),
flushbarPosition: FlushbarPosition.BOTTOM,
backgroundColor: AppColor.success,
borderRadius: BorderRadius.circular(12),
margin: const EdgeInsets.all(12),
).show(context);
}
static void showError(BuildContext context, String message) {
Flushbar(
messageText: Text(
message,
style: AppStyle.md.copyWith(
color: Colors.white,
fontWeight: FontWeight.w700,
),
),
icon: const Icon(Icons.error, color: Colors.white),
duration: const Duration(seconds: 3),
flushbarPosition: FlushbarPosition.BOTTOM,
backgroundColor: AppColor.error,
borderRadius: BorderRadius.circular(12),
margin: const EdgeInsets.all(12),
).show(context);
}
static void showAuthFailureToast(BuildContext context, AuthFailure failure) =>
showError(
context,
failure.map(
serverError: (value) => value.failure.toStringFormatted(context),
dynamicErrorMessage: (value) => value.erroMessage,
unexpectedError: (value) => 'Terjadi kesalahan, silahkan coba lagi',
),
);
}

View File

@ -0,0 +1,106 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../../application/auth/bloc/login_form_bloc.dart';
import '../../../../common/constant/app_constant.dart';
import '../../../../common/theme/theme.dart';
import '../../../../injection.dart';
import '../../../components/assets/assets.gen.dart';
import '../../../components/button/button.dart';
import '../../../components/spaces/space.dart';
import '../../../components/toast/flushbar.dart';
import 'widgets/email_field.dart';
import 'widgets/password_field.dart';
@RoutePage()
class LoginPage extends StatelessWidget implements AutoRouteWrapper {
const LoginPage({super.key});
@override
Widget build(BuildContext context) {
return BlocListener<LoginFormBloc, LoginFormState>(
listenWhen: (previous, current) =>
previous.failureOrLoginOption != current.failureOrLoginOption,
listener: (context, state) {
state.failureOrLoginOption.fold(
() => null,
(either) => either.fold(
(f) => AppFlushbar.showAuthFailureToast(context, f),
(data) {
if (context.mounted) {
AppFlushbar.showSuccess(
context,
'Login berhasil! Selamat datang, ${data.user.name}.',
);
// context.read<AuthBloc>().add(AuthEvent.fetchCurrentUser());
// context.router.replace(const MainRoute());
}
},
),
);
},
child: Scaffold(
body: BlocBuilder<LoginFormBloc, LoginFormState>(
builder: (context, state) {
return Form(
autovalidateMode: state.showErrorMessages
? AutovalidateMode.always
: AutovalidateMode.disabled,
child: ListView(
padding: const EdgeInsets.symmetric(
horizontal: 260.0,
vertical: 20.0,
),
children: [
const SpaceHeight(60.0),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 130.0),
child: Assets.images.logo.image(width: 100, height: 100),
),
const SpaceHeight(16.0),
Center(
child: Text(
AppConstant.appName,
style: AppStyle.lg.copyWith(fontWeight: FontWeight.w700),
),
),
const SpaceHeight(8.0),
Center(
child: Text(
'Akses Login Kasir Resto',
style: AppStyle.sm.copyWith(
color: AppColor.textSecondary,
),
),
),
const SpaceHeight(20.0),
LoginEmailField(),
const SpaceHeight(12.0),
LoginPasswordField(),
const SpaceHeight(24.0),
AppElevatedButton.filled(
onPressed: state.isSubmitting
? null
: () => context.read<LoginFormBloc>().add(
LoginFormEvent.submitted(),
),
textColor: state.isSubmitting
? AppColor.primary
: Colors.white,
label: 'Masuk',
isLoading: state.isSubmitting,
),
],
),
);
},
),
),
);
}
@override
Widget wrappedRoute(BuildContext context) =>
BlocProvider(create: (context) => getIt<LoginFormBloc>(), child: this);
}

View File

@ -0,0 +1,23 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../../../application/auth/bloc/login_form_bloc.dart';
import '../../../../../common/validator/validator.dart';
import '../../../../components/field/field.dart';
class LoginEmailField extends StatelessWidget {
const LoginEmailField({super.key});
@override
Widget build(BuildContext context) {
return AppTextFormField(
label: 'Email',
keyboardType: TextInputType.emailAddress,
textInputAction: TextInputAction.next,
validator: (value) =>
AppValidator.validateEmail(context.read<LoginFormBloc>().state.email),
onChanged: (value) =>
context.read<LoginFormBloc>().add(LoginFormEvent.emailChanged(value)),
);
}
}

View File

@ -0,0 +1,25 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../../../application/auth/bloc/login_form_bloc.dart';
import '../../../../../common/validator/validator.dart';
import '../../../../components/field/field.dart';
class LoginPasswordField extends StatelessWidget {
const LoginPasswordField({super.key});
@override
Widget build(BuildContext context) {
return AppPasswordTextFormField(
label: 'Password',
keyboardType: TextInputType.visiblePassword,
textInputAction: TextInputAction.done,
validator: (value) => AppValidator.validatePassword(
context.read<LoginFormBloc>().state.password,
),
onChanged: (value) => context.read<LoginFormBloc>().add(
LoginFormEvent.passwordChanged(value),
),
);
}
}

View File

@ -1,6 +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 '../../components/assets/assets.gen.dart';
import '../../router/app_router.gr.dart';
@RoutePage() @RoutePage()
class SplashPage extends StatefulWidget { class SplashPage extends StatefulWidget {
const SplashPage({super.key}); const SplashPage({super.key});
@ -10,8 +13,29 @@ class SplashPage extends StatefulWidget {
} }
class _SplashPageState extends State<SplashPage> { class _SplashPageState extends State<SplashPage> {
_startDelay() => Future.delayed(const Duration(seconds: 2), _goNext);
_goNext() => context.router.replace(const LoginRoute());
@override
void initState() {
_startDelay();
super.initState();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold(body: Center(child: Text("Splash Page"))); return Scaffold(
body: Center(
child: Padding(
padding: const EdgeInsets.all(20),
child: Assets.images.logo.image(
width: 100,
height: 100,
fit: BoxFit.contain,
),
),
),
);
} }
} }

View File

@ -7,5 +7,8 @@ class AppRouter extends RootStackRouter {
List<AutoRoute> get routes => [ List<AutoRoute> get routes => [
// Splash // Splash
AutoRoute(page: SplashRoute.page, initial: true), AutoRoute(page: SplashRoute.page, initial: true),
// Router
AutoRoute(page: LoginRoute.page),
]; ];
} }

View File

@ -9,22 +9,40 @@
// coverage:ignore-file // coverage:ignore-file
// ignore_for_file: no_leading_underscores_for_library_prefixes // ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:apskel_pos_flutter_v2/presentation/pages/splash/splash_page.dart' import 'package:apskel_pos_flutter_v2/presentation/pages/auth/login/login_page.dart'
as _i1; as _i1;
import 'package:auto_route/auto_route.dart' as _i2; import 'package:apskel_pos_flutter_v2/presentation/pages/splash/splash_page.dart'
as _i2;
import 'package:auto_route/auto_route.dart' as _i3;
/// generated route for /// generated route for
/// [_i1.SplashPage] /// [_i1.LoginPage]
class SplashRoute extends _i2.PageRouteInfo<void> { class LoginRoute extends _i3.PageRouteInfo<void> {
const SplashRoute({List<_i2.PageRouteInfo>? children}) const LoginRoute({List<_i3.PageRouteInfo>? children})
: super(LoginRoute.name, initialChildren: children);
static const String name = 'LoginRoute';
static _i3.PageInfo page = _i3.PageInfo(
name,
builder: (data) {
return const _i1.LoginPage();
},
);
}
/// generated route for
/// [_i2.SplashPage]
class SplashRoute extends _i3.PageRouteInfo<void> {
const SplashRoute({List<_i3.PageRouteInfo>? children})
: super(SplashRoute.name, initialChildren: children); : super(SplashRoute.name, initialChildren: children);
static const String name = 'SplashRoute'; static const String name = 'SplashRoute';
static _i2.PageInfo page = _i2.PageInfo( static _i3.PageInfo page = _i3.PageInfo(
name, name,
builder: (data) { builder: (data) {
return const _i1.SplashPage(); return const _i2.SplashPage();
}, },
); );
} }

View File

@ -25,6 +25,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.7.1" version: "7.7.1"
another_flushbar:
dependency: "direct main"
description:
name: another_flushbar
sha256: "2b99671c010a7d5770acf5cb24c9f508b919c3a7948b6af9646e773e7da7b757"
url: "https://pub.dev"
source: hosted
version: "1.12.32"
archive: archive:
dependency: transitive dependency: transitive
description: description:
@ -73,6 +81,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.0" version: "1.3.0"
bloc:
dependency: "direct main"
description:
name: bloc
sha256: a2cebb899f91d36eeeaa55c7b20b5915db5a9df1b8fd4a3c9c825e22e474537d
url: "https://pub.dev"
source: hosted
version: "9.1.0"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
@ -382,6 +398,14 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
flutter_bloc:
dependency: "direct main"
description:
name: flutter_bloc
sha256: cf51747952201a455a1c840f8171d273be009b932c75093020f9af64f2123e38
url: "https://pub.dev"
source: hosted
version: "9.1.1"
flutter_gen_core: flutter_gen_core:
dependency: transitive dependency: transitive
description: description:
@ -414,6 +438,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.0.0" version: "5.0.0"
flutter_spinkit:
dependency: "direct main"
description:
name: flutter_spinkit
sha256: "77850df57c00dc218bfe96071d576a8babec24cf58b2ed121c83cca4a2fdce7f"
url: "https://pub.dev"
source: hosted
version: "5.2.2"
flutter_svg: flutter_svg:
dependency: "direct main" dependency: "direct main"
description: description:
@ -656,6 +688,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.0" version: "2.0.0"
nested:
dependency: transitive
description:
name: nested
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
nm: nm:
dependency: transitive dependency: transitive
description: description:
@ -776,6 +816,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.0.3" version: "6.0.3"
provider:
dependency: transitive
description:
name: provider
sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272"
url: "https://pub.dev"
source: hosted
version: "6.1.5+1"
pub_semver: pub_semver:
dependency: transitive dependency: transitive
description: description:

View File

@ -29,6 +29,10 @@ dependencies:
shared_preferences: ^2.5.3 shared_preferences: ^2.5.3
firebase_core: ^4.2.0 firebase_core: ^4.2.0
firebase_crashlytics: ^5.0.3 firebase_crashlytics: ^5.0.3
another_flushbar: ^1.12.32
flutter_spinkit: ^5.2.2
bloc: ^9.1.0
flutter_bloc: ^9.1.1
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: