feat: Refactor code structure and optimize performance across multiple modules
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
import 'package:injectable/injectable.dart';
|
||||
import 'package:krow/features/profile/wages_forms/domain/entities/edd_form_entity.dart';
|
||||
import 'package:krow/features/profile/wages_forms/domain/entities/i_nine_entity.dart';
|
||||
import 'package:krow/features/profile/wages_forms/domain/entities/offer_letter_entity.dart';
|
||||
import 'package:krow/features/profile/wages_forms/domain/entities/w_four_entity.dart';
|
||||
import 'package:krow/features/profile/wages_forms/domain/entities/wage_form_entity.dart';
|
||||
|
||||
@injectable
|
||||
class WagesFormApiProvider {
|
||||
Future<List<WageFormEntity>> fetchActualForms() async {
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
|
||||
return [
|
||||
const EddFormEntity.empty(),
|
||||
const INineEntity.empty(),
|
||||
const OfferLetterEntity.empty(),
|
||||
const WFourEntity.empty(),
|
||||
];
|
||||
}
|
||||
|
||||
Future<void> submitWageForm(WageFormEntity formData) async {
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import 'package:injectable/injectable.dart';
|
||||
import 'package:krow/features/profile/wages_forms/data/wages_form_api_provider.dart';
|
||||
import 'package:krow/features/profile/wages_forms/domain/entities/wage_form_entity.dart';
|
||||
import 'package:krow/features/profile/wages_forms/domain/wages_forms_repository.dart';
|
||||
|
||||
@Injectable(as: WagesFormsRepository)
|
||||
class WagesFormsRepositoryImpl implements WagesFormsRepository {
|
||||
WagesFormsRepositoryImpl({required WagesFormApiProvider apiProvider})
|
||||
: _apiProvider = apiProvider;
|
||||
|
||||
final WagesFormApiProvider _apiProvider;
|
||||
|
||||
@override
|
||||
Future<List<WageFormEntity>> getActualForms() async {
|
||||
return _apiProvider.fetchActualForms();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> submitWageForm({required WageFormEntity form}) {
|
||||
return _apiProvider.submitWageForm(form);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:krow/core/application/di/injectable.dart';
|
||||
import 'package:krow/core/data/enums/state_status.dart';
|
||||
import 'package:krow/features/profile/wages_forms/domain/entities/wage_form_entity.dart';
|
||||
import 'package:krow/features/profile/wages_forms/domain/utils/validator_wage_from_visitor.dart';
|
||||
import 'package:krow/features/profile/wages_forms/domain/wages_forms_repository.dart';
|
||||
|
||||
part 'wages_form_event.dart';
|
||||
|
||||
part 'wages_form_state.dart';
|
||||
|
||||
part 'wages_form_fields.dart';
|
||||
|
||||
class WagesFormBloc extends Bloc<WagesFormEvent, WagesFormState> {
|
||||
WagesFormBloc() : super(const WagesFormState()) {
|
||||
on<InitializeWagesFormBloc>(_onInitial);
|
||||
|
||||
on<SetActiveForm>(_onSetActiveForm);
|
||||
|
||||
on<AddUserSignImage>(_onAddUserSign);
|
||||
|
||||
on<UpdateCurrentForm>(_onUpdateCurrentForm);
|
||||
|
||||
on<ValidateCurrentForm>(_onValidateCurrentForm);
|
||||
|
||||
on<SubmitCurrentForm>(_onSubmitCurrentForm);
|
||||
}
|
||||
|
||||
final _repository = getIt<WagesFormsRepository>();
|
||||
final _validatorVisitor = const ValidatorWageFromVisitor();
|
||||
|
||||
Future<void> _onInitial(
|
||||
InitializeWagesFormBloc event,
|
||||
Emitter<WagesFormState> emit,
|
||||
) async {
|
||||
final forms = await _repository.getActualForms();
|
||||
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: StateStatus.idle,
|
||||
actualForms: forms,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onSetActiveForm(SetActiveForm event, Emitter<WagesFormState> emit) {
|
||||
emit(state.copyWith(activeForm: event.form));
|
||||
}
|
||||
|
||||
void _onAddUserSign(AddUserSignImage event, Emitter<WagesFormState> emit) {
|
||||
emit(state.copyWith(signImageData: event.imageData));
|
||||
}
|
||||
|
||||
void _onUpdateCurrentForm(
|
||||
UpdateCurrentForm event,
|
||||
Emitter<WagesFormState> emit,
|
||||
) {
|
||||
emit(state.copyWith(activeForm: event.form));
|
||||
|
||||
final index = state.actualForms.indexWhere(
|
||||
(item) => item.runtimeType == event.form.runtimeType,
|
||||
);
|
||||
if (index < 0) return;
|
||||
|
||||
emit(
|
||||
state.copyWith(
|
||||
actualForms: List.from(state.actualForms)..[index] = event.form,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onValidateCurrentForm(
|
||||
ValidateCurrentForm event,
|
||||
Emitter<WagesFormState> emit,
|
||||
) {
|
||||
final formErrors = state.activeForm?.visit(_validatorVisitor) ?? {};
|
||||
|
||||
final isSignAdded = state.signImageData != null;
|
||||
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: formErrors.isEmpty && isSignAdded
|
||||
? StateStatus.success
|
||||
: StateStatus.error,
|
||||
formErrors: formErrors,
|
||||
),
|
||||
);
|
||||
|
||||
emit(state.copyWith(status: StateStatus.idle, formErrors: {}));
|
||||
}
|
||||
|
||||
void _onSubmitCurrentForm(
|
||||
SubmitCurrentForm event,
|
||||
Emitter<WagesFormState> emit,
|
||||
) async {
|
||||
final form = state.activeForm;
|
||||
if (form == null) return;
|
||||
|
||||
emit(state.copyWith(status: StateStatus.loading));
|
||||
|
||||
await _repository.submitWageForm(form: form);
|
||||
|
||||
emit(state.copyWith(status: StateStatus.success));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
part of 'wages_form_bloc.dart';
|
||||
|
||||
@immutable
|
||||
sealed class WagesFormEvent {
|
||||
const WagesFormEvent();
|
||||
}
|
||||
|
||||
class InitializeWagesFormBloc extends WagesFormEvent {
|
||||
const InitializeWagesFormBloc();
|
||||
}
|
||||
|
||||
class SetActiveForm extends WagesFormEvent {
|
||||
const SetActiveForm({required this.form});
|
||||
|
||||
final WageFormEntity form;
|
||||
}
|
||||
|
||||
class AddUserSignImage extends WagesFormEvent {
|
||||
const AddUserSignImage({required this.imageData});
|
||||
|
||||
final Uint8List imageData;
|
||||
}
|
||||
|
||||
class UpdateCurrentForm extends WagesFormEvent {
|
||||
const UpdateCurrentForm({required this.form});
|
||||
|
||||
final WageFormEntity form;
|
||||
}
|
||||
|
||||
class ValidateCurrentForm extends WagesFormEvent {
|
||||
const ValidateCurrentForm();
|
||||
}
|
||||
|
||||
class SubmitCurrentForm extends WagesFormEvent {
|
||||
const SubmitCurrentForm();
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
part of 'wages_form_bloc.dart';
|
||||
|
||||
abstract class WagesFormFields {
|
||||
static const fullName = 'fullName';
|
||||
static const city = 'city';
|
||||
static const lastName = 'lastName';
|
||||
static const firstName = 'firstName';
|
||||
static const middleName = 'middleName';
|
||||
static const address = 'address';
|
||||
static const state = 'state';
|
||||
static const zipCode = 'zipCode';
|
||||
static const birthdate = 'birthdate';
|
||||
static const socialNumber = 'socialNumber';
|
||||
static const email = 'email';
|
||||
static const phone = 'phone';
|
||||
}
|
||||
|
||||
const usStates = [
|
||||
'Alabama',
|
||||
'Alaska',
|
||||
'Arizona',
|
||||
'Arkansas',
|
||||
'California',
|
||||
'Colorado',
|
||||
'Connecticut',
|
||||
'Delaware',
|
||||
'District of Columbia',
|
||||
'Florida',
|
||||
'Georgia',
|
||||
'Hawaii',
|
||||
'Idaho',
|
||||
'Illinois',
|
||||
'Indiana',
|
||||
'Iowa',
|
||||
'Kansas',
|
||||
'Kentucky',
|
||||
'Louisiana',
|
||||
'Maine',
|
||||
'Montana',
|
||||
'Nebraska',
|
||||
'Nevada',
|
||||
'New Hampshire',
|
||||
'New Jersey',
|
||||
'New Mexico',
|
||||
'New York',
|
||||
'North Carolina',
|
||||
'North Dakota',
|
||||
'Ohio',
|
||||
'Oklahoma',
|
||||
'Oregon',
|
||||
'Maryland',
|
||||
'Massachusetts',
|
||||
'Michigan',
|
||||
'Minnesota',
|
||||
'Mississippi',
|
||||
'Missouri',
|
||||
'Pennsylvania',
|
||||
'Rhode Island',
|
||||
'South Carolina',
|
||||
'South Dakota',
|
||||
'Tennessee',
|
||||
'Texas',
|
||||
'Utah',
|
||||
'Vermont',
|
||||
'Virginia',
|
||||
'Washington',
|
||||
'West Virginia',
|
||||
'Wisconsin',
|
||||
'Wyoming',
|
||||
];
|
||||
@@ -0,0 +1,34 @@
|
||||
part of 'wages_form_bloc.dart';
|
||||
|
||||
@immutable
|
||||
class WagesFormState {
|
||||
const WagesFormState({
|
||||
this.status = StateStatus.loading,
|
||||
this.signImageData,
|
||||
this.actualForms = const [],
|
||||
this.activeForm,
|
||||
this.formErrors = const {},
|
||||
});
|
||||
|
||||
final StateStatus status;
|
||||
final Uint8List? signImageData;
|
||||
final List<WageFormEntity> actualForms;
|
||||
final WageFormEntity? activeForm;
|
||||
final Map<String, String> formErrors;
|
||||
|
||||
WagesFormState copyWith({
|
||||
StateStatus? status,
|
||||
Uint8List? signImageData,
|
||||
List<WageFormEntity>? actualForms,
|
||||
WageFormEntity? activeForm,
|
||||
Map<String, String>? formErrors
|
||||
}) {
|
||||
return WagesFormState(
|
||||
status: status ?? this.status,
|
||||
signImageData: signImageData ?? this.signImageData,
|
||||
actualForms: actualForms ?? this.actualForms,
|
||||
activeForm: activeForm ?? this.activeForm,
|
||||
formErrors: formErrors ?? this.formErrors,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import 'package:krow/features/profile/wages_forms/domain/entities/wage_form_entity.dart';
|
||||
import 'package:krow/features/profile/wages_forms/domain/utils/wage_form_visitor.dart';
|
||||
|
||||
class EddFormEntity extends WageFormEntity {
|
||||
const EddFormEntity({
|
||||
required super.isSigned,
|
||||
required super.isSubmitted,
|
||||
required this.firstName,
|
||||
required this.lastName,
|
||||
required this.address,
|
||||
required this.aptNumber,
|
||||
required this.city,
|
||||
required this.state,
|
||||
required this.zipCode,
|
||||
required this.socialNumber,
|
||||
});
|
||||
|
||||
const EddFormEntity.empty({
|
||||
super.isSigned = false,
|
||||
super.isSubmitted = false,
|
||||
this.firstName = '',
|
||||
this.lastName = '',
|
||||
this.address = '',
|
||||
this.aptNumber,
|
||||
this.city = '',
|
||||
this.state = '',
|
||||
this.zipCode = '',
|
||||
this.socialNumber = '',
|
||||
});
|
||||
|
||||
final String firstName;
|
||||
final String lastName;
|
||||
final String address;
|
||||
final int? aptNumber;
|
||||
final String city;
|
||||
final String state;
|
||||
final String zipCode;
|
||||
final String socialNumber;
|
||||
|
||||
@override
|
||||
String get name => 'EDD';
|
||||
|
||||
@override
|
||||
T visit<T>(WageFormVisitor<T> visitor) {
|
||||
return visitor.visitEddForm(this);
|
||||
}
|
||||
|
||||
EddFormEntity copyWith({
|
||||
bool? isSigned,
|
||||
bool? isSubmitted,
|
||||
String? firstName,
|
||||
String? lastName,
|
||||
String? address,
|
||||
int? aptNumber,
|
||||
String? city,
|
||||
String? state,
|
||||
String? zipCode,
|
||||
String? socialNumber,
|
||||
}) {
|
||||
return EddFormEntity(
|
||||
isSigned: isSigned ?? this.isSigned,
|
||||
isSubmitted: isSubmitted ?? this.isSubmitted,
|
||||
firstName: firstName ?? this.firstName,
|
||||
lastName: lastName ?? this.lastName,
|
||||
address: address ?? this.address,
|
||||
aptNumber: aptNumber ?? this.aptNumber,
|
||||
city: city ?? this.city,
|
||||
state: state ?? this.state,
|
||||
zipCode: zipCode ?? this.zipCode,
|
||||
socialNumber: socialNumber ?? this.socialNumber,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
import 'package:krow/features/profile/wages_forms/domain/entities/wage_form_entity.dart';
|
||||
import 'package:krow/features/profile/wages_forms/domain/utils/wage_form_visitor.dart';
|
||||
|
||||
class INineEntity extends WageFormEntity {
|
||||
const INineEntity({
|
||||
required super.isSigned,
|
||||
required super.isSubmitted,
|
||||
required this.firstName,
|
||||
required this.lastName,
|
||||
required this.middleName,
|
||||
required this.address,
|
||||
required this.aptNumber,
|
||||
required this.city,
|
||||
required this.state,
|
||||
required this.zipCode,
|
||||
required this.dateOfBirth,
|
||||
required this.socialNumber,
|
||||
required this.email,
|
||||
required this.phoneNumber,
|
||||
});
|
||||
|
||||
const INineEntity.empty({
|
||||
super.isSigned = false,
|
||||
super.isSubmitted = false,
|
||||
this.firstName = '',
|
||||
this.lastName = '',
|
||||
this.middleName = '',
|
||||
this.address = '',
|
||||
this.aptNumber,
|
||||
this.city = '',
|
||||
this.state = '',
|
||||
this.zipCode = '',
|
||||
this.dateOfBirth,
|
||||
this.socialNumber = '',
|
||||
this.email = '',
|
||||
this.phoneNumber = '+1',
|
||||
});
|
||||
|
||||
final String firstName;
|
||||
final String lastName;
|
||||
final String middleName;
|
||||
final String address;
|
||||
final int? aptNumber;
|
||||
final String city;
|
||||
final String state;
|
||||
final String zipCode;
|
||||
final DateTime? dateOfBirth;
|
||||
final String socialNumber;
|
||||
final String email;
|
||||
final String phoneNumber;
|
||||
|
||||
@override
|
||||
String get name => 'I-9';
|
||||
|
||||
@override
|
||||
T visit<T>(WageFormVisitor<T> visitor) {
|
||||
return visitor.visitINineForm(this);
|
||||
}
|
||||
|
||||
INineEntity copyWith({
|
||||
bool? isSigned,
|
||||
bool? isSubmitted,
|
||||
String? firstName,
|
||||
String? middleName,
|
||||
String? lastName,
|
||||
String? address,
|
||||
int? aptNumber,
|
||||
String? city,
|
||||
String? state,
|
||||
String? zipCode,
|
||||
String? socialNumber,
|
||||
DateTime? dateOfBirth,
|
||||
String? email,
|
||||
String? phoneNumber,
|
||||
}) {
|
||||
return INineEntity(
|
||||
isSigned: isSigned ?? this.isSigned,
|
||||
isSubmitted: isSubmitted ?? this.isSubmitted,
|
||||
firstName: firstName ?? this.firstName,
|
||||
middleName: middleName ?? this.middleName,
|
||||
lastName: lastName ?? this.lastName,
|
||||
address: address ?? this.address,
|
||||
aptNumber: aptNumber ?? this.aptNumber,
|
||||
city: city ?? this.city,
|
||||
state: state ?? this.state,
|
||||
zipCode: zipCode ?? this.zipCode,
|
||||
socialNumber: socialNumber ?? this.socialNumber,
|
||||
dateOfBirth: dateOfBirth ?? this.dateOfBirth,
|
||||
email: email ?? this.email,
|
||||
phoneNumber: phoneNumber ?? this.phoneNumber,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import 'package:krow/features/profile/wages_forms/domain/entities/wage_form_entity.dart';
|
||||
import 'package:krow/features/profile/wages_forms/domain/utils/wage_form_visitor.dart';
|
||||
|
||||
class OfferLetterEntity extends WageFormEntity {
|
||||
const OfferLetterEntity({
|
||||
required super.isSigned,
|
||||
required super.isSubmitted,
|
||||
required this.fullName,
|
||||
required this.city,
|
||||
});
|
||||
|
||||
const OfferLetterEntity.empty({
|
||||
super.isSigned = false,
|
||||
super.isSubmitted = false,
|
||||
this.fullName = '',
|
||||
this.city = '',
|
||||
});
|
||||
|
||||
final String fullName;
|
||||
final String city;
|
||||
|
||||
@override
|
||||
String get name => 'Offer Letter & Agreement';
|
||||
|
||||
@override
|
||||
T visit<T>(WageFormVisitor<T> visitor) {
|
||||
return visitor.visitOfferLetterFrom(this);
|
||||
}
|
||||
|
||||
OfferLetterEntity copyWith({
|
||||
bool? isSigned,
|
||||
bool? isSubmitted,
|
||||
String? fullName,
|
||||
String? city,
|
||||
}) {
|
||||
return OfferLetterEntity(
|
||||
isSigned: isSigned ?? this.isSigned,
|
||||
isSubmitted: isSubmitted ?? this.isSubmitted,
|
||||
fullName: fullName ?? this.fullName,
|
||||
city: city ?? this.city,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import 'package:krow/features/profile/wages_forms/domain/entities/wage_form_entity.dart';
|
||||
import 'package:krow/features/profile/wages_forms/domain/utils/wage_form_visitor.dart';
|
||||
|
||||
class WFourEntity extends WageFormEntity {
|
||||
const WFourEntity({
|
||||
required super.isSigned,
|
||||
required super.isSubmitted,
|
||||
required this.firstName,
|
||||
required this.lastName,
|
||||
required this.middleName,
|
||||
required this.address,
|
||||
required this.city,
|
||||
required this.state,
|
||||
required this.zipCode,
|
||||
required this.socialNumber,
|
||||
});
|
||||
|
||||
const WFourEntity.empty({
|
||||
super.isSigned = false,
|
||||
super.isSubmitted = false,
|
||||
this.firstName = '',
|
||||
this.lastName = '',
|
||||
this.middleName = '',
|
||||
this.address = '',
|
||||
this.city = '',
|
||||
this.state = '',
|
||||
this.zipCode = '',
|
||||
this.socialNumber = '',
|
||||
});
|
||||
|
||||
final String firstName;
|
||||
final String lastName;
|
||||
final String middleName;
|
||||
final String address;
|
||||
final String city;
|
||||
final String state;
|
||||
final String zipCode;
|
||||
final String socialNumber;
|
||||
|
||||
@override
|
||||
String get name => 'W-4';
|
||||
|
||||
@override
|
||||
T visit<T>(WageFormVisitor<T> visitor) {
|
||||
return visitor.visitWFourForm(this);
|
||||
}
|
||||
|
||||
WFourEntity copyWith({
|
||||
bool? isSigned,
|
||||
bool? isSubmitted,
|
||||
String? firstName,
|
||||
String? middleName,
|
||||
String? lastName,
|
||||
String? address,
|
||||
String? city,
|
||||
String? state,
|
||||
String? zipCode,
|
||||
String? socialNumber,
|
||||
}) {
|
||||
return WFourEntity(
|
||||
isSigned: isSigned ?? this.isSigned,
|
||||
isSubmitted: isSubmitted ?? this.isSubmitted,
|
||||
firstName: firstName ?? this.firstName,
|
||||
middleName: middleName ?? this.middleName,
|
||||
lastName: lastName ?? this.lastName,
|
||||
address: address ?? this.address,
|
||||
city: city ?? this.city,
|
||||
state: state ?? this.state,
|
||||
zipCode: zipCode ?? this.zipCode,
|
||||
socialNumber: socialNumber ?? this.socialNumber,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:krow/features/profile/wages_forms/domain/utils/wage_form_visitor.dart';
|
||||
|
||||
@immutable
|
||||
abstract class WageFormEntity {
|
||||
const WageFormEntity({required this.isSigned, required this.isSubmitted});
|
||||
|
||||
final bool isSigned;
|
||||
final bool isSubmitted;
|
||||
|
||||
String get name;
|
||||
|
||||
T visit<T>(WageFormVisitor<T> visitor);
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import 'dart:typed_data';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncfusion_flutter_signaturepad/signaturepad.dart';
|
||||
|
||||
class SignImageService {
|
||||
Future<Uint8List> getSignPngBytes(SfSignaturePadState state) async {
|
||||
final image = await getSignImage(state);
|
||||
final byteData = await image.toByteData(format: ui.ImageByteFormat.png);
|
||||
final bytes = byteData!.buffer.asUint8List();
|
||||
return bytes;
|
||||
}
|
||||
|
||||
Future<ui.Image> getSignImage(SfSignaturePadState state) async {
|
||||
var paths = state.toPathList();
|
||||
Rect? bounds;
|
||||
|
||||
for (final path in paths) {
|
||||
final pathBounds = path.getBounds();
|
||||
if (bounds == null) {
|
||||
bounds = pathBounds;
|
||||
} else {
|
||||
bounds = bounds.expandToInclude(pathBounds);
|
||||
}
|
||||
}
|
||||
|
||||
if (bounds == null) {
|
||||
throw ArgumentError('The paths list is empty!');
|
||||
}
|
||||
|
||||
final offset = Offset(-bounds.left, -bounds.top);
|
||||
final recorder = ui.PictureRecorder();
|
||||
final canvas = Canvas(recorder);
|
||||
var scale = 2.0;
|
||||
canvas.scale(scale); // Масштабування для більшої деталізації
|
||||
|
||||
final paint = Paint()
|
||||
..color = Colors.black
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1.0
|
||||
..isAntiAlias = true
|
||||
..strokeCap = StrokeCap.round
|
||||
..strokeJoin = StrokeJoin.round;
|
||||
|
||||
for (final path in paths) {
|
||||
canvas.drawPath(path.shift(offset), paint);
|
||||
}
|
||||
|
||||
final picture = recorder.endRecording();
|
||||
return await picture.toImage(
|
||||
(bounds.width * scale).ceil(),
|
||||
(bounds.height * scale).ceil(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
import 'package:krow/core/application/common/validators/email_validator.dart';
|
||||
import 'package:krow/core/application/common/validators/phone_validator.dart';
|
||||
import 'package:krow/core/application/common/validators/social_number_validator.dart';
|
||||
import 'package:krow/features/profile/wages_forms/domain/bloc/wages_form_bloc.dart';
|
||||
import 'package:krow/features/profile/wages_forms/domain/entities/edd_form_entity.dart';
|
||||
import 'package:krow/features/profile/wages_forms/domain/entities/i_nine_entity.dart';
|
||||
import 'package:krow/features/profile/wages_forms/domain/entities/offer_letter_entity.dart';
|
||||
import 'package:krow/features/profile/wages_forms/domain/entities/w_four_entity.dart';
|
||||
import 'package:krow/features/profile/wages_forms/domain/utils/wage_form_visitor.dart';
|
||||
|
||||
/// Returns a Map of keys that represent the form fields and values that
|
||||
/// represent corresponding error
|
||||
class ValidatorWageFromVisitor implements WageFormVisitor<Map<String, String>> {
|
||||
const ValidatorWageFromVisitor();
|
||||
|
||||
@override
|
||||
Map<String, String> visitEddForm(EddFormEntity entity) {
|
||||
final ssnError = SocialNumberValidator.validate(
|
||||
entity.socialNumber,
|
||||
isRequired: true,
|
||||
);
|
||||
|
||||
return <String, String>{
|
||||
if (entity.firstName.isEmpty)
|
||||
WagesFormFields.firstName: 'Required to fill',
|
||||
if (entity.lastName.isEmpty) WagesFormFields.lastName: 'Required to fill',
|
||||
if (entity.address.isEmpty) WagesFormFields.address: 'Required to fill',
|
||||
if (entity.city.isEmpty) WagesFormFields.city: 'Required to fill',
|
||||
if (entity.state.isEmpty) WagesFormFields.state: 'Required to fill',
|
||||
if (entity.zipCode.isEmpty) WagesFormFields.zipCode: 'Required to fill',
|
||||
if (ssnError != null) WagesFormFields.socialNumber: ssnError,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, String> visitINineForm(INineEntity entity) {
|
||||
final emailError = EmailValidator.validate(entity.email, isRequired: true);
|
||||
final phoneError = PhoneValidator.validate(
|
||||
entity.phoneNumber,
|
||||
isRequired: true,
|
||||
);
|
||||
final ssnError = SocialNumberValidator.validate(
|
||||
entity.socialNumber,
|
||||
isRequired: true,
|
||||
);
|
||||
|
||||
final birthdate = entity.dateOfBirth;
|
||||
String? birthDateError;
|
||||
if (birthdate == null) {
|
||||
birthDateError = 'This field is required';
|
||||
} else {
|
||||
final now = DateTime.now();
|
||||
if (birthdate.isAfter(now)) {
|
||||
birthDateError = 'Future date selected';
|
||||
} else if (birthdate.difference(now).inDays.abs() < 5840) {
|
||||
birthDateError = 'Invalid date selected';
|
||||
}
|
||||
}
|
||||
|
||||
return <String, String>{
|
||||
if (entity.firstName.isEmpty)
|
||||
WagesFormFields.firstName: 'Required to fill',
|
||||
if (entity.lastName.isEmpty) WagesFormFields.lastName: 'Required to fill',
|
||||
if (entity.address.isEmpty) WagesFormFields.address: 'Required to fill',
|
||||
if (entity.city.isEmpty) WagesFormFields.city: 'Required to fill',
|
||||
if (entity.state.isEmpty) WagesFormFields.state: 'Required to fill',
|
||||
if (entity.zipCode.isEmpty) WagesFormFields.zipCode: 'Required to fill',
|
||||
if (birthDateError != null) WagesFormFields.birthdate: birthDateError,
|
||||
if (ssnError != null) WagesFormFields.socialNumber: ssnError,
|
||||
if (emailError != null) WagesFormFields.email: emailError,
|
||||
if (phoneError != null) WagesFormFields.phone: phoneError,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, String> visitOfferLetterFrom(OfferLetterEntity entity) {
|
||||
return <String, String>{
|
||||
if (entity.fullName.isEmpty) WagesFormFields.fullName: 'Required to fill',
|
||||
if (entity.city.isEmpty) WagesFormFields.city: 'Required to fill'
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, String> visitWFourForm(WFourEntity entity) {
|
||||
final ssnError = SocialNumberValidator.validate(
|
||||
entity.socialNumber,
|
||||
isRequired: true,
|
||||
);
|
||||
|
||||
return <String, String>{
|
||||
if (entity.firstName.isEmpty)
|
||||
WagesFormFields.firstName: 'Required to fill',
|
||||
if (entity.lastName.isEmpty) WagesFormFields.lastName: 'Required to fill',
|
||||
if (entity.address.isEmpty) WagesFormFields.address: 'Required to fill',
|
||||
if (entity.city.isEmpty) WagesFormFields.city: 'Required to fill',
|
||||
if (entity.state.isEmpty) WagesFormFields.state: 'Required to fill',
|
||||
if (entity.zipCode.isEmpty) WagesFormFields.zipCode: 'Required to fill',
|
||||
if (ssnError != null) WagesFormFields.socialNumber: ssnError,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import 'package:krow/features/profile/wages_forms/domain/entities/edd_form_entity.dart';
|
||||
import 'package:krow/features/profile/wages_forms/domain/entities/i_nine_entity.dart';
|
||||
import 'package:krow/features/profile/wages_forms/domain/entities/offer_letter_entity.dart';
|
||||
import 'package:krow/features/profile/wages_forms/domain/entities/w_four_entity.dart';
|
||||
|
||||
abstract interface class WageFormVisitor<T> {
|
||||
const WageFormVisitor();
|
||||
|
||||
T visitEddForm(EddFormEntity entity);
|
||||
|
||||
T visitINineForm(INineEntity entity);
|
||||
|
||||
T visitOfferLetterFrom(OfferLetterEntity entity);
|
||||
|
||||
T visitWFourForm(WFourEntity entity);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import 'package:krow/features/profile/wages_forms/domain/entities/wage_form_entity.dart';
|
||||
|
||||
abstract interface class WagesFormsRepository {
|
||||
Future<List<WageFormEntity>> getActualForms();
|
||||
|
||||
Future<void> submitWageForm({required WageFormEntity form});
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:krow/features/profile/wages_forms/domain/entities/edd_form_entity.dart';
|
||||
import 'package:krow/features/profile/wages_forms/domain/entities/i_nine_entity.dart';
|
||||
import 'package:krow/features/profile/wages_forms/domain/entities/offer_letter_entity.dart';
|
||||
import 'package:krow/features/profile/wages_forms/domain/entities/w_four_entity.dart';
|
||||
import 'package:krow/features/profile/wages_forms/domain/utils/wage_form_visitor.dart';
|
||||
|
||||
class PreviewWageFormVisitor implements WageFormVisitor<Map<String, String>> {
|
||||
const PreviewWageFormVisitor();
|
||||
|
||||
@override
|
||||
Map<String, String> visitEddForm(EddFormEntity entity) {
|
||||
return {
|
||||
'Last Name': entity.lastName,
|
||||
'First Name': entity.firstName,
|
||||
'Address': entity.address,
|
||||
'Apt. number': entity.aptNumber?.toString() ?? '',
|
||||
'City': entity.city,
|
||||
'State': entity.state,
|
||||
'ZIP code': entity.zipCode,
|
||||
'Social Number': entity.socialNumber,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, String> visitINineForm(INineEntity entity) {
|
||||
return {
|
||||
'Last Name': entity.lastName,
|
||||
'First Name': entity.firstName,
|
||||
'Middle Name': entity.middleName,
|
||||
'Address': entity.address,
|
||||
'Apt. number': entity.aptNumber?.toString() ?? '',
|
||||
'City': entity.city,
|
||||
'State': entity.state,
|
||||
'ZIP code': entity.zipCode,
|
||||
'Date of birth': entity.dateOfBirth != null
|
||||
? DateFormat('M/d/y').format(entity.dateOfBirth!)
|
||||
: '',
|
||||
'Social Number': entity.socialNumber,
|
||||
'Email': entity.email,
|
||||
'Phone Number': entity.phoneNumber,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, String> visitOfferLetterFrom(OfferLetterEntity entity) {
|
||||
return {
|
||||
'Full Name': entity.fullName,
|
||||
'City': entity.city,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, String> visitWFourForm(WFourEntity entity) {
|
||||
return {
|
||||
'Last Name': entity.lastName,
|
||||
'First Name': entity.firstName,
|
||||
'Middle Name': entity.middleName,
|
||||
'Address': entity.address,
|
||||
'City': entity.city,
|
||||
'State': entity.state,
|
||||
'ZIP code': entity.zipCode,
|
||||
'Social Number': entity.socialNumber,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:krow/core/application/routing/routes.gr.dart';
|
||||
import 'package:krow/features/profile/wages_forms/domain/entities/edd_form_entity.dart';
|
||||
import 'package:krow/features/profile/wages_forms/domain/entities/i_nine_entity.dart';
|
||||
import 'package:krow/features/profile/wages_forms/domain/entities/offer_letter_entity.dart';
|
||||
import 'package:krow/features/profile/wages_forms/domain/entities/w_four_entity.dart';
|
||||
import 'package:krow/features/profile/wages_forms/domain/utils/wage_form_visitor.dart';
|
||||
import 'package:krow/features/profile/wages_forms/presentation/widgets/form_item_widget.dart';
|
||||
|
||||
class RoutingWageFormVisitor implements WageFormVisitor<Widget> {
|
||||
const RoutingWageFormVisitor();
|
||||
|
||||
@override
|
||||
Widget visitEddForm(EddFormEntity entity) {
|
||||
return FormItemWidget(
|
||||
formRoute: const EddFormRoute(),
|
||||
wageForm: entity,
|
||||
label: 'EDD',
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget visitINineForm(INineEntity entity) {
|
||||
return FormItemWidget(
|
||||
formRoute: const INineFormRoute(),
|
||||
wageForm: entity,
|
||||
label: 'I-9',
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget visitOfferLetterFrom(OfferLetterEntity entity) {
|
||||
return FormItemWidget(
|
||||
formRoute: const OfferLetterFormRoute(),
|
||||
wageForm: entity,
|
||||
label: 'Offer Letter & Agreement',
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget visitWFourForm(WFourEntity entity) {
|
||||
return FormItemWidget(
|
||||
formRoute: const WFourFormRoute(),
|
||||
wageForm: entity,
|
||||
label: 'W-4',
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import 'package:krow/features/profile/wages_forms/domain/entities/edd_form_entity.dart';
|
||||
import 'package:krow/features/profile/wages_forms/domain/entities/i_nine_entity.dart';
|
||||
import 'package:krow/features/profile/wages_forms/domain/entities/offer_letter_entity.dart';
|
||||
import 'package:krow/features/profile/wages_forms/domain/entities/w_four_entity.dart';
|
||||
import 'package:krow/features/profile/wages_forms/domain/utils/wage_form_visitor.dart';
|
||||
|
||||
class TitleWageFormVisitor implements WageFormVisitor<String> {
|
||||
const TitleWageFormVisitor({this.isPreview = false});
|
||||
|
||||
final bool isPreview;
|
||||
|
||||
@override
|
||||
String visitEddForm(EddFormEntity entity) {
|
||||
return 'Form EDD${isPreview ? ' Preview' : ''}';
|
||||
}
|
||||
|
||||
@override
|
||||
String visitINineForm(INineEntity entity) {
|
||||
return 'Form I-9${isPreview ? ' Preview' : ''}';
|
||||
}
|
||||
|
||||
@override
|
||||
String visitOfferLetterFrom(OfferLetterEntity entity) {
|
||||
return isPreview ? 'Form Offer Let... Preview' : 'Offer Letter & Agreement';
|
||||
}
|
||||
|
||||
@override
|
||||
String visitWFourForm(WFourEntity entity) {
|
||||
return 'Form W-4${isPreview ? ' Preview' : ''}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:krow/features/profile/wages_forms/domain/bloc/wages_form_bloc.dart';
|
||||
import 'package:krow/features/profile/wages_forms/domain/entities/wage_form_entity.dart';
|
||||
|
||||
mixin FormUpdaterMixin<T extends StatefulWidget, F extends WageFormEntity>
|
||||
on State<T> {
|
||||
late final formBloc = context.read<WagesFormBloc>();
|
||||
late F form;
|
||||
Timer? updateTimer;
|
||||
|
||||
void updateCurrentForm(F updatedForm) {
|
||||
updateTimer?.cancel();
|
||||
form = updatedForm;
|
||||
|
||||
updateTimer = Timer(
|
||||
const Duration(milliseconds: 500),
|
||||
() {
|
||||
formBloc.add(UpdateCurrentForm(form: form));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void handleSubmit() {
|
||||
formBloc.add(const ValidateCurrentForm());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:krow/core/application/common/str_extensions.dart';
|
||||
import 'package:krow/core/application/routing/routes.gr.dart';
|
||||
import 'package:krow/core/data/enums/state_status.dart';
|
||||
import 'package:krow/core/presentation/gen/assets.gen.dart';
|
||||
import 'package:krow/core/presentation/styles/kw_text_styles.dart';
|
||||
import 'package:krow/core/presentation/styles/theme.dart';
|
||||
import 'package:krow/core/presentation/widgets/ui_kit/kw_app_bar.dart';
|
||||
import 'package:krow/core/presentation/widgets/ui_kit/kw_button.dart';
|
||||
import 'package:krow/core/presentation/widgets/ui_kit/kw_date_selector.dart';
|
||||
import 'package:krow/core/presentation/widgets/ui_kit/kw_dropdown.dart';
|
||||
import 'package:krow/core/presentation/widgets/ui_kit/kw_input.dart';
|
||||
import 'package:krow/features/profile/wages_forms/domain/bloc/wages_form_bloc.dart';
|
||||
import 'package:krow/features/profile/wages_forms/domain/entities/edd_form_entity.dart';
|
||||
import 'package:krow/features/profile/wages_forms/presentation/mixins/form_updater_mixin.dart';
|
||||
import 'package:krow/features/profile/wages_forms/presentation/widgets/form_sign_holder_widget.dart';
|
||||
|
||||
@RoutePage()
|
||||
class EddFormScreen extends StatefulWidget {
|
||||
const EddFormScreen({super.key});
|
||||
|
||||
@override
|
||||
State<EddFormScreen> createState() => _EddFormScreenState();
|
||||
}
|
||||
|
||||
class _EddFormScreenState extends State<EddFormScreen>
|
||||
with FormUpdaterMixin<EddFormScreen, EddFormEntity> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
final activeForm = context.read<WagesFormBloc>().state.activeForm;
|
||||
form =
|
||||
activeForm is EddFormEntity ? activeForm : const EddFormEntity.empty();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: KwAppBar(
|
||||
titleText: 'EDD',
|
||||
showNotification: true,
|
||||
),
|
||||
body: BlocConsumer<WagesFormBloc, WagesFormState>(
|
||||
listenWhen: (previous, current) =>
|
||||
current.status == StateStatus.success,
|
||||
buildWhen: (previous, current) => current.status == StateStatus.error,
|
||||
listener: (context, state) {
|
||||
context.pushRoute(const SignedFormRoute());
|
||||
updateCurrentForm(form.copyWith(isSigned: true));
|
||||
},
|
||||
builder: (context, state) {
|
||||
final formErrors = state.formErrors;
|
||||
|
||||
return ListView(
|
||||
primary: false,
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
const Text(
|
||||
'Fill the form below:',
|
||||
style: AppTextStyles.bodySmallRegBlackGrey,
|
||||
),
|
||||
const Gap(24),
|
||||
KwTextInput(
|
||||
initialValue: form.lastName,
|
||||
onChanged: (lastName) {
|
||||
updateCurrentForm(form.copyWith(lastName: lastName));
|
||||
},
|
||||
title: 'Last Name (Family Name)',
|
||||
hintText: 'Enter your Last name',
|
||||
borderColor: AppColors.grayStroke,
|
||||
textInputAction: TextInputAction.next,
|
||||
keyboardType: TextInputType.name,
|
||||
textCapitalization: TextCapitalization.sentences,
|
||||
showError: formErrors.containsKey(WagesFormFields.lastName),
|
||||
helperText: formErrors[WagesFormFields.lastName],
|
||||
),
|
||||
const Gap(8),
|
||||
KwTextInput(
|
||||
initialValue: form.firstName,
|
||||
onChanged: (firstName) {
|
||||
updateCurrentForm(form.copyWith(firstName: firstName));
|
||||
},
|
||||
title: 'First Name (Given Name)',
|
||||
hintText: 'Enter your First name',
|
||||
borderColor: AppColors.grayStroke,
|
||||
textInputAction: TextInputAction.next,
|
||||
keyboardType: TextInputType.name,
|
||||
textCapitalization: TextCapitalization.sentences,
|
||||
showError: formErrors.containsKey(WagesFormFields.firstName),
|
||||
helperText: formErrors[WagesFormFields.firstName],
|
||||
),
|
||||
const Gap(8),
|
||||
KwTextInput(
|
||||
initialValue: form.address,
|
||||
onChanged: (address) {
|
||||
updateCurrentForm(form.copyWith(address: address));
|
||||
},
|
||||
title: 'Address',
|
||||
hintText: 'Enter your Address name',
|
||||
borderColor: AppColors.grayStroke,
|
||||
textInputAction: TextInputAction.next,
|
||||
keyboardType: TextInputType.name,
|
||||
textCapitalization: TextCapitalization.sentences,
|
||||
showError: formErrors.containsKey(WagesFormFields.address),
|
||||
helperText: formErrors[WagesFormFields.address],
|
||||
),
|
||||
const Gap(8),
|
||||
KwTextInput(
|
||||
initialValue: form.aptNumber?.toString(),
|
||||
onChanged: (apt) {
|
||||
updateCurrentForm(
|
||||
form.copyWith(aptNumber: int.tryParse(apt)),
|
||||
);
|
||||
},
|
||||
title: 'Apt. Number (if any)',
|
||||
hintText: 'Enter your Apt. Number',
|
||||
borderColor: AppColors.grayStroke,
|
||||
textInputAction: TextInputAction.next,
|
||||
keyboardType: TextInputType.number,
|
||||
showError: false,
|
||||
),
|
||||
const Gap(8),
|
||||
KwTextInput(
|
||||
initialValue: form.city,
|
||||
onChanged: (city) {
|
||||
updateCurrentForm(form.copyWith(city: city));
|
||||
},
|
||||
title: 'City or Town',
|
||||
hintText: 'Enter your City',
|
||||
borderColor: AppColors.grayStroke,
|
||||
textInputAction: TextInputAction.next,
|
||||
keyboardType: TextInputType.name,
|
||||
textCapitalization: TextCapitalization.sentences,
|
||||
showError: formErrors.containsKey(WagesFormFields.city),
|
||||
helperText: formErrors[WagesFormFields.city],
|
||||
),
|
||||
const Gap(8),
|
||||
KwDropdown<String>(
|
||||
title: 'State',
|
||||
horizontalPadding: 16,
|
||||
borderColor: AppColors.grayStroke,
|
||||
hintText: 'Select state',
|
||||
selectedItem: form.state.toDropDownItem(),
|
||||
items: [
|
||||
for (final state in usStates)
|
||||
KwDropDownItem(data: state, title: state),
|
||||
],
|
||||
onSelected: (state) {
|
||||
updateCurrentForm(form.copyWith(state: state));
|
||||
},
|
||||
showError: formErrors.containsKey(WagesFormFields.state),
|
||||
helperText: formErrors[WagesFormFields.state],
|
||||
),
|
||||
const Gap(8),
|
||||
KwTextInput(
|
||||
initialValue: form.zipCode,
|
||||
onChanged: (zipCode) {
|
||||
updateCurrentForm(form.copyWith(zipCode: zipCode));
|
||||
},
|
||||
title: 'ZIP Code',
|
||||
hintText: 'Enter your City\'s ZIP code',
|
||||
borderColor: AppColors.grayStroke,
|
||||
keyboardType: TextInputType.number,
|
||||
textInputAction: TextInputAction.next,
|
||||
showError: formErrors.containsKey(WagesFormFields.zipCode),
|
||||
helperText: formErrors[WagesFormFields.zipCode],
|
||||
),
|
||||
const Gap(8),
|
||||
KwTextInput(
|
||||
initialValue: form.socialNumber,
|
||||
onChanged: (socialNumber) {
|
||||
updateCurrentForm(form.copyWith(socialNumber: socialNumber));
|
||||
},
|
||||
title: 'U.S. Social Number',
|
||||
hintText: 'Enter your U.S. Social Number',
|
||||
borderColor: AppColors.grayStroke,
|
||||
textInputAction: TextInputAction.done,
|
||||
showError: formErrors.containsKey(WagesFormFields.socialNumber),
|
||||
helperText: formErrors[WagesFormFields.socialNumber],
|
||||
),
|
||||
const Gap(24),
|
||||
const FormSignHolderWidget(signingPageTitle: 'EDD'),
|
||||
const Gap(24),
|
||||
KwDateSelector(
|
||||
readOnly: true,
|
||||
title: 'Today’s Date',
|
||||
hintText: 'mm/dd/yyyy',
|
||||
initialValue: DateTime.now(),
|
||||
onSelect: (date) {},
|
||||
),
|
||||
const Gap(24),
|
||||
KwButton.outlinedPrimary(
|
||||
label: 'Document Preview',
|
||||
leftIcon: Assets.images.icons.eye,
|
||||
onPressed: () {
|
||||
context.pushRoute(const FormPreviewRoute());
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
bottomNavigationBar: SafeArea(
|
||||
top: false,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: KwButton.primary(label: 'Submit', onPressed: handleSubmit),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import 'package:auto_route/annotations.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:krow/core/presentation/styles/kw_text_styles.dart';
|
||||
import 'package:krow/core/presentation/styles/theme.dart';
|
||||
import 'package:krow/core/presentation/widgets/ui_kit/kw_app_bar.dart';
|
||||
import 'package:krow/features/profile/wages_forms/domain/bloc/wages_form_bloc.dart';
|
||||
import 'package:krow/features/profile/wages_forms/presentation/components/preview_wage_from_visitor.dart';
|
||||
import 'package:krow/features/profile/wages_forms/presentation/components/title_wage_form_visitor.dart';
|
||||
|
||||
@RoutePage()
|
||||
class FormPreviewScreen extends StatelessWidget {
|
||||
const FormPreviewScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final form = context.read<WagesFormBloc>().state.activeForm;
|
||||
const titleVisitor = TitleWageFormVisitor(isPreview: true);
|
||||
final previewData =
|
||||
form != null ? form.visit(const PreviewWageFormVisitor()) : {};
|
||||
|
||||
return Scaffold(
|
||||
appBar: KwAppBar(
|
||||
titleText: form != null ? form.visit(titleVisitor) : 'Preview',
|
||||
showNotification: true,
|
||||
),
|
||||
body: Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 24),
|
||||
decoration: const BoxDecoration(
|
||||
color: AppColors.grayPrimaryFrame,
|
||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
primary: false,
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
spacing: 16,
|
||||
children: [
|
||||
for (final entry in previewData.entries)
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'${entry.key}:',
|
||||
textAlign: TextAlign.start,
|
||||
style: AppTextStyles.bodyMediumMed.copyWith(
|
||||
color: AppColors.blackGray,
|
||||
),
|
||||
),
|
||||
const Gap(12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
entry.value,
|
||||
textAlign: TextAlign.end,
|
||||
style: AppTextStyles.bodyMediumMed,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:krow/core/presentation/styles/kw_box_decorations.dart';
|
||||
import 'package:krow/core/presentation/styles/kw_text_styles.dart';
|
||||
import 'package:krow/core/presentation/styles/theme.dart';
|
||||
import 'package:krow/core/presentation/widgets/ui_kit/kw_app_bar.dart';
|
||||
import 'package:krow/core/presentation/widgets/ui_kit/kw_button.dart';
|
||||
import 'package:krow/features/profile/wages_forms/domain/bloc/wages_form_bloc.dart';
|
||||
import 'package:krow/features/profile/wages_forms/domain/sign_image_service.dart';
|
||||
import 'package:krow/features/profile/wages_forms/presentation/widgets/sign_notice_widget.dart';
|
||||
import 'package:syncfusion_flutter_signaturepad/signaturepad.dart';
|
||||
|
||||
@RoutePage()
|
||||
class FormSignScreen extends StatefulWidget {
|
||||
const FormSignScreen({super.key, required this.title});
|
||||
|
||||
final String title;
|
||||
|
||||
@override
|
||||
State<FormSignScreen> createState() => _FormSignScreenState();
|
||||
}
|
||||
|
||||
class _FormSignScreenState extends State<FormSignScreen> {
|
||||
final GlobalKey<SfSignaturePadState> _signaturePadKey = GlobalKey();
|
||||
final _signNotifier = ValueNotifier<bool>(false);
|
||||
Timer? _signTrackingTimer;
|
||||
|
||||
void _startSignTracking() {
|
||||
_signNotifier.value = false;
|
||||
_signTrackingTimer?.cancel();
|
||||
_signTrackingTimer = Timer.periodic(const Duration(milliseconds: 300), (_) {
|
||||
_signNotifier.value =
|
||||
_signaturePadKey.currentState?.toPathList().isNotEmpty ?? false;
|
||||
|
||||
if (_signNotifier.value) _signTrackingTimer?.cancel();
|
||||
});
|
||||
}
|
||||
|
||||
void _handleOnSubmit() {
|
||||
final wagesBloc = context.read<WagesFormBloc>();
|
||||
SignImageService()
|
||||
.getSignPngBytes(_signaturePadKey.currentState!)
|
||||
.then((imageData) {
|
||||
wagesBloc.add(AddUserSignImage(imageData: imageData));
|
||||
});
|
||||
|
||||
context.router.maybePop();
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_startSignTracking();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: KwAppBar(
|
||||
titleText: widget.title,
|
||||
showNotification: true,
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Sign the document below:',
|
||||
style: AppTextStyles.bodySmallRegBlackGrey,
|
||||
),
|
||||
const Gap(24),
|
||||
Expanded(
|
||||
child: Stack(
|
||||
alignment: AlignmentDirectional.topEnd,
|
||||
children: [
|
||||
DecoratedBox(
|
||||
decoration: KwBoxDecorations.primaryLight8,
|
||||
child: SfSignaturePad(
|
||||
key: _signaturePadKey,
|
||||
),
|
||||
),
|
||||
ListenableBuilder(
|
||||
listenable: _signNotifier,
|
||||
builder: (context, _) {
|
||||
return _signNotifier.value
|
||||
? IconButton(
|
||||
onPressed: () {
|
||||
_signaturePadKey.currentState?.clear();
|
||||
_startSignTracking();
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.close,
|
||||
color: AppColors.bgColorDark,
|
||||
size: 24,
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink();
|
||||
},
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: ListenableBuilder(
|
||||
listenable: _signNotifier,
|
||||
builder: (context, _) {
|
||||
return _signNotifier.value
|
||||
? const SizedBox.shrink()
|
||||
: const Padding(
|
||||
padding: EdgeInsets.all(12),
|
||||
child: SignNoticeWidget(),
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
const Gap(28),
|
||||
],
|
||||
),
|
||||
),
|
||||
bottomNavigationBar: SafeArea(
|
||||
top: false,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: ListenableBuilder(
|
||||
listenable: _signNotifier,
|
||||
builder: (context, _) {
|
||||
return KwButton.primary(
|
||||
label: 'Submit and Back to Form',
|
||||
disabled: !_signNotifier.value,
|
||||
onPressed: _handleOnSubmit,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_signTrackingTimer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:krow/core/data/enums/state_status.dart';
|
||||
import 'package:krow/core/presentation/styles/kw_text_styles.dart';
|
||||
import 'package:krow/core/presentation/styles/theme.dart';
|
||||
import 'package:krow/core/presentation/widgets/ui_kit/kw_app_bar.dart';
|
||||
import 'package:krow/features/profile/wages_forms/domain/bloc/wages_form_bloc.dart';
|
||||
import 'package:krow/features/profile/wages_forms/presentation/components/routing_wage_form_visitor.dart';
|
||||
|
||||
@RoutePage()
|
||||
class FormsListScreen extends StatelessWidget {
|
||||
const FormsListScreen({super.key});
|
||||
|
||||
final _routingVisitor = const RoutingWageFormVisitor();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: KwAppBar(titleText: 'Wages Form', showNotification: true),
|
||||
body: BlocBuilder<WagesFormBloc, WagesFormState>(
|
||||
buildWhen: (previous, current) =>
|
||||
previous.actualForms != current.actualForms,
|
||||
builder: (context, state) {
|
||||
return ListView(
|
||||
primary: false,
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
Text(
|
||||
'View and manage your wage forms below. '
|
||||
'Select a form to review or update details.',
|
||||
style: AppTextStyles.bodySmallReg.copyWith(
|
||||
color: AppColors.blackGray,
|
||||
),
|
||||
),
|
||||
const Gap(24),
|
||||
if (state.status == StateStatus.loading &&
|
||||
state.actualForms.isEmpty)
|
||||
const Align(
|
||||
alignment: AlignmentDirectional.bottomCenter,
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
for (final entry in state.actualForms)
|
||||
entry.visit(_routingVisitor),
|
||||
const Gap(90),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:krow/core/application/common/str_extensions.dart';
|
||||
import 'package:krow/core/application/routing/routes.gr.dart';
|
||||
import 'package:krow/core/data/enums/state_status.dart';
|
||||
import 'package:krow/core/presentation/gen/assets.gen.dart';
|
||||
import 'package:krow/core/presentation/styles/kw_text_styles.dart';
|
||||
import 'package:krow/core/presentation/styles/theme.dart';
|
||||
import 'package:krow/core/presentation/widgets/ui_kit/kw_app_bar.dart';
|
||||
import 'package:krow/core/presentation/widgets/ui_kit/kw_button.dart';
|
||||
import 'package:krow/core/presentation/widgets/ui_kit/kw_date_selector.dart';
|
||||
import 'package:krow/core/presentation/widgets/ui_kit/kw_dropdown.dart';
|
||||
import 'package:krow/core/presentation/widgets/ui_kit/kw_input.dart';
|
||||
import 'package:krow/core/presentation/widgets/ui_kit/kw_phone_input.dart';
|
||||
import 'package:krow/features/profile/wages_forms/domain/bloc/wages_form_bloc.dart';
|
||||
import 'package:krow/features/profile/wages_forms/domain/entities/i_nine_entity.dart';
|
||||
import 'package:krow/features/profile/wages_forms/presentation/mixins/form_updater_mixin.dart';
|
||||
import 'package:krow/features/profile/wages_forms/presentation/widgets/form_sign_holder_widget.dart';
|
||||
|
||||
@RoutePage()
|
||||
class INineFormScreen extends StatefulWidget {
|
||||
const INineFormScreen({super.key});
|
||||
|
||||
@override
|
||||
State<INineFormScreen> createState() => _INineFormScreenState();
|
||||
}
|
||||
|
||||
class _INineFormScreenState extends State<INineFormScreen>
|
||||
with FormUpdaterMixin<INineFormScreen, INineEntity> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
final activeForm = context.read<WagesFormBloc>().state.activeForm;
|
||||
form = activeForm is INineEntity ? activeForm : const INineEntity.empty();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final currentData = DateTime.now();
|
||||
|
||||
return Scaffold(
|
||||
appBar: KwAppBar(
|
||||
titleText: 'I-9',
|
||||
showNotification: true,
|
||||
),
|
||||
body: BlocConsumer<WagesFormBloc, WagesFormState>(
|
||||
listenWhen: (previous, current) =>
|
||||
current.status == StateStatus.success,
|
||||
buildWhen: (previous, current) => current.status == StateStatus.error,
|
||||
listener: (context, state) {
|
||||
context.pushRoute(const SignedFormRoute());
|
||||
updateCurrentForm(form.copyWith(isSigned: true));
|
||||
},
|
||||
builder: (context, state) {
|
||||
final formErrors = state.formErrors;
|
||||
|
||||
return ListView(
|
||||
primary: false,
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
const Text(
|
||||
'Fill the form below:',
|
||||
style: AppTextStyles.bodySmallRegBlackGrey,
|
||||
),
|
||||
const Gap(24),
|
||||
KwTextInput(
|
||||
initialValue: form.lastName,
|
||||
onChanged: (lastName) {
|
||||
updateCurrentForm(form.copyWith(lastName: lastName));
|
||||
},
|
||||
title: 'Last Name (Family Name)',
|
||||
hintText: 'Enter your Last name',
|
||||
borderColor: AppColors.grayStroke,
|
||||
textInputAction: TextInputAction.next,
|
||||
keyboardType: TextInputType.name,
|
||||
textCapitalization: TextCapitalization.sentences,
|
||||
showError: formErrors.containsKey(WagesFormFields.lastName),
|
||||
helperText: formErrors[WagesFormFields.lastName],
|
||||
),
|
||||
const Gap(8),
|
||||
KwTextInput(
|
||||
initialValue: form.firstName,
|
||||
onChanged: (firstName) {
|
||||
updateCurrentForm(form.copyWith(firstName: firstName));
|
||||
},
|
||||
title: 'First Name (Given Name)',
|
||||
hintText: 'Enter your First name',
|
||||
borderColor: AppColors.grayStroke,
|
||||
textInputAction: TextInputAction.next,
|
||||
keyboardType: TextInputType.name,
|
||||
textCapitalization: TextCapitalization.sentences,
|
||||
showError: formErrors.containsKey(WagesFormFields.firstName),
|
||||
helperText: formErrors[WagesFormFields.firstName],
|
||||
),
|
||||
const Gap(8),
|
||||
KwTextInput(
|
||||
initialValue: form.middleName,
|
||||
onChanged: (middleName) {
|
||||
updateCurrentForm(form.copyWith(middleName: middleName));
|
||||
},
|
||||
title: 'Middle Initial (if any)',
|
||||
hintText: 'Enter your Middle name',
|
||||
borderColor: AppColors.grayStroke,
|
||||
textInputAction: TextInputAction.next,
|
||||
keyboardType: TextInputType.name,
|
||||
textCapitalization: TextCapitalization.sentences,
|
||||
),
|
||||
const Gap(8),
|
||||
KwTextInput(
|
||||
initialValue: form.address,
|
||||
onChanged: (address) {
|
||||
updateCurrentForm(form.copyWith(address: address));
|
||||
},
|
||||
title: 'Address',
|
||||
hintText: 'Enter your Address name',
|
||||
borderColor: AppColors.grayStroke,
|
||||
textInputAction: TextInputAction.next,
|
||||
keyboardType: TextInputType.name,
|
||||
textCapitalization: TextCapitalization.sentences,
|
||||
showError: formErrors.containsKey(WagesFormFields.address),
|
||||
helperText: formErrors[WagesFormFields.address],
|
||||
),
|
||||
const Gap(8),
|
||||
KwTextInput(
|
||||
initialValue: form.aptNumber?.toString(),
|
||||
onChanged: (apt) {
|
||||
updateCurrentForm(
|
||||
form.copyWith(aptNumber: int.tryParse(apt)),
|
||||
);
|
||||
},
|
||||
title: 'Apt. Number (if any)',
|
||||
hintText: 'Enter your Apt. Number',
|
||||
borderColor: AppColors.grayStroke,
|
||||
textInputAction: TextInputAction.next,
|
||||
keyboardType: TextInputType.number,
|
||||
showError: false,
|
||||
),
|
||||
const Gap(8),
|
||||
KwTextInput(
|
||||
initialValue: form.city,
|
||||
onChanged: (city) {
|
||||
updateCurrentForm(form.copyWith(city: city));
|
||||
},
|
||||
title: 'City or Town',
|
||||
hintText: 'Enter your City',
|
||||
borderColor: AppColors.grayStroke,
|
||||
textInputAction: TextInputAction.next,
|
||||
keyboardType: TextInputType.name,
|
||||
textCapitalization: TextCapitalization.sentences,
|
||||
showError: formErrors.containsKey(WagesFormFields.city),
|
||||
helperText: formErrors[WagesFormFields.city],
|
||||
),
|
||||
const Gap(8),
|
||||
KwDropdown<String>(
|
||||
title: 'State',
|
||||
horizontalPadding: 16,
|
||||
borderColor: AppColors.grayStroke,
|
||||
hintText: 'Select state',
|
||||
selectedItem: form.state.toDropDownItem(),
|
||||
items: [
|
||||
for (final state in usStates)
|
||||
KwDropDownItem(data: state, title: state),
|
||||
],
|
||||
onSelected: (state) {
|
||||
updateCurrentForm(form.copyWith(state: state));
|
||||
},
|
||||
showError: formErrors.containsKey(WagesFormFields.state),
|
||||
helperText: formErrors[WagesFormFields.state],
|
||||
),
|
||||
const Gap(8),
|
||||
KwTextInput(
|
||||
initialValue: form.zipCode,
|
||||
onChanged: (zipCode) {
|
||||
updateCurrentForm(form.copyWith(zipCode: zipCode));
|
||||
},
|
||||
title: 'ZIP Code',
|
||||
hintText: 'Enter your City\'s ZIP code',
|
||||
borderColor: AppColors.grayStroke,
|
||||
keyboardType: TextInputType.number,
|
||||
textInputAction: TextInputAction.next,
|
||||
showError: formErrors.containsKey(WagesFormFields.zipCode),
|
||||
helperText: formErrors[WagesFormFields.zipCode],
|
||||
),
|
||||
const Gap(8),
|
||||
KwDateSelector(
|
||||
title: 'Date of Birth',
|
||||
hintText: 'mm/dd/yyyy',
|
||||
initialValue: form.dateOfBirth,
|
||||
firstDate: currentData.subtract(const Duration(days: 36500)),
|
||||
lastDate: currentData.subtract(const Duration(days: 3650)),
|
||||
initialDate: currentData.subtract(const Duration(days: 6570)),
|
||||
onSelect: (date) {
|
||||
updateCurrentForm(form.copyWith(dateOfBirth: date));
|
||||
},
|
||||
showError: formErrors.containsKey(WagesFormFields.birthdate),
|
||||
helperText: formErrors[WagesFormFields.birthdate],
|
||||
),
|
||||
const Gap(8),
|
||||
KwTextInput(
|
||||
initialValue: form.socialNumber,
|
||||
onChanged: (socialNumber) {
|
||||
updateCurrentForm(form.copyWith(socialNumber: socialNumber));
|
||||
},
|
||||
title: 'U.S. Social Number',
|
||||
hintText: 'Enter your U.S. Social Number',
|
||||
borderColor: AppColors.grayStroke,
|
||||
textInputAction: TextInputAction.next,
|
||||
showError: formErrors.containsKey(WagesFormFields.socialNumber),
|
||||
helperText: formErrors[WagesFormFields.socialNumber],
|
||||
),
|
||||
const Gap(8),
|
||||
KwTextInput(
|
||||
initialValue: form.email,
|
||||
onChanged: (email) {
|
||||
updateCurrentForm(form.copyWith(email: email));
|
||||
},
|
||||
title: 'Employee’s Email Address',
|
||||
hintText: 'Enter your Email',
|
||||
borderColor: AppColors.grayStroke,
|
||||
textInputAction: TextInputAction.next,
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
showError: formErrors.containsKey(WagesFormFields.email),
|
||||
helperText: formErrors[WagesFormFields.email],
|
||||
),
|
||||
const Gap(8),
|
||||
KwPhoneInput(
|
||||
title: 'Phone Number',
|
||||
initialValue: form.phoneNumber,
|
||||
onChanged: (phone) {
|
||||
updateCurrentForm(form.copyWith(phoneNumber: phone));
|
||||
},
|
||||
borderColor: AppColors.grayStroke,
|
||||
showError: formErrors.containsKey(WagesFormFields.phone),
|
||||
helperText: formErrors[WagesFormFields.phone],
|
||||
),
|
||||
const Gap(24),
|
||||
const FormSignHolderWidget(signingPageTitle: 'I-9'),
|
||||
const Gap(24),
|
||||
KwDateSelector(
|
||||
readOnly: true,
|
||||
title: 'Today’s Date',
|
||||
hintText: 'mm/dd/yyyy',
|
||||
initialValue: currentData,
|
||||
onSelect: (date) {},
|
||||
),
|
||||
const Gap(24),
|
||||
KwButton.outlinedPrimary(
|
||||
label: 'Document Preview',
|
||||
leftIcon: Assets.images.icons.eye,
|
||||
onPressed: () {
|
||||
context.pushRoute(const FormPreviewRoute());
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
bottomNavigationBar: SafeArea(
|
||||
top: false,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: KwButton.primary(label: 'Submit', onPressed: handleSubmit),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:krow/core/application/routing/routes.gr.dart';
|
||||
import 'package:krow/core/data/enums/state_status.dart';
|
||||
import 'package:krow/core/presentation/gen/assets.gen.dart';
|
||||
import 'package:krow/core/presentation/styles/kw_text_styles.dart';
|
||||
import 'package:krow/core/presentation/styles/theme.dart';
|
||||
import 'package:krow/core/presentation/widgets/ui_kit/kw_app_bar.dart';
|
||||
import 'package:krow/core/presentation/widgets/ui_kit/kw_button.dart';
|
||||
import 'package:krow/core/presentation/widgets/ui_kit/kw_input.dart';
|
||||
import 'package:krow/features/profile/wages_forms/domain/bloc/wages_form_bloc.dart';
|
||||
import 'package:krow/features/profile/wages_forms/domain/entities/offer_letter_entity.dart';
|
||||
import 'package:krow/features/profile/wages_forms/presentation/mixins/form_updater_mixin.dart';
|
||||
import 'package:krow/features/profile/wages_forms/presentation/widgets/form_sign_holder_widget.dart';
|
||||
|
||||
@RoutePage()
|
||||
class OfferLetterFormScreen extends StatefulWidget {
|
||||
const OfferLetterFormScreen({super.key});
|
||||
|
||||
@override
|
||||
State<OfferLetterFormScreen> createState() => _OfferLetterFormScreenState();
|
||||
}
|
||||
|
||||
class _OfferLetterFormScreenState extends State<OfferLetterFormScreen>
|
||||
with FormUpdaterMixin<OfferLetterFormScreen, OfferLetterEntity> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
final activeForm = context.read<WagesFormBloc>().state.activeForm;
|
||||
form = activeForm is OfferLetterEntity
|
||||
? activeForm
|
||||
: const OfferLetterEntity.empty();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: KwAppBar(
|
||||
titleText: 'Offer Letter & Agreement',
|
||||
showNotification: true,
|
||||
),
|
||||
body: BlocConsumer<WagesFormBloc, WagesFormState>(
|
||||
listenWhen: (previous, current) =>
|
||||
current.status == StateStatus.success,
|
||||
buildWhen: (previous, current) => current.status == StateStatus.error,
|
||||
listener: (context, state) {
|
||||
context.pushRoute(
|
||||
const SignedFormRoute(),
|
||||
);
|
||||
updateCurrentForm(form.copyWith(isSigned: true));
|
||||
},
|
||||
builder: (context, state) {
|
||||
final formErrors = state.formErrors;
|
||||
|
||||
return ListView(
|
||||
primary: false,
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
const Text(
|
||||
'Fill the form below:',
|
||||
style: AppTextStyles.bodySmallRegBlackGrey,
|
||||
),
|
||||
const Gap(24),
|
||||
KwTextInput(
|
||||
initialValue: form.fullName,
|
||||
onChanged: (fullName) {
|
||||
updateCurrentForm(form.copyWith(fullName: fullName));
|
||||
},
|
||||
title: 'Full Name',
|
||||
hintText: 'Enter your full name',
|
||||
textInputAction: TextInputAction.next,
|
||||
textCapitalization: TextCapitalization.sentences,
|
||||
keyboardType: TextInputType.name,
|
||||
borderColor: AppColors.grayStroke,
|
||||
showError: formErrors.containsKey(WagesFormFields.fullName),
|
||||
helperText: formErrors[WagesFormFields.fullName],
|
||||
),
|
||||
const Gap(8),
|
||||
KwTextInput(
|
||||
initialValue: form.city,
|
||||
onChanged: (city) {
|
||||
updateCurrentForm(form.copyWith(city: city));
|
||||
},
|
||||
title: 'City',
|
||||
hintText: 'Enter your City',
|
||||
textInputAction: TextInputAction.next,
|
||||
textCapitalization: TextCapitalization.sentences,
|
||||
keyboardType: TextInputType.name,
|
||||
borderColor: AppColors.grayStroke,
|
||||
showError: formErrors.containsKey(WagesFormFields.city),
|
||||
helperText: formErrors[WagesFormFields.city],
|
||||
),
|
||||
const Gap(24),
|
||||
const Text(
|
||||
'By signing below, you agree you have read and '
|
||||
'understood the following documents:',
|
||||
style: AppTextStyles.bodySmallMed,
|
||||
),
|
||||
const Gap(12),
|
||||
Text(
|
||||
' • Advantage Workforce Services (“AWS”) Offer Letter',
|
||||
style: AppTextStyles.bodySmallMed.copyWith(
|
||||
color: AppColors.primaryBlue,
|
||||
),
|
||||
),
|
||||
const Gap(12),
|
||||
Text(
|
||||
' • Arbitration Agreements',
|
||||
style: AppTextStyles.bodySmallMed.copyWith(
|
||||
color: AppColors.primaryBlue,
|
||||
),
|
||||
),
|
||||
const Gap(12),
|
||||
Text(
|
||||
' • Employee Policies',
|
||||
style: AppTextStyles.bodySmallMed.copyWith(
|
||||
color: AppColors.primaryBlue,
|
||||
),
|
||||
),
|
||||
const Gap(24),
|
||||
const FormSignHolderWidget(
|
||||
signingPageTitle: 'Offer Letter & Agreement',
|
||||
),
|
||||
const Gap(24),
|
||||
KwButton.outlinedPrimary(
|
||||
label: 'Document Preview',
|
||||
leftIcon: Assets.images.icons.eye,
|
||||
onPressed: () {
|
||||
context.pushRoute(const FormPreviewRoute());
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
bottomNavigationBar: SafeArea(
|
||||
top: false,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: KwButton.primary(label: 'Submit', onPressed: handleSubmit),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:krow/core/application/routing/routes.gr.dart';
|
||||
import 'package:krow/core/data/enums/state_status.dart';
|
||||
import 'package:krow/core/presentation/gen/assets.gen.dart';
|
||||
import 'package:krow/core/presentation/styles/kw_text_styles.dart';
|
||||
import 'package:krow/core/presentation/styles/theme.dart';
|
||||
import 'package:krow/core/presentation/widgets/ui_kit/kw_app_bar.dart';
|
||||
import 'package:krow/core/presentation/widgets/ui_kit/kw_button.dart';
|
||||
import 'package:krow/core/presentation/widgets/ui_kit/kw_loading_overlay.dart';
|
||||
import 'package:krow/features/profile/wages_forms/domain/bloc/wages_form_bloc.dart';
|
||||
import 'package:krow/features/profile/wages_forms/presentation/components/title_wage_form_visitor.dart';
|
||||
|
||||
@RoutePage()
|
||||
class SignedFormScreen extends StatelessWidget {
|
||||
const SignedFormScreen({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final form = context.read<WagesFormBloc>().state.activeForm;
|
||||
const titleVisitor = TitleWageFormVisitor();
|
||||
|
||||
return Scaffold(
|
||||
appBar: KwAppBar(
|
||||
titleText: form != null ? form.visit(titleVisitor) : 'Form',
|
||||
showNotification: true,
|
||||
),
|
||||
body: ListView(
|
||||
primary: false,
|
||||
padding: const EdgeInsetsDirectional.fromSTEB(16, 4, 16, 60),
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: const BoxDecoration(
|
||||
color: AppColors.grayPrimaryFrame,
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(12),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
form?.name ?? 'Form',
|
||||
style: AppTextStyles.headingH3,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const Gap(12),
|
||||
DecoratedBox(
|
||||
decoration: const BoxDecoration(
|
||||
color: AppColors.statusSuccess,
|
||||
borderRadius: BorderRadius.all(Radius.circular(32)),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 6,
|
||||
),
|
||||
child: Text(
|
||||
'Signed',
|
||||
style: AppTextStyles.bodySmallMed
|
||||
.copyWith(color: AppColors.grayWhite),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
const Gap(12),
|
||||
const Divider(color: AppColors.grayTintStroke),
|
||||
const Gap(12),
|
||||
const Text('SIGNED DATE', style: AppTextStyles.captionReg),
|
||||
const Gap(8),
|
||||
Text(
|
||||
DateFormat('M/d/y').format(DateTime.now()),
|
||||
style: AppTextStyles.bodyMediumMed,
|
||||
),
|
||||
const Gap(24),
|
||||
const Text('SIGNATURE', style: AppTextStyles.captionReg),
|
||||
const Gap(6),
|
||||
Container(
|
||||
height: 106,
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.graySecondaryFrame,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Align(
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
child: Image.memory(
|
||||
context.read<WagesFormBloc>().state.signImageData ??
|
||||
Uint8List(0),
|
||||
),
|
||||
),
|
||||
),
|
||||
const Gap(6),
|
||||
const Divider(
|
||||
height: 1,
|
||||
color: AppColors.grayTintStroke,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Gap(12),
|
||||
KwButton.outlinedPrimary(
|
||||
label: 'Document Preview',
|
||||
leftIcon: Assets.images.icons.eye,
|
||||
onPressed: () {
|
||||
context.pushRoute(const FormPreviewRoute());
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
bottomNavigationBar: SafeArea(
|
||||
top: false,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: BlocConsumer<WagesFormBloc, WagesFormState>(
|
||||
listenWhen: (previous, current) =>
|
||||
current.status == StateStatus.success,
|
||||
buildWhen: (previous, current) => current.status != previous.status,
|
||||
listener: (context, state) {
|
||||
context.router.popUntilRoot();
|
||||
},
|
||||
builder: (context, state) {
|
||||
return KwLoadingOverlay(
|
||||
shouldShowLoading: state.status == StateStatus.loading,
|
||||
child: KwButton.primary(
|
||||
label: 'Submit',
|
||||
onPressed: () {
|
||||
context
|
||||
.read<WagesFormBloc>()
|
||||
.add(const SubmitCurrentForm());
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:krow/core/application/common/str_extensions.dart';
|
||||
import 'package:krow/core/application/routing/routes.gr.dart';
|
||||
import 'package:krow/core/data/enums/state_status.dart';
|
||||
import 'package:krow/core/presentation/gen/assets.gen.dart';
|
||||
import 'package:krow/core/presentation/styles/kw_text_styles.dart';
|
||||
import 'package:krow/core/presentation/styles/theme.dart';
|
||||
import 'package:krow/core/presentation/widgets/ui_kit/kw_app_bar.dart';
|
||||
import 'package:krow/core/presentation/widgets/ui_kit/kw_button.dart';
|
||||
import 'package:krow/core/presentation/widgets/ui_kit/kw_date_selector.dart';
|
||||
import 'package:krow/core/presentation/widgets/ui_kit/kw_dropdown.dart';
|
||||
import 'package:krow/core/presentation/widgets/ui_kit/kw_input.dart';
|
||||
import 'package:krow/features/profile/wages_forms/domain/bloc/wages_form_bloc.dart';
|
||||
import 'package:krow/features/profile/wages_forms/domain/entities/w_four_entity.dart';
|
||||
import 'package:krow/features/profile/wages_forms/presentation/mixins/form_updater_mixin.dart';
|
||||
import 'package:krow/features/profile/wages_forms/presentation/widgets/form_sign_holder_widget.dart';
|
||||
|
||||
@RoutePage()
|
||||
class WFourFormScreen extends StatefulWidget {
|
||||
const WFourFormScreen({super.key});
|
||||
|
||||
@override
|
||||
State<WFourFormScreen> createState() => _WFourFormScreenState();
|
||||
}
|
||||
|
||||
class _WFourFormScreenState extends State<WFourFormScreen>
|
||||
with FormUpdaterMixin<WFourFormScreen, WFourEntity> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
final activeForm = context.read<WagesFormBloc>().state.activeForm;
|
||||
form = activeForm is WFourEntity ? activeForm : const WFourEntity.empty();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: KwAppBar(
|
||||
titleText: 'W-4',
|
||||
showNotification: true,
|
||||
),
|
||||
body: BlocConsumer<WagesFormBloc, WagesFormState>(
|
||||
listenWhen: (previous, current) =>
|
||||
current.status == StateStatus.success,
|
||||
buildWhen: (previous, current) => current.status == StateStatus.error,
|
||||
listener: (context, state) {
|
||||
context.pushRoute(const SignedFormRoute());
|
||||
updateCurrentForm(form.copyWith(isSigned: true));
|
||||
},
|
||||
builder: (context, state) {
|
||||
final formErrors = state.formErrors;
|
||||
|
||||
return ListView(
|
||||
primary: false,
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
const Text(
|
||||
'Fill the form below:',
|
||||
style: AppTextStyles.bodySmallRegBlackGrey,
|
||||
),
|
||||
const Gap(24),
|
||||
KwTextInput(
|
||||
initialValue: form.lastName,
|
||||
onChanged: (lastName) {
|
||||
updateCurrentForm(form.copyWith(lastName: lastName));
|
||||
},
|
||||
title: 'Last Name (Family Name)',
|
||||
hintText: 'Enter your Last name',
|
||||
borderColor: AppColors.grayStroke,
|
||||
textInputAction: TextInputAction.next,
|
||||
keyboardType: TextInputType.name,
|
||||
textCapitalization: TextCapitalization.sentences,
|
||||
showError: formErrors.containsKey(WagesFormFields.lastName),
|
||||
helperText: formErrors[WagesFormFields.lastName],
|
||||
),
|
||||
const Gap(8),
|
||||
KwTextInput(
|
||||
initialValue: form.firstName,
|
||||
onChanged: (firstName) {
|
||||
updateCurrentForm(form.copyWith(firstName: firstName));
|
||||
},
|
||||
title: 'First Name (Given Name)',
|
||||
hintText: 'Enter your First name',
|
||||
borderColor: AppColors.grayStroke,
|
||||
textInputAction: TextInputAction.next,
|
||||
keyboardType: TextInputType.name,
|
||||
textCapitalization: TextCapitalization.sentences,
|
||||
showError: formErrors.containsKey(WagesFormFields.firstName),
|
||||
helperText: formErrors[WagesFormFields.firstName],
|
||||
),
|
||||
const Gap(8),
|
||||
KwTextInput(
|
||||
initialValue: form.middleName,
|
||||
onChanged: (middleName) {
|
||||
updateCurrentForm(form.copyWith(middleName: middleName));
|
||||
},
|
||||
title: 'Middle Initial (if any)',
|
||||
hintText: 'Enter your Middle name',
|
||||
borderColor: AppColors.grayStroke,
|
||||
textInputAction: TextInputAction.next,
|
||||
keyboardType: TextInputType.name,
|
||||
textCapitalization: TextCapitalization.sentences,
|
||||
),
|
||||
const Gap(8),
|
||||
KwTextInput(
|
||||
initialValue: form.address,
|
||||
onChanged: (address) {
|
||||
updateCurrentForm(form.copyWith(address: address));
|
||||
},
|
||||
title: 'Address',
|
||||
hintText: 'Enter your Address name',
|
||||
borderColor: AppColors.grayStroke,
|
||||
textInputAction: TextInputAction.next,
|
||||
keyboardType: TextInputType.name,
|
||||
textCapitalization: TextCapitalization.sentences,
|
||||
showError: formErrors.containsKey(WagesFormFields.address),
|
||||
helperText: formErrors[WagesFormFields.address],
|
||||
),
|
||||
const Gap(8),
|
||||
KwTextInput(
|
||||
initialValue: form.city,
|
||||
onChanged: (city) {
|
||||
updateCurrentForm(form.copyWith(city: city));
|
||||
},
|
||||
title: 'City or Town',
|
||||
hintText: 'Enter your City',
|
||||
borderColor: AppColors.grayStroke,
|
||||
textInputAction: TextInputAction.next,
|
||||
keyboardType: TextInputType.name,
|
||||
textCapitalization: TextCapitalization.sentences,
|
||||
showError: formErrors.containsKey(WagesFormFields.city),
|
||||
helperText: formErrors[WagesFormFields.city],
|
||||
),
|
||||
const Gap(8),
|
||||
KwDropdown<String>(
|
||||
title: 'State',
|
||||
horizontalPadding: 16,
|
||||
borderColor: AppColors.grayStroke,
|
||||
hintText: 'Select state',
|
||||
selectedItem: form.state.toDropDownItem(),
|
||||
items: [
|
||||
for (final state in usStates)
|
||||
KwDropDownItem(data: state, title: state),
|
||||
],
|
||||
onSelected: (state) {
|
||||
updateCurrentForm(form.copyWith(state: state));
|
||||
},
|
||||
showError: formErrors.containsKey(WagesFormFields.state),
|
||||
helperText: formErrors[WagesFormFields.state],
|
||||
),
|
||||
const Gap(8),
|
||||
KwTextInput(
|
||||
initialValue: form.zipCode,
|
||||
onChanged: (zipCode) {
|
||||
updateCurrentForm(form.copyWith(zipCode: zipCode));
|
||||
},
|
||||
title: 'ZIP Code',
|
||||
hintText: 'Enter your City\'s ZIP code',
|
||||
borderColor: AppColors.grayStroke,
|
||||
keyboardType: TextInputType.number,
|
||||
textInputAction: TextInputAction.next,
|
||||
showError: formErrors.containsKey(WagesFormFields.zipCode),
|
||||
helperText: formErrors[WagesFormFields.zipCode],
|
||||
),
|
||||
const Gap(8),
|
||||
KwTextInput(
|
||||
initialValue: form.socialNumber,
|
||||
onChanged: (socialNumber) {
|
||||
updateCurrentForm(form.copyWith(socialNumber: socialNumber));
|
||||
},
|
||||
title: 'U.S. Social Number',
|
||||
hintText: 'Enter your U.S. Social Number',
|
||||
borderColor: AppColors.grayStroke,
|
||||
textInputAction: TextInputAction.next,
|
||||
showError: formErrors.containsKey(WagesFormFields.socialNumber),
|
||||
helperText: formErrors[WagesFormFields.socialNumber],
|
||||
),
|
||||
const Gap(24),
|
||||
const FormSignHolderWidget(signingPageTitle: 'W-4'),
|
||||
const Gap(24),
|
||||
KwDateSelector(
|
||||
readOnly: true,
|
||||
title: 'Today’s Date',
|
||||
hintText: 'mm/dd/yyyy',
|
||||
initialValue: DateTime.now(),
|
||||
onSelect: (date) {},
|
||||
),
|
||||
const Gap(24),
|
||||
KwButton.outlinedPrimary(
|
||||
label: 'Document Preview',
|
||||
leftIcon: Assets.images.icons.eye,
|
||||
onPressed: () {
|
||||
context.pushRoute(const FormPreviewRoute());
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
bottomNavigationBar: SafeArea(
|
||||
top: false,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: KwButton.primary(label: 'Submit', onPressed: handleSubmit),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:krow/features/profile/wages_forms/domain/bloc/wages_form_bloc.dart';
|
||||
|
||||
@RoutePage()
|
||||
class WagesFormsFlowScreen extends StatelessWidget {
|
||||
const WagesFormsFlowScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) =>
|
||||
WagesFormBloc()..add(const InitializeWagesFormBloc()),
|
||||
child: const AutoRouter(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:krow/core/presentation/gen/assets.gen.dart';
|
||||
import 'package:krow/core/presentation/styles/kw_box_decorations.dart';
|
||||
import 'package:krow/core/presentation/styles/kw_text_styles.dart';
|
||||
import 'package:krow/features/profile/wages_forms/domain/bloc/wages_form_bloc.dart';
|
||||
import 'package:krow/features/profile/wages_forms/domain/entities/wage_form_entity.dart';
|
||||
|
||||
class FormItemWidget extends StatelessWidget {
|
||||
const FormItemWidget({
|
||||
super.key,
|
||||
required this.formRoute,
|
||||
required this.label,
|
||||
required this.wageForm,
|
||||
});
|
||||
|
||||
final PageRouteInfo<Object?> formRoute;
|
||||
final WageFormEntity wageForm;
|
||||
final String label;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
context.pushRoute(formRoute);
|
||||
|
||||
context.read<WagesFormBloc>().add(SetActiveForm(form: wageForm));
|
||||
},
|
||||
child: Container(
|
||||
height: 56,
|
||||
decoration: KwBoxDecorations.primaryLight8,
|
||||
margin: const EdgeInsets.only(bottom: 8),
|
||||
padding: const EdgeInsetsDirectional.fromSTEB(12, 16, 12, 16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: AppTextStyles.headingH3,
|
||||
),
|
||||
Assets.images.icons.caretRight.svg(width: 24, height: 24)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:krow/core/application/routing/routes.gr.dart';
|
||||
import 'package:krow/core/data/enums/state_status.dart';
|
||||
import 'package:krow/core/presentation/styles/theme.dart';
|
||||
import 'package:krow/features/profile/wages_forms/domain/bloc/wages_form_bloc.dart';
|
||||
import 'package:krow/features/profile/wages_forms/presentation/widgets/sign_notice_widget.dart';
|
||||
|
||||
class FormSignHolderWidget extends StatelessWidget {
|
||||
const FormSignHolderWidget({super.key, required this.signingPageTitle});
|
||||
|
||||
final String signingPageTitle;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () => context.router.push(FormSignRoute(title: signingPageTitle)),
|
||||
child: BlocBuilder<WagesFormBloc, WagesFormState>(
|
||||
buildWhen: (previous, current) =>
|
||||
previous.signImageData != current.signImageData ||
|
||||
current.status == StateStatus.error,
|
||||
builder: (context, state) {
|
||||
final signImage = state.signImageData;
|
||||
final shouldShowError =
|
||||
state.status == StateStatus.error && signImage == null;
|
||||
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.grayPrimaryFrame,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: shouldShowError
|
||||
? Border.all(color: AppColors.statusError)
|
||||
: null,
|
||||
),
|
||||
height: 106,
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: signImage == null
|
||||
? const SignNoticeWidget()
|
||||
: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Align(
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
child: Image.memory(signImage),
|
||||
),
|
||||
),
|
||||
const Gap(6),
|
||||
const Divider(
|
||||
height: 1,
|
||||
color: AppColors.grayTintStroke,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:krow/core/presentation/gen/assets.gen.dart';
|
||||
import 'package:krow/core/presentation/styles/kw_text_styles.dart';
|
||||
import 'package:krow/core/presentation/styles/theme.dart';
|
||||
|
||||
class SignNoticeWidget extends StatelessWidget {
|
||||
const SignNoticeWidget({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Assets.images.icons.edit.svg(),
|
||||
const Gap(6),
|
||||
const Text(
|
||||
'Tap to Sign',
|
||||
style: AppTextStyles.bodyMediumMed,
|
||||
)
|
||||
],
|
||||
),
|
||||
const Gap(12),
|
||||
const Divider(
|
||||
height: 1,
|
||||
color: AppColors.grayTintStroke,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user