fix: add ignore_for_file to data connect Repos and modify CI to avoid analyzing deleted files

This commit is contained in:
2026-02-20 19:51:44 +05:30
parent 24835f127e
commit 474be43448
259 changed files with 1810 additions and 1714 deletions

View File

@@ -6,13 +6,13 @@ import 'package:krow_core/core.dart';
import '../../domain/repositories/place_repository.dart';
class PlaceRepositoryImpl implements PlaceRepository {
final http.Client _client;
PlaceRepositoryImpl({http.Client? client}) : _client = client ?? http.Client();
final http.Client _client;
@override
Future<List<String>> searchCities(String query) async {
if (query.isEmpty) return [];
if (query.isEmpty) return <String>[];
final Uri uri = Uri.https(
'maps.googleapis.com',
@@ -39,7 +39,7 @@ class PlaceRepositoryImpl implements PlaceRepository {
} else {
// Handle other statuses (OVER_QUERY_LIMIT, REQUEST_DENIED, etc.)
// Returning empty list for now to avoid crashing UI, ideally log this.
return [];
return <String>[];
}
} else {
throw Exception('Network Error: ${response.statusCode}');

View File

@@ -5,9 +5,9 @@ import 'package:firebase_auth/firebase_auth.dart' as auth;
import '../../domain/repositories/profile_setup_repository.dart';
class ProfileSetupRepositoryImpl implements ProfileSetupRepository {
final DataConnectService _service;
ProfileSetupRepositoryImpl() : _service = DataConnectService.instance;
final DataConnectService _service;
@override
Future<void> submitProfile({

View File

@@ -4,13 +4,13 @@ import 'package:krow_core/core.dart';
///
/// Encapsulates the phone number needed to initiate the sign-in process.
class SignInWithPhoneArguments extends UseCaseArgument {
/// The phone number to be used for sign-in or sign-up.
final String phoneNumber;
/// Creates a [SignInWithPhoneArguments] instance.
///
/// The [phoneNumber] is required.
const SignInWithPhoneArguments({required this.phoneNumber});
/// The phone number to be used for sign-in or sign-up.
final String phoneNumber;
@override
List<Object> get props => <Object>[phoneNumber];

View File

@@ -6,14 +6,6 @@ import '../ui_entities/auth_mode.dart';
/// Encapsulates the verification ID and the SMS code needed to verify
/// a phone number during the authentication process.
class VerifyOtpArguments extends UseCaseArgument {
/// The unique identifier received after requesting an OTP.
final String verificationId;
/// The one-time password (OTP) sent to the user's phone.
final String smsCode;
/// The authentication mode (login or signup).
final AuthMode mode;
/// Creates a [VerifyOtpArguments] instance.
///
@@ -23,6 +15,14 @@ class VerifyOtpArguments extends UseCaseArgument {
required this.smsCode,
required this.mode,
});
/// The unique identifier received after requesting an OTP.
final String verificationId;
/// The one-time password (OTP) sent to the user's phone.
final String smsCode;
/// The authentication mode (login or signup).
final AuthMode mode;
@override
List<Object> get props => <Object>[verificationId, smsCode, mode];

View File

@@ -1,4 +1,3 @@
import 'package:krow_domain/krow_domain.dart';
abstract class ProfileSetupRepository {
Future<void> submitProfile({

View File

@@ -1,9 +1,9 @@
import '../repositories/place_repository.dart';
class SearchCitiesUseCase {
final PlaceRepository _repository;
SearchCitiesUseCase(this._repository);
final PlaceRepository _repository;
Future<List<String>> call(String query) {
return _repository.searchCities(query);

View File

@@ -7,12 +7,12 @@ import '../repositories/auth_repository_interface.dart';
/// This use case delegates the sign-in logic to the [AuthRepositoryInterface].
class SignInWithPhoneUseCase
implements UseCase<SignInWithPhoneArguments, String?> {
final AuthRepositoryInterface _repository;
/// Creates a [SignInWithPhoneUseCase].
///
/// Requires an [AuthRepositoryInterface] to interact with the authentication data source.
SignInWithPhoneUseCase(this._repository);
final AuthRepositoryInterface _repository;
@override
Future<String?> call(SignInWithPhoneArguments arguments) {

View File

@@ -1,9 +1,9 @@
import '../repositories/profile_setup_repository.dart';
class SubmitProfileSetup {
final ProfileSetupRepository repository;
SubmitProfileSetup(this.repository);
final ProfileSetupRepository repository;
Future<void> call({
required String fullName,

View File

@@ -7,12 +7,12 @@ import '../repositories/auth_repository_interface.dart';
///
/// This use case delegates the OTP verification logic to the [AuthRepositoryInterface].
class VerifyOtpUseCase implements UseCase<VerifyOtpArguments, User?> {
final AuthRepositoryInterface _repository;
/// Creates a [VerifyOtpUseCase].
///
/// Requires an [AuthRepositoryInterface] to interact with the authentication data source.
VerifyOtpUseCase(this._repository);
final AuthRepositoryInterface _repository;
@override
Future<User?> call(VerifyOtpArguments arguments) {

View File

@@ -14,16 +14,6 @@ import 'auth_state.dart';
class AuthBloc extends Bloc<AuthEvent, AuthState>
with BlocErrorHandler<AuthState>
implements Disposable {
/// The use case for signing in with a phone number.
final SignInWithPhoneUseCase _signInUseCase;
/// The use case for verifying an OTP.
final VerifyOtpUseCase _verifyOtpUseCase;
int _requestToken = 0;
DateTime? _lastCodeRequestAt;
DateTime? _cooldownUntil;
static const Duration _resendCooldown = Duration(seconds: 31);
Timer? _cooldownTimer;
/// Creates an [AuthBloc].
AuthBloc({
@@ -40,6 +30,16 @@ class AuthBloc extends Bloc<AuthEvent, AuthState>
on<AuthResetRequested>(_onResetRequested);
on<AuthCooldownTicked>(_onCooldownTicked);
}
/// The use case for signing in with a phone number.
final SignInWithPhoneUseCase _signInUseCase;
/// The use case for verifying an OTP.
final VerifyOtpUseCase _verifyOtpUseCase;
int _requestToken = 0;
DateTime? _lastCodeRequestAt;
DateTime? _cooldownUntil;
static const Duration _resendCooldown = Duration(seconds: 31);
Timer? _cooldownTimer;
/// Clears any authentication error from the state.
void _onErrorCleared(AuthErrorCleared event, Emitter<AuthState> emit) {
@@ -111,7 +111,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState>
);
await handleError(
emit: emit,
emit: emit.call,
action: () async {
final String? verificationId = await _signInUseCase(
SignInWithPhoneArguments(
@@ -193,7 +193,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState>
) async {
emit(state.copyWith(status: AuthStatus.loading));
await handleError(
emit: emit,
emit: emit.call,
action: () async {
final User? user = await _verifyOtpUseCase(
VerifyOtpArguments(

View File

@@ -10,14 +10,14 @@ abstract class AuthEvent extends Equatable {
/// Event for requesting a sign-in with a phone number.
class AuthSignInRequested extends AuthEvent {
const AuthSignInRequested({this.phoneNumber, required this.mode});
/// The phone number provided by the user.
final String? phoneNumber;
/// The authentication mode (login or signup).
final AuthMode mode;
const AuthSignInRequested({this.phoneNumber, required this.mode});
@override
List<Object?> get props => <Object?>[phoneNumber, mode];
}
@@ -27,6 +27,12 @@ class AuthSignInRequested extends AuthEvent {
/// This event is dispatched after the user has received an OTP and
/// submits it for verification.
class AuthOtpSubmitted extends AuthEvent {
const AuthOtpSubmitted({
required this.verificationId,
required this.smsCode,
required this.mode,
});
/// The verification ID received after the phone number submission.
final String verificationId;
@@ -36,12 +42,6 @@ class AuthOtpSubmitted extends AuthEvent {
/// The authentication mode (login or signup).
final AuthMode mode;
const AuthOtpSubmitted({
required this.verificationId,
required this.smsCode,
required this.mode,
});
@override
List<Object?> get props => <Object?>[verificationId, smsCode, mode];
}
@@ -51,10 +51,10 @@ class AuthErrorCleared extends AuthEvent {}
/// Event for resetting the authentication flow back to initial.
class AuthResetRequested extends AuthEvent {
/// The authentication mode (login or signup).
final AuthMode mode;
const AuthResetRequested({required this.mode});
/// The authentication mode (login or signup).
final AuthMode mode;
@override
List<Object?> get props => <Object?>[mode];
@@ -62,9 +62,9 @@ class AuthResetRequested extends AuthEvent {
/// Event for ticking down the resend cooldown.
class AuthCooldownTicked extends AuthEvent {
final int secondsRemaining;
const AuthCooldownTicked(this.secondsRemaining);
final int secondsRemaining;
@override
List<Object?> get props => <Object?>[secondsRemaining];
@@ -72,10 +72,10 @@ class AuthCooldownTicked extends AuthEvent {
/// Event for updating the current draft OTP in the state.
class AuthOtpUpdated extends AuthEvent {
/// The current draft OTP.
final String otp;
const AuthOtpUpdated(this.otp);
/// The current draft OTP.
final String otp;
@override
List<Object?> get props => <Object?>[otp];
@@ -83,10 +83,10 @@ class AuthOtpUpdated extends AuthEvent {
/// Event for updating the current draft phone number in the state.
class AuthPhoneUpdated extends AuthEvent {
/// The current draft phone number.
final String phoneNumber;
const AuthPhoneUpdated(this.phoneNumber);
/// The current draft phone number.
final String phoneNumber;
@override
List<Object?> get props => <Object?>[phoneNumber];

View File

@@ -22,6 +22,17 @@ enum AuthStatus {
/// A unified state class for the authentication process.
class AuthState extends Equatable {
const AuthState({
this.status = AuthStatus.initial,
this.verificationId,
this.mode = AuthMode.login,
this.otp = '',
this.phoneNumber = '',
this.errorMessage,
this.cooldownSecondsRemaining = 0,
this.user,
});
/// The current status of the authentication flow.
final AuthStatus status;
@@ -46,17 +57,6 @@ class AuthState extends Equatable {
/// The authenticated user's data (available when status is [AuthStatus.authenticated]).
final User? user;
const AuthState({
this.status = AuthStatus.initial,
this.verificationId,
this.mode = AuthMode.login,
this.otp = '',
this.phoneNumber = '',
this.errorMessage,
this.cooldownSecondsRemaining = 0,
this.user,
});
@override
List<Object?> get props => <Object?>[
status,

View File

@@ -89,7 +89,7 @@ class ProfileSetupBloc extends Bloc<ProfileSetupEvent, ProfileSetupState>
emit(state.copyWith(status: ProfileSetupStatus.loading));
await handleError(
emit: emit,
emit: emit.call,
action: () async {
await _submitProfileSetup(
fullName: state.fullName,
@@ -114,18 +114,18 @@ class ProfileSetupBloc extends Bloc<ProfileSetupEvent, ProfileSetupState>
Emitter<ProfileSetupState> emit,
) async {
if (event.query.isEmpty) {
emit(state.copyWith(locationSuggestions: []));
emit(state.copyWith(locationSuggestions: <String>[]));
return;
}
// For search, we might want to handle errors silently or distinctively
// Using simple try-catch here as it's a search-as-you-type feature where error dialogs are intrusive
try {
final results = await _searchCities(event.query);
final List<String> results = await _searchCities(event.query);
emit(state.copyWith(locationSuggestions: results));
} catch (e) {
// Quietly fail or clear
emit(state.copyWith(locationSuggestions: []));
emit(state.copyWith(locationSuggestions: <String>[]));
}
}
@@ -133,7 +133,7 @@ class ProfileSetupBloc extends Bloc<ProfileSetupEvent, ProfileSetupState>
ProfileSetupClearLocationSuggestions event,
Emitter<ProfileSetupState> emit,
) {
emit(state.copyWith(locationSuggestions: []));
emit(state.copyWith(locationSuggestions: <String>[]));
}
}

View File

@@ -10,11 +10,11 @@ abstract class ProfileSetupEvent extends Equatable {
/// Event triggered when the full name changes.
class ProfileSetupFullNameChanged extends ProfileSetupEvent {
/// The new full name value.
final String fullName;
/// Creates a [ProfileSetupFullNameChanged] event.
const ProfileSetupFullNameChanged(this.fullName);
/// The new full name value.
final String fullName;
@override
List<Object?> get props => <Object?>[fullName];
@@ -22,11 +22,11 @@ class ProfileSetupFullNameChanged extends ProfileSetupEvent {
/// Event triggered when the bio changes.
class ProfileSetupBioChanged extends ProfileSetupEvent {
/// The new bio value.
final String bio;
/// Creates a [ProfileSetupBioChanged] event.
const ProfileSetupBioChanged(this.bio);
/// The new bio value.
final String bio;
@override
List<Object?> get props => <Object?>[bio];
@@ -34,11 +34,11 @@ class ProfileSetupBioChanged extends ProfileSetupEvent {
/// Event triggered when the preferred locations change.
class ProfileSetupLocationsChanged extends ProfileSetupEvent {
/// The new list of locations.
final List<String> locations;
/// Creates a [ProfileSetupLocationsChanged] event.
const ProfileSetupLocationsChanged(this.locations);
/// The new list of locations.
final List<String> locations;
@override
List<Object?> get props => <Object?>[locations];
@@ -46,11 +46,11 @@ class ProfileSetupLocationsChanged extends ProfileSetupEvent {
/// Event triggered when the max distance changes.
class ProfileSetupDistanceChanged extends ProfileSetupEvent {
/// The new max distance value in miles.
final double distance;
/// Creates a [ProfileSetupDistanceChanged] event.
const ProfileSetupDistanceChanged(this.distance);
/// The new max distance value in miles.
final double distance;
@override
List<Object?> get props => <Object?>[distance];
@@ -58,11 +58,11 @@ class ProfileSetupDistanceChanged extends ProfileSetupEvent {
/// Event triggered when the skills change.
class ProfileSetupSkillsChanged extends ProfileSetupEvent {
/// The new list of selected skills.
final List<String> skills;
/// Creates a [ProfileSetupSkillsChanged] event.
const ProfileSetupSkillsChanged(this.skills);
/// The new list of selected skills.
final List<String> skills;
@override
List<Object?> get props => <Object?>[skills];
@@ -70,11 +70,11 @@ class ProfileSetupSkillsChanged extends ProfileSetupEvent {
/// Event triggered when the industries change.
class ProfileSetupIndustriesChanged extends ProfileSetupEvent {
/// The new list of selected industries.
final List<String> industries;
/// Creates a [ProfileSetupIndustriesChanged] event.
const ProfileSetupIndustriesChanged(this.industries);
/// The new list of selected industries.
final List<String> industries;
@override
List<Object?> get props => <Object?>[industries];
@@ -82,11 +82,11 @@ class ProfileSetupIndustriesChanged extends ProfileSetupEvent {
/// Event triggered when the location query changes.
class ProfileSetupLocationQueryChanged extends ProfileSetupEvent {
/// The search query.
final String query;
/// Creates a [ProfileSetupLocationQueryChanged] event.
const ProfileSetupLocationQueryChanged(this.query);
/// The search query.
final String query;
@override
List<Object?> get props => <Object?>[query];

View File

@@ -5,6 +5,19 @@ enum ProfileSetupStatus { initial, loading, success, failure }
/// State for the ProfileSetupBloc.
class ProfileSetupState extends Equatable {
/// Creates a [ProfileSetupState] instance.
const ProfileSetupState({
this.fullName = '',
this.bio = '',
this.preferredLocations = const <String>[],
this.maxDistanceMiles = 25,
this.skills = const <String>[],
this.industries = const <String>[],
this.status = ProfileSetupStatus.initial,
this.errorMessage,
this.locationSuggestions = const <String>[],
});
/// The user's full name.
final String fullName;
@@ -32,19 +45,6 @@ class ProfileSetupState extends Equatable {
/// List of location suggestions from the API.
final List<String> locationSuggestions;
/// Creates a [ProfileSetupState] instance.
const ProfileSetupState({
this.fullName = '',
this.bio = '',
this.preferredLocations = const <String>[],
this.maxDistanceMiles = 25,
this.skills = const <String>[],
this.industries = const <String>[],
this.status = ProfileSetupStatus.initial,
this.errorMessage,
this.locationSuggestions = const <String>[],
});
/// Creates a copy of the current state with updated values.
ProfileSetupState copyWith({
String? fullName,

View File

@@ -17,11 +17,11 @@ import '../widgets/phone_verification_page/phone_input.dart';
/// This page coordinates the authentication flow by switching between
/// [PhoneInput] and [OtpVerification] based on the current [AuthState].
class PhoneVerificationPage extends StatefulWidget {
/// The authentication mode (login or signup).
final AuthMode mode;
/// Creates a [PhoneVerificationPage].
const PhoneVerificationPage({super.key, required this.mode});
/// The authentication mode (login or signup).
final AuthMode mode;
@override
State<PhoneVerificationPage> createState() => _PhoneVerificationPageState();

View File

@@ -157,9 +157,9 @@ class _ProfileSetupPageState extends State<ProfileSetupPage> {
),
),
child: isCreatingProfile
? ElevatedButton(
? const ElevatedButton(
onPressed: null,
child: const SizedBox(
child: SizedBox(
width: 24,
height: 24,
child: CircularProgressIndicator(strokeWidth: 2),

View File

@@ -3,17 +3,17 @@ import 'package:flutter/material.dart';
/// A widget for displaying a section title and subtitle
class SectionTitleSubtitle extends StatelessWidget {
/// The title of the section
final String title;
/// The subtitle of the section
final String subtitle;
const SectionTitleSubtitle({
super.key,
required this.title,
required this.subtitle,
});
/// The title of the section
final String title;
/// The subtitle of the section
final String subtitle;
@override
Widget build(BuildContext context) {

View File

@@ -3,14 +3,14 @@ import 'package:design_system/design_system.dart';
import 'package:core_localization/core_localization.dart';
class GetStartedActions extends StatelessWidget {
final VoidCallback onSignUpPressed;
final VoidCallback onLoginPressed;
const GetStartedActions({
super.key,
required this.onSignUpPressed,
required this.onLoginPressed,
});
final VoidCallback onSignUpPressed;
final VoidCallback onLoginPressed;
@override
Widget build(BuildContext context) {

View File

@@ -15,7 +15,7 @@ class _GetStartedBackgroundState extends State<GetStartedBackground> {
Widget build(BuildContext context) {
return Container(
child: Column(
children: [
children: <Widget>[
const SizedBox(height: UiConstants.space8),
// Logo
Image.asset(
@@ -35,7 +35,7 @@ class _GetStartedBackgroundState extends State<GetStartedBackground> {
child: ClipOval(
child: Stack(
fit: StackFit.expand,
children: [
children: <Widget>[
// Layer 1: The Fallback Logo (Always visible until image loads)
Padding(
padding: const EdgeInsets.all(UiConstants.space12),
@@ -47,7 +47,7 @@ class _GetStartedBackgroundState extends State<GetStartedBackground> {
Image.network(
'https://images.unsplash.com/photo-1577219491135-ce391730fb2c?w=400&h=400&fit=crop&crop=faces',
fit: BoxFit.cover,
frameBuilder: (context, child, frame, wasSynchronouslyLoaded) {
frameBuilder: (BuildContext context, Widget child, int? frame, bool wasSynchronouslyLoaded) {
if (wasSynchronouslyLoaded) return child;
// Only animate opacity if we have a frame
return AnimatedOpacity(
@@ -56,12 +56,12 @@ class _GetStartedBackgroundState extends State<GetStartedBackground> {
child: child,
);
},
loadingBuilder: (context, child, loadingProgress) {
loadingBuilder: (BuildContext context, Widget child, ImageChunkEvent? loadingProgress) {
// While loading, show nothing (transparent) so layer 1 shows
if (loadingProgress == null) return child;
return const SizedBox.shrink();
},
errorBuilder: (context, error, stackTrace) {
errorBuilder: (BuildContext context, Object error, StackTrace? stackTrace) {
// On error, show nothing (transparent) so layer 1 shows
// Also schedule a state update to prevent retries if needed
WidgetsBinding.instance.addPostFrameCallback((_) {
@@ -83,7 +83,7 @@ class _GetStartedBackgroundState extends State<GetStartedBackground> {
// Pagination dots (Visual only)
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
children: <Widget>[
Container(
width: UiConstants.space6,
height: UiConstants.space2,

View File

@@ -8,6 +8,15 @@ import 'otp_verification/otp_verification_header.dart';
/// A widget that displays the OTP verification UI.
class OtpVerification extends StatelessWidget {
/// Creates an [OtpVerification].
const OtpVerification({
super.key,
required this.state,
required this.onOtpSubmitted,
required this.onResend,
required this.onContinue,
});
/// The current state of the authentication process.
final AuthState state;
@@ -20,15 +29,6 @@ class OtpVerification extends StatelessWidget {
/// Callback for the "Continue" action.
final VoidCallback onContinue;
/// Creates an [OtpVerification].
const OtpVerification({
super.key,
required this.state,
required this.onOtpSubmitted,
required this.onResend,
required this.onContinue,
});
@override
Widget build(BuildContext context) {
return Column(

View File

@@ -11,11 +11,6 @@ import '../../../blocs/auth_bloc.dart';
/// This widget handles its own internal [TextEditingController]s and focus nodes.
/// It dispatches [AuthOtpUpdated] to the [AuthBloc] on every change.
class OtpInputField extends StatefulWidget {
/// Callback for when the OTP code is fully entered (6 digits).
final ValueChanged<String> onCompleted;
/// The error message to display, if any.
final String error;
/// Creates an [OtpInputField].
const OtpInputField({
@@ -23,6 +18,11 @@ class OtpInputField extends StatefulWidget {
required this.onCompleted,
required this.error,
});
/// Callback for when the OTP code is fully entered (6 digits).
final ValueChanged<String> onCompleted;
/// The error message to display, if any.
final String error;
@override
State<OtpInputField> createState() => _OtpInputFieldState();

View File

@@ -4,11 +4,6 @@ import 'package:flutter/material.dart';
/// A widget that handles the OTP resend logic and countdown timer.
class OtpResendSection extends StatefulWidget {
/// Callback for when the resend link is pressed.
final VoidCallback onResend;
/// Whether an error is currently displayed. (Used for layout tweaks in the original code)
final bool hasError;
/// Creates an [OtpResendSection].
const OtpResendSection({
@@ -16,6 +11,11 @@ class OtpResendSection extends StatefulWidget {
required this.onResend,
this.hasError = false,
});
/// Callback for when the resend link is pressed.
final VoidCallback onResend;
/// Whether an error is currently displayed. (Used for layout tweaks in the original code)
final bool hasError;
@override
State<OtpResendSection> createState() => _OtpResendSectionState();

View File

@@ -6,14 +6,6 @@ import '../../common/auth_trouble_link.dart';
/// A widget that displays the primary action button and trouble link for OTP verification.
class OtpVerificationActions extends StatelessWidget {
/// Whether the verification process is currently loading.
final bool isLoading;
/// Whether the submit button should be enabled.
final bool canSubmit;
/// Callback for when the Continue button is pressed.
final VoidCallback? onContinue;
/// Creates an [OtpVerificationActions].
const OtpVerificationActions({
@@ -22,6 +14,14 @@ class OtpVerificationActions extends StatelessWidget {
required this.canSubmit,
this.onContinue,
});
/// Whether the verification process is currently loading.
final bool isLoading;
/// Whether the submit button should be enabled.
final bool canSubmit;
/// Callback for when the Continue button is pressed.
final VoidCallback? onContinue;
@override
Widget build(BuildContext context) {
@@ -36,9 +36,9 @@ class OtpVerificationActions extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
isLoading
? ElevatedButton(
? const ElevatedButton(
onPressed: null,
child: const SizedBox(
child: SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2),

View File

@@ -4,11 +4,11 @@ import 'package:flutter/material.dart';
/// A widget that displays the title and subtitle for the OTP Verification page.
class OtpVerificationHeader extends StatelessWidget {
/// The phone number to which the code was sent.
final String phoneNumber;
/// Creates an [OtpVerificationHeader].
const OtpVerificationHeader({super.key, required this.phoneNumber});
/// The phone number to which the code was sent.
final String phoneNumber;
@override
Widget build(BuildContext context) {

View File

@@ -5,11 +5,6 @@ import 'package:staff_authentication/src/presentation/widgets/common/auth_troubl
/// A widget that displays the primary action button and trouble link for Phone Input.
class PhoneInputActions extends StatelessWidget {
/// Whether the sign-in process is currently loading.
final bool isLoading;
/// Callback for when the Send Code button is pressed.
final VoidCallback? onSendCode;
/// Creates a [PhoneInputActions].
const PhoneInputActions({
@@ -17,6 +12,11 @@ class PhoneInputActions extends StatelessWidget {
required this.isLoading,
this.onSendCode,
});
/// Whether the sign-in process is currently loading.
final bool isLoading;
/// Callback for when the Send Code button is pressed.
final VoidCallback? onSendCode;
@override
Widget build(BuildContext context) {
@@ -29,9 +29,9 @@ class PhoneInputActions extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
isLoading
? UiButton.secondary(
? const UiButton.secondary(
onPressed: null,
child: const SizedBox(
child: SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2),

View File

@@ -2,20 +2,11 @@ import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:core_localization/core_localization.dart';
import 'package:staff_authentication/staff_authentication.dart';
/// A widget that displays the phone number input field with country code.
///
/// This widget handles its own [TextEditingController] to manage input.
class PhoneInputFormField extends StatefulWidget {
/// The initial value for the phone number.
final String initialValue;
/// The error message to display, if any.
final String error;
/// Callback for when the text field value changes.
final ValueChanged<String> onChanged;
/// Creates a [PhoneInputFormField].
const PhoneInputFormField({
@@ -24,6 +15,14 @@ class PhoneInputFormField extends StatefulWidget {
required this.error,
required this.onChanged,
});
/// The initial value for the phone number.
final String initialValue;
/// The error message to display, if any.
final String error;
/// Callback for when the text field value changes.
final ValueChanged<String> onChanged;
@override
State<PhoneInputFormField> createState() => _PhoneInputFormFieldState();

View File

@@ -5,6 +5,15 @@ import 'package:staff_authentication/src/presentation/widgets/common/section_tit
/// A widget for setting up basic profile information (photo, name, bio).
class ProfileSetupBasicInfo extends StatelessWidget {
/// Creates a [ProfileSetupBasicInfo] widget.
const ProfileSetupBasicInfo({
super.key,
required this.fullName,
required this.bio,
required this.onFullNameChanged,
required this.onBioChanged,
});
/// The user's full name.
final String fullName;
@@ -17,15 +26,6 @@ class ProfileSetupBasicInfo extends StatelessWidget {
/// Callback for when the bio changes.
final ValueChanged<String> onBioChanged;
/// Creates a [ProfileSetupBasicInfo] widget.
const ProfileSetupBasicInfo({
super.key,
required this.fullName,
required this.bio,
required this.onFullNameChanged,
required this.onBioChanged,
});
@override
/// Builds the basic info step UI.
Widget build(BuildContext context) {

View File

@@ -6,6 +6,15 @@ import 'package:staff_authentication/src/presentation/widgets/common/section_tit
/// A widget for setting up skills and preferred industries.
class ProfileSetupExperience extends StatelessWidget {
/// Creates a [ProfileSetupExperience] widget.
const ProfileSetupExperience({
super.key,
required this.skills,
required this.industries,
required this.onSkillsChanged,
required this.onIndustriesChanged,
});
/// The list of selected skills.
final List<String> skills;
@@ -18,15 +27,6 @@ class ProfileSetupExperience extends StatelessWidget {
/// Callback for when industries change.
final ValueChanged<List<String>> onIndustriesChanged;
/// Creates a [ProfileSetupExperience] widget.
const ProfileSetupExperience({
super.key,
required this.skills,
required this.industries,
required this.onSkillsChanged,
required this.onIndustriesChanged,
});
/// Toggles a skill.
void _toggleSkill({required String skill}) {
final List<String> updatedList = List<String>.from(skills);

View File

@@ -4,14 +4,6 @@ import 'package:flutter/material.dart';
/// A header widget for the profile setup page showing back button and step count.
class ProfileSetupHeader extends StatelessWidget {
/// The current step index (0-based).
final int currentStep;
/// The total number of steps.
final int totalSteps;
/// Callback when the back button is tapped.
final VoidCallback? onBackTap;
/// Creates a [ProfileSetupHeader].
const ProfileSetupHeader({
@@ -20,6 +12,14 @@ class ProfileSetupHeader extends StatelessWidget {
required this.totalSteps,
this.onBackTap,
});
/// The current step index (0-based).
final int currentStep;
/// The total number of steps.
final int totalSteps;
/// Callback when the back button is tapped.
final VoidCallback? onBackTap;
@override
/// Builds the header UI.

View File

@@ -9,6 +9,15 @@ import 'package:staff_authentication/src/presentation/widgets/common/section_tit
/// A widget for setting up preferred work locations and distance.
class ProfileSetupLocation extends StatefulWidget {
/// Creates a [ProfileSetupLocation] widget.
const ProfileSetupLocation({
super.key,
required this.preferredLocations,
required this.maxDistanceMiles,
required this.onLocationsChanged,
required this.onDistanceChanged,
});
/// The list of preferred locations.
final List<String> preferredLocations;
@@ -21,15 +30,6 @@ class ProfileSetupLocation extends StatefulWidget {
/// Callback for when the max distance changes.
final ValueChanged<double> onDistanceChanged;
/// Creates a [ProfileSetupLocation] widget.
const ProfileSetupLocation({
super.key,
required this.preferredLocations,
required this.maxDistanceMiles,
required this.onLocationsChanged,
required this.onDistanceChanged,
});
@override
State<ProfileSetupLocation> createState() => _ProfileSetupLocationState();
}
@@ -97,9 +97,9 @@ class _ProfileSetupLocationState extends State<ProfileSetupLocation> {
// Suggestions List
BlocBuilder<ProfileSetupBloc, ProfileSetupState>(
buildWhen: (previous, current) =>
buildWhen: (ProfileSetupState previous, ProfileSetupState current) =>
previous.locationSuggestions != current.locationSuggestions,
builder: (context, state) {
builder: (BuildContext context, ProfileSetupState state) {
if (state.locationSuggestions.isEmpty) {
return const SizedBox.shrink();
}
@@ -114,9 +114,9 @@ class _ProfileSetupLocationState extends State<ProfileSetupLocation> {
shrinkWrap: true,
padding: EdgeInsets.zero,
itemCount: state.locationSuggestions.length,
separatorBuilder: (context, index) => const Divider(height: 1),
itemBuilder: (context, index) {
final suggestion = state.locationSuggestions[index];
separatorBuilder: (BuildContext context, int index) => const Divider(height: 1),
itemBuilder: (BuildContext context, int index) {
final String suggestion = state.locationSuggestions[index];
return ListTile(
title: Text(suggestion, style: UiTypography.body2m),
leading: const Icon(UiIcons.mapPin, size: 16),

View File

@@ -31,7 +31,7 @@ class AvailabilityBloc extends Bloc<AvailabilityEvent, AvailabilityState>
) async {
emit(AvailabilityLoading());
await handleError(
emit: emit,
emit: emit.call,
action: () async {
final days = await getWeeklyAvailability(
GetWeeklyAvailabilityParams(event.weekStart),
@@ -103,7 +103,7 @@ class AvailabilityBloc extends Bloc<AvailabilityEvent, AvailabilityState>
));
await handleError(
emit: emit,
emit: emit.call,
action: () async {
await updateDayAvailability(UpdateDayAvailabilityParams(newDay));
// Success feedback
@@ -155,7 +155,7 @@ class AvailabilityBloc extends Bloc<AvailabilityEvent, AvailabilityState>
));
await handleError(
emit: emit,
emit: emit.call,
action: () async {
await updateDayAvailability(UpdateDayAvailabilityParams(newDay));
// Success feedback
@@ -195,7 +195,7 @@ class AvailabilityBloc extends Bloc<AvailabilityEvent, AvailabilityState>
);
await handleError(
emit: emit,
emit: emit.call,
action: () async {
final newDays = await applyQuickSet(
ApplyQuickSetParams(currentState.currentWeekStart, event.type),

View File

@@ -404,7 +404,7 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
value: isAvailable,
onChanged: (val) =>
context.read<AvailabilityBloc>().add(ToggleDayStatus(day)),
activeColor: UiColors.primary,
activeThumbColor: UiColors.primary,
),
],
),
@@ -417,7 +417,7 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
final uiConfig = _getSlotUiConfig(slot.id);
return _buildTimeSlotItem(context, day, slot, uiConfig);
}).toList(),
}),
],
),
);

View File

@@ -1,4 +1,3 @@
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter_modular/flutter_modular.dart';
import 'package:krow_core/core.dart';
import 'package:krow_data_connect/krow_data_connect.dart';

View File

@@ -1,3 +1,3 @@
library staff_availability;
library;
export 'src/staff_availability_module.dart';

View File

@@ -49,7 +49,7 @@ class ClockInBloc extends Bloc<ClockInEvent, ClockInState>
) async {
emit(state.copyWith(status: ClockInStatus.loading));
await handleError(
emit: emit,
emit: emit.call,
action: () async {
final List<Shift> shifts = await _getTodaysShift();
final AttendanceStatus status = await _getAttendanceStatus();
@@ -88,7 +88,7 @@ class ClockInBloc extends Bloc<ClockInEvent, ClockInState>
Emitter<ClockInState> emit,
) async {
await handleError(
emit: emit,
emit: emit.call,
action: () async {
LocationPermission permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
@@ -203,7 +203,7 @@ class ClockInBloc extends Bloc<ClockInEvent, ClockInState>
) async {
emit(state.copyWith(status: ClockInStatus.actionInProgress));
await handleError(
emit: emit,
emit: emit.call,
action: () async {
final AttendanceStatus newStatus = await _clockIn(
ClockInArguments(shiftId: event.shiftId, notes: event.notes),
@@ -226,7 +226,7 @@ class ClockInBloc extends Bloc<ClockInEvent, ClockInState>
) async {
emit(state.copyWith(status: ClockInStatus.actionInProgress));
await handleError(
emit: emit,
emit: emit.call,
action: () async {
final AttendanceStatus newStatus = await _clockOut(
ClockOutArguments(

View File

@@ -32,7 +32,7 @@ class _ClockInPageState extends State<ClockInPage> {
@override
Widget build(BuildContext context) {
final i18n = Translations.of(context).staff.clock_in;
final TranslationsStaffClockInEn i18n = Translations.of(context).staff.clock_in;
return BlocProvider<ClockInBloc>.value(
value: _bloc,
child: BlocConsumer<ClockInBloc, ClockInState>(
@@ -479,7 +479,7 @@ class _ClockInPageState extends State<ClockInPage> {
}
Future<void> _showNFCDialog(BuildContext context) async {
final i18n = Translations.of(context).staff.clock_in;
final TranslationsStaffClockInEn i18n = Translations.of(context).staff.clock_in;
bool scanned = false;
// Using a local navigator context since we are in a dialog
@@ -622,7 +622,7 @@ class _ClockInPageState extends State<ClockInPage> {
final DateTime windowStart = shiftStart.subtract(const Duration(minutes: 15));
return DateFormat('h:mm a').format(windowStart);
} catch (e) {
final i18n = Translations.of(context).staff.clock_in;
final TranslationsStaffClockInEn i18n = Translations.of(context).staff.clock_in;
return i18n.soon;
}
}

View File

@@ -132,7 +132,7 @@ class _CommuteTrackerState extends State<CommuteTracker> {
@override
Widget build(BuildContext context) {
final CommuteMode mode = _getAppMode();
final i18n = Translations.of(context).staff.clock_in.commute;
final TranslationsStaffClockInCommuteEn i18n = Translations.of(context).staff.clock_in.commute;
// Notify parent of mode change
WidgetsBinding.instance.addPostFrameCallback((_) {
@@ -501,7 +501,7 @@ class _CommuteTrackerState extends State<CommuteTracker> {
margin: const EdgeInsets.only(bottom: UiConstants.space5),
padding: const EdgeInsets.all(UiConstants.space5),
decoration: BoxDecoration(
gradient: LinearGradient(
gradient: const LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: <Color>[

View File

@@ -39,7 +39,7 @@ class _LunchBreakDialogState extends State<LunchBreakDialog> {
@override
Widget build(BuildContext context) {
final i18n = Translations.of(context).staff.clock_in.lunch_break;
final TranslationsStaffClockInLunchBreakEn i18n = Translations.of(context).staff.clock_in.lunch_break;
return Dialog(
backgroundColor: UiColors.white,
shape: RoundedRectangleBorder(
@@ -171,7 +171,7 @@ class _LunchBreakDialogState extends State<LunchBreakDialog> {
Expanded(
child: DropdownButtonFormField<String>(
isExpanded: true,
value: _breakStart,
initialValue: _breakStart,
items: _timeOptions
.map(
(String t) => DropdownMenuItem(
@@ -194,7 +194,7 @@ class _LunchBreakDialogState extends State<LunchBreakDialog> {
Expanded(
child: DropdownButtonFormField<String>(
isExpanded: true,
value: _breakEnd,
initialValue: _breakEnd,
items: _timeOptions
.map(
(String t) => DropdownMenuItem(

View File

@@ -72,7 +72,7 @@ class _SwipeToCheckInState extends State<SwipeToCheckIn>
@override
Widget build(BuildContext context) {
final i18n = Translations.of(context).staff.clock_in.swipe;
final TranslationsStaffClockInSwipeEn i18n = Translations.of(context).staff.clock_in.swipe;
final Color baseColor = widget.isCheckedIn
? UiColors.success
: UiColors.primary;

View File

@@ -1,3 +1,4 @@
import 'package:firebase_data_connect/src/core/ref.dart';
import 'package:krow_data_connect/krow_data_connect.dart' as dc;
import 'package:krow_data_connect/krow_data_connect.dart';
import 'package:krow_domain/krow_domain.dart';
@@ -16,7 +17,7 @@ class PaymentsRepositoryImpl
final String currentStaffId = await _service.getStaffId();
// Fetch recent payments with a limit
final response = await _service.connector.listRecentPaymentsByStaffId(
final QueryResult<dc.ListRecentPaymentsByStaffIdData, dc.ListRecentPaymentsByStaffIdVariables> response = await _service.connector.listRecentPaymentsByStaffId(
staffId: currentStaffId,
).limit(100).execute();
@@ -61,7 +62,7 @@ class PaymentsRepositoryImpl
return _service.run(() async {
final String currentStaffId = await _service.getStaffId();
final response = await _service.connector
final QueryResult<dc.ListRecentPaymentsByStaffIdData, dc.ListRecentPaymentsByStaffIdVariables> response = await _service.connector
.listRecentPaymentsByStaffId(staffId: currentStaffId)
.execute();

View File

@@ -25,7 +25,7 @@ class PaymentsBloc extends Bloc<PaymentsEvent, PaymentsState>
) async {
emit(PaymentsLoading());
await handleError(
emit: emit,
emit: emit.call,
action: () async {
final PaymentSummary currentSummary = await getPaymentSummary();
@@ -51,7 +51,7 @@ class PaymentsBloc extends Bloc<PaymentsEvent, PaymentsState>
final PaymentsState currentState = state;
if (currentState is PaymentsLoaded) {
await handleError(
emit: emit,
emit: emit.call,
action: () async {
final List<StaffPayment> newHistory = await getPaymentHistory(
GetPaymentHistoryArguments(event.period),

View File

@@ -37,7 +37,7 @@ class _PaymentsPageState extends State<PaymentsPage> {
child: Scaffold(
backgroundColor: UiColors.background,
body: BlocConsumer<PaymentsBloc, PaymentsState>(
listener: (context, state) {
listener: (BuildContext context, PaymentsState state) {
// Error is already shown on the page itself (lines 53-63), no need for snackbar
},
builder: (BuildContext context, PaymentsState state) {

View File

@@ -10,12 +10,6 @@ import 'profile_state.dart';
/// Handles loading profile data and user sign-out actions.
class ProfileCubit extends Cubit<ProfileState>
with BlocErrorHandler<ProfileState> {
final GetStaffProfileUseCase _getProfileUseCase;
final SignOutStaffUseCase _signOutUseCase;
final GetPersonalInfoCompletionUseCase _getPersonalInfoCompletionUseCase;
final GetEmergencyContactsCompletionUseCase _getEmergencyContactsCompletionUseCase;
final GetExperienceCompletionUseCase _getExperienceCompletionUseCase;
final GetTaxFormsCompletionUseCase _getTaxFormsCompletionUseCase;
/// Creates a [ProfileCubit] with the required use cases.
ProfileCubit(
@@ -26,6 +20,12 @@ class ProfileCubit extends Cubit<ProfileState>
this._getExperienceCompletionUseCase,
this._getTaxFormsCompletionUseCase,
) : super(const ProfileState());
final GetStaffProfileUseCase _getProfileUseCase;
final SignOutStaffUseCase _signOutUseCase;
final GetPersonalInfoCompletionUseCase _getPersonalInfoCompletionUseCase;
final GetEmergencyContactsCompletionUseCase _getEmergencyContactsCompletionUseCase;
final GetExperienceCompletionUseCase _getExperienceCompletionUseCase;
final GetTaxFormsCompletionUseCase _getTaxFormsCompletionUseCase;
/// Loads the staff member's profile.
///

View File

@@ -24,6 +24,16 @@ enum ProfileStatus {
/// Contains the current profile data and loading status.
/// Uses the [Staff] entity directly from domain layer.
class ProfileState extends Equatable {
const ProfileState({
this.status = ProfileStatus.initial,
this.profile,
this.errorMessage,
this.personalInfoComplete,
this.emergencyContactsComplete,
this.experienceComplete,
this.taxFormsComplete,
});
/// Current status of the profile feature
final ProfileStatus status;
@@ -45,16 +55,6 @@ class ProfileState extends Equatable {
/// Whether tax forms are complete
final bool? taxFormsComplete;
const ProfileState({
this.status = ProfileStatus.initial,
this.profile,
this.errorMessage,
this.personalInfoComplete,
this.emergencyContactsComplete,
this.experienceComplete,
this.taxFormsComplete,
});
/// Creates a copy of this state with updated values.
ProfileState copyWith({
ProfileStatus? status,

View File

@@ -1,7 +1,6 @@
import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_modular/flutter_modular.dart';
/// A bottom sheet that allows the user to select their preferred language.
@@ -15,8 +14,8 @@ class LanguageSelectorBottomSheet extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(UiConstants.space6),
decoration: BoxDecoration(
padding: const EdgeInsets.all(UiConstants.space6),
decoration: const BoxDecoration(
color: UiColors.background,
borderRadius: BorderRadius.vertical(top: Radius.circular(UiConstants.radiusBase)),
),
@@ -24,25 +23,25 @@ class LanguageSelectorBottomSheet extends StatelessWidget {
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
children: <Widget>[
Text(
t.settings.change_language,
style: UiTypography.headline4m,
textAlign: TextAlign.center,
),
SizedBox(height: UiConstants.space6),
const SizedBox(height: UiConstants.space6),
_buildLanguageOption(
context,
label: 'English',
locale: AppLocale.en,
),
SizedBox(height: UiConstants.space4),
const SizedBox(height: UiConstants.space4),
_buildLanguageOption(
context,
label: 'Español',
locale: AppLocale.es,
),
SizedBox(height: UiConstants.space6),
const SizedBox(height: UiConstants.space6),
],
),
),
@@ -73,7 +72,7 @@ class LanguageSelectorBottomSheet extends StatelessWidget {
},
borderRadius: BorderRadius.circular(UiConstants.radiusMdValue),
child: Container(
padding: EdgeInsets.symmetric(
padding: const EdgeInsets.symmetric(
vertical: UiConstants.space4,
horizontal: UiConstants.space4,
),
@@ -87,7 +86,7 @@ class LanguageSelectorBottomSheet extends StatelessWidget {
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
children: <Widget>[
Text(
label,
style: isSelected
@@ -95,7 +94,7 @@ class LanguageSelectorBottomSheet extends StatelessWidget {
: UiTypography.body1r,
),
if (isSelected)
Icon(
const Icon(
UiIcons.check,
color: UiColors.primary,
size: 24.0,

View File

@@ -4,14 +4,14 @@ import 'package:design_system/design_system.dart';
/// Lays out a list of widgets (intended for [ProfileMenuItem]s) in a responsive grid.
/// It uses [Wrap] and manually calculates item width based on the screen size.
class ProfileMenuGrid extends StatelessWidget {
final int crossAxisCount;
final List<Widget> children;
const ProfileMenuGrid({
super.key,
required this.children,
this.crossAxisCount = 2,
});
final int crossAxisCount;
final List<Widget> children;
@override
Widget build(BuildContext context) {
@@ -19,17 +19,17 @@ class ProfileMenuGrid extends StatelessWidget {
const double spacing = UiConstants.space3;
return LayoutBuilder(
builder: (context, constraints) {
final totalWidth = constraints.maxWidth;
final totalSpacingWidth = spacing * (crossAxisCount - 1);
final itemWidth = (totalWidth - totalSpacingWidth) / crossAxisCount;
builder: (BuildContext context, BoxConstraints constraints) {
final double totalWidth = constraints.maxWidth;
final double totalSpacingWidth = spacing * (crossAxisCount - 1);
final double itemWidth = (totalWidth - totalSpacingWidth) / crossAxisCount;
return Wrap(
spacing: spacing,
runSpacing: spacing,
alignment: WrapAlignment.start,
crossAxisAlignment: WrapCrossAlignment.start,
children: children.map((child) {
children: children.map((Widget child) {
return SizedBox(
width: itemWidth,
child: child,

View File

@@ -6,17 +6,17 @@ import 'package:design_system/design_system.dart';
///
/// Uses design system tokens for all colors, typography, and spacing.
class ReliabilityScoreBar extends StatelessWidget {
final int? reliabilityScore;
const ReliabilityScoreBar({
super.key,
this.reliabilityScore,
});
final int? reliabilityScore;
@override
Widget build(BuildContext context) {
final i18n = t.staff.profile.reliability_score;
final score = (reliabilityScore ?? 0) / 100;
final TranslationsStaffProfileReliabilityScoreEn i18n = t.staff.profile.reliability_score;
final double score = (reliabilityScore ?? 0) / 100;
return Container(
padding: const EdgeInsets.all(UiConstants.space4),
@@ -26,10 +26,10 @@ class ReliabilityScoreBar extends StatelessWidget {
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
children: <Widget>[
Text(
i18n.title,
style: UiTypography.body2m.primary,

View File

@@ -5,11 +5,6 @@ import 'package:flutter/material.dart';
///
/// Uses design system tokens for all colors, typography, spacing, and icons.
class ReliabilityStatsCard extends StatelessWidget {
final int? totalShifts;
final double? averageRating;
final int? onTimeRate;
final int? noShowCount;
final int? cancellationCount;
const ReliabilityStatsCard({
super.key,
@@ -19,6 +14,11 @@ class ReliabilityStatsCard extends StatelessWidget {
this.noShowCount,
this.cancellationCount,
});
final int? totalShifts;
final double? averageRating;
final int? onTimeRate;
final int? noShowCount;
final int? cancellationCount;
@override
Widget build(BuildContext context) {
@@ -28,7 +28,7 @@ class ReliabilityStatsCard extends StatelessWidget {
color: UiColors.bgPopup,
borderRadius: UiConstants.radiusLg,
border: Border.all(color: UiColors.border),
boxShadow: [
boxShadow: <BoxShadow>[
BoxShadow(
color: UiColors.foreground.withValues(alpha: 0.05),
blurRadius: 4,
@@ -38,7 +38,7 @@ class ReliabilityStatsCard extends StatelessWidget {
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
children: <Widget>[
_buildStatItem(
context,
UiIcons.briefcase,
@@ -82,7 +82,7 @@ class ReliabilityStatsCard extends StatelessWidget {
) {
return Expanded(
child: Column(
children: [
children: <Widget>[
Container(
width: UiConstants.space10,
height: UiConstants.space10,

View File

@@ -5,9 +5,9 @@ import 'package:design_system/design_system.dart';
///
/// Uses design system tokens for typography, colors, and spacing.
class SectionTitle extends StatelessWidget {
final String title;
const SectionTitle(this.title, {super.key});
final String title;
@override
Widget build(BuildContext context) {

View File

@@ -1,3 +1,4 @@
import 'package:firebase_data_connect/src/core/ref.dart';
import 'package:krow_core/core.dart';
import 'package:krow_data_connect/krow_data_connect.dart';
import 'package:krow_domain/krow_domain.dart' as domain;
@@ -10,11 +11,11 @@ import '../../domain/repositories/certificates_repository.dart';
/// It maps raw generated data types to clean [domain.StaffDocument] entities.
class CertificatesRepositoryImpl
implements CertificatesRepository {
/// The Data Connect service instance.
final DataConnectService _service;
/// Creates a [CertificatesRepositoryImpl].
CertificatesRepositoryImpl() : _service = DataConnectService.instance;
/// The Data Connect service instance.
final DataConnectService _service;
@override
Future<List<domain.StaffDocument>> getCertificates() async {
@@ -22,7 +23,7 @@ class CertificatesRepositoryImpl
final String staffId = await _service.getStaffId();
// Execute the query via DataConnect generated SDK
final result =
final QueryResult<ListStaffDocumentsByStaffIdData, ListStaffDocumentsByStaffIdVariables> result =
await _service.connector
.listStaffDocumentsByStaffId(staffId: staffId)
.execute();

View File

@@ -7,12 +7,12 @@ import '../repositories/certificates_repository.dart';
/// Delegates the data retrieval to the [CertificatesRepository].
/// Follows the strict one-to-one mapping between action and use case.
class GetCertificatesUseCase extends NoInputUseCase<List<StaffDocument>> {
final CertificatesRepository _repository;
/// Creates a [GetCertificatesUseCase].
///
/// Requires a [CertificatesRepository] to access the certificates data source.
GetCertificatesUseCase(this._repository);
final CertificatesRepository _repository;
@override
Future<List<StaffDocument>> call() {

View File

@@ -6,12 +6,12 @@ import 'certificates_state.dart';
class CertificatesCubit extends Cubit<CertificatesState>
with BlocErrorHandler<CertificatesState> {
final GetCertificatesUseCase _getCertificatesUseCase;
CertificatesCubit(this._getCertificatesUseCase)
: super(const CertificatesState()) {
loadCertificates();
}
final GetCertificatesUseCase _getCertificatesUseCase;
Future<void> loadCertificates() async {
emit(state.copyWith(status: CertificatesStatus.loading));

View File

@@ -4,15 +4,15 @@ import 'package:krow_domain/krow_domain.dart';
enum CertificatesStatus { initial, loading, success, failure }
class CertificatesState extends Equatable {
final CertificatesStatus status;
final List<StaffDocument> certificates;
final String? errorMessage;
const CertificatesState({
this.status = CertificatesStatus.initial,
List<StaffDocument>? certificates,
this.errorMessage,
}) : certificates = certificates ?? const <StaffDocument>[];
final CertificatesStatus status;
final List<StaffDocument> certificates;
final String? errorMessage;
CertificatesState copyWith({
CertificatesStatus? status,
@@ -27,11 +27,11 @@ class CertificatesState extends Equatable {
}
@override
List<Object?> get props => [status, certificates, errorMessage];
List<Object?> get props => <Object?>[status, certificates, errorMessage];
/// The number of verified certificates.
int get completedCount => certificates
.where((doc) => doc.status == DocumentStatus.verified)
.where((StaffDocument doc) => doc.status == DocumentStatus.verified)
.length;
/// The total number of certificates.

View File

@@ -3,9 +3,9 @@ import 'package:flutter/material.dart';
import 'package:core_localization/core_localization.dart';
class AddCertificateCard extends StatelessWidget {
final VoidCallback onTap;
const AddCertificateCard({super.key, required this.onTap});
final VoidCallback onTap;
@override
Widget build(BuildContext context) {

View File

@@ -5,11 +5,6 @@ import 'package:intl/intl.dart';
import 'package:krow_domain/krow_domain.dart';
class CertificateCard extends StatelessWidget {
final StaffDocument document;
final VoidCallback? onUpload;
final VoidCallback? onEditExpiry;
final VoidCallback? onRemove;
final VoidCallback? onView;
const CertificateCard({
super.key,
@@ -19,6 +14,11 @@ class CertificateCard extends StatelessWidget {
this.onRemove,
this.onView,
});
final StaffDocument document;
final VoidCallback? onUpload;
final VoidCallback? onEditExpiry;
final VoidCallback? onRemove;
final VoidCallback? onView;
@override
Widget build(BuildContext context) {
@@ -412,7 +412,7 @@ class CertificateCard extends StatelessWidget {
}
class _CertificateUiProps {
_CertificateUiProps(this.icon, this.color);
final IconData icon;
final Color color;
_CertificateUiProps(this.icon, this.color);
}

View File

@@ -4,6 +4,13 @@ import 'package:flutter/material.dart';
/// Modal for uploading or editing a certificate expiry.
class CertificateUploadModal extends StatelessWidget {
const CertificateUploadModal({
super.key,
this.document,
required this.onSave,
required this.onCancel,
});
/// The document being edited, or null for a new upload.
// ignore: unused_field
final dynamic
@@ -13,13 +20,6 @@ class CertificateUploadModal extends StatelessWidget {
final VoidCallback onSave;
final VoidCallback onCancel;
const CertificateUploadModal({
super.key,
this.document,
required this.onSave,
required this.onCancel,
});
@override
Widget build(BuildContext context) {
return Container(
@@ -100,7 +100,7 @@ class CertificateUploadModal extends StatelessWidget {
children: <Widget>[
Container(
padding: const EdgeInsets.all(UiConstants.space4),
decoration: BoxDecoration(
decoration: const BoxDecoration(
color: UiColors.tagActive,
shape: BoxShape.circle,
),

View File

@@ -4,14 +4,14 @@ import 'package:flutter_modular/flutter_modular.dart';
import 'package:core_localization/core_localization.dart';
class CertificatesHeader extends StatelessWidget {
final int completedCount;
final int totalCount;
const CertificatesHeader({
super.key,
required this.completedCount,
required this.totalCount,
});
final int completedCount;
final int totalCount;
@override
Widget build(BuildContext context) {

View File

@@ -1,3 +1,3 @@
library staff_certificates;
library;
export 'src/staff_certificates_module.dart';

View File

@@ -7,21 +7,21 @@ import '../../domain/repositories/documents_repository.dart';
/// Implementation of [DocumentsRepository] using Data Connect.
class DocumentsRepositoryImpl
implements DocumentsRepository {
final DataConnectService _service;
DocumentsRepositoryImpl() : _service = DataConnectService.instance;
final DataConnectService _service;
@override
Future<List<domain.StaffDocument>> getDocuments() async {
return _service.run(() async {
final String? staffId = await _service.getStaffId();
final String staffId = await _service.getStaffId();
/// MOCK IMPLEMENTATION
/// To be replaced with real data connect query when available
return [
return <domain.StaffDocument>[
domain.StaffDocument(
id: 'doc1',
staffId: staffId!,
staffId: staffId,
documentId: 'd1',
name: 'Work Permit',
description: 'Valid work permit document',
@@ -31,7 +31,7 @@ class DocumentsRepositoryImpl
),
domain.StaffDocument(
id: 'doc2',
staffId: staffId!,
staffId: staffId,
documentId: 'd2',
name: 'Health and Safety Training',
description: 'Certificate of completion for health and safety training',

View File

@@ -6,9 +6,9 @@ import '../repositories/documents_repository.dart';
///
/// Delegates to [DocumentsRepository].
class GetDocumentsUseCase implements NoInputUseCase<List<StaffDocument>> {
final DocumentsRepository _repository;
GetDocumentsUseCase(this._repository);
final DocumentsRepository _repository;
@override
Future<List<StaffDocument>> call() {

View File

@@ -6,9 +6,9 @@ import 'documents_state.dart';
class DocumentsCubit extends Cubit<DocumentsState>
with BlocErrorHandler<DocumentsState> {
final GetDocumentsUseCase _getDocumentsUseCase;
DocumentsCubit(this._getDocumentsUseCase) : super(const DocumentsState());
final GetDocumentsUseCase _getDocumentsUseCase;
Future<void> loadDocuments() async {
emit(state.copyWith(status: DocumentsStatus.loading));

View File

@@ -4,15 +4,15 @@ import 'package:krow_domain/krow_domain.dart';
enum DocumentsStatus { initial, loading, success, failure }
class DocumentsState extends Equatable {
final DocumentsStatus status;
final List<StaffDocument> documents;
final String? errorMessage;
const DocumentsState({
this.status = DocumentsStatus.initial,
List<StaffDocument>? documents,
this.errorMessage,
}) : documents = documents ?? const <StaffDocument>[];
final DocumentsStatus status;
final List<StaffDocument> documents;
final String? errorMessage;
DocumentsState copyWith({
DocumentsStatus? status,

View File

@@ -8,11 +8,12 @@ import 'package:core_localization/core_localization.dart';
import '../blocs/documents/documents_cubit.dart';
import '../blocs/documents/documents_state.dart';
import 'package:krow_core/core.dart';
import '../widgets/document_card.dart';
import '../widgets/documents_progress_card.dart';
class DocumentsPage extends StatelessWidget {
const DocumentsPage({super.key});
@override
Widget build(BuildContext context) {

View File

@@ -5,14 +5,14 @@ import 'package:krow_domain/krow_domain.dart';
import 'package:core_localization/core_localization.dart';
class DocumentCard extends StatelessWidget {
final StaffDocument document;
final VoidCallback? onTap;
const DocumentCard({
super.key,
required this.document,
this.onTap,
});
final StaffDocument document;
final VoidCallback? onTap;
@override
Widget build(BuildContext context) {

View File

@@ -5,6 +5,13 @@ import 'package:core_localization/core_localization.dart';
/// A card displaying the overall verification progress of documents.
class DocumentsProgressCard extends StatelessWidget {
const DocumentsProgressCard({
super.key,
required this.completedCount,
required this.totalCount,
required this.progress,
});
/// The number of verified documents.
final int completedCount;
@@ -14,13 +21,6 @@ class DocumentsProgressCard extends StatelessWidget {
/// The progress ratio (0.0 to 1.0).
final double progress;
const DocumentsProgressCard({
super.key,
required this.completedCount,
required this.totalCount,
required this.progress,
});
@override
Widget build(BuildContext context) {
return Container(

View File

@@ -18,7 +18,7 @@ class StaffDocumentsModule extends Module {
void routes(RouteManager r) {
r.child(
StaffPaths.childRoute(StaffPaths.documents, StaffPaths.documents),
child: (_) => DocumentsPage(),
child: (_) => const DocumentsPage(),
);
}
}

View File

@@ -1,3 +1,3 @@
library staff_documents;
library;
export 'src/staff_documents_module.dart';

View File

@@ -6,7 +6,7 @@ import 'package:krow_domain/krow_domain.dart';
class TaxFormMapper {
static TaxForm fromDataConnect(dc.GetTaxFormsByStaffIdTaxForms form) {
// Construct the legacy map for the entity
final Map<String, dynamic> formData = {
final Map<String, dynamic> formData = <String, dynamic>{
'firstName': form.firstName,
'lastName': form.lastName,
'middleInitial': form.mInitial,

View File

@@ -17,7 +17,7 @@ class TaxFormsRepositoryImpl
Future<List<TaxForm>> getTaxForms() async {
return _service.run(() async {
final String staffId = await _service.getStaffId();
final response = await _service.connector
final QueryResult<dc.GetTaxFormsByStaffIdData, dc.GetTaxFormsByStaffIdVariables> response = await _service.connector
.getTaxFormsByStaffId(staffId: staffId)
.execute();
@@ -39,7 +39,7 @@ class TaxFormsRepositoryImpl
}
if (createdNew) {
final response2 =
final QueryResult<dc.GetTaxFormsByStaffIdData, dc.GetTaxFormsByStaffIdVariables> response2 =
await _service.connector.getTaxFormsByStaffId(staffId: staffId).execute();
return response2.data.taxForms
.map(TaxFormMapper.fromDataConnect)
@@ -115,14 +115,18 @@ class TaxFormsRepositoryImpl
void _mapCommonFields(
dc.UpdateTaxFormVariablesBuilder builder, Map<String, dynamic> data) {
if (data.containsKey('firstName'))
if (data.containsKey('firstName')) {
builder.firstName(data['firstName'] as String?);
if (data.containsKey('lastName'))
}
if (data.containsKey('lastName')) {
builder.lastName(data['lastName'] as String?);
if (data.containsKey('middleInitial'))
}
if (data.containsKey('middleInitial')) {
builder.mInitial(data['middleInitial'] as String?);
if (data.containsKey('otherLastNames'))
}
if (data.containsKey('otherLastNames')) {
builder.oLastName(data['otherLastNames'] as String?);
}
if (data.containsKey('dob')) {
final String dob = data['dob'] as String;
// Handle both ISO string and MM/dd/yyyy manual entry
@@ -155,14 +159,17 @@ class TaxFormsRepositoryImpl
}
if (data.containsKey('email')) builder.email(data['email'] as String?);
if (data.containsKey('phone')) builder.phone(data['phone'] as String?);
if (data.containsKey('address'))
if (data.containsKey('address')) {
builder.address(data['address'] as String?);
if (data.containsKey('aptNumber'))
}
if (data.containsKey('aptNumber')) {
builder.apt(data['aptNumber'] as String?);
}
if (data.containsKey('city')) builder.city(data['city'] as String?);
if (data.containsKey('state')) builder.state(data['state'] as String?);
if (data.containsKey('zipCode'))
if (data.containsKey('zipCode')) {
builder.zipCode(data['zipCode'] as String?);
}
}
void _mapI9Fields(
@@ -176,16 +183,21 @@ class TaxFormsRepositoryImpl
dc.CitizenshipStatus.values.byName(status.toUpperCase()));
} catch (_) {}
}
if (data.containsKey('uscisNumber'))
if (data.containsKey('uscisNumber')) {
builder.uscis(data['uscisNumber'] as String?);
if (data.containsKey('passportNumber'))
}
if (data.containsKey('passportNumber')) {
builder.passportNumber(data['passportNumber'] as String?);
if (data.containsKey('countryIssuance'))
}
if (data.containsKey('countryIssuance')) {
builder.countryIssue(data['countryIssuance'] as String?);
if (data.containsKey('preparerUsed'))
}
if (data.containsKey('preparerUsed')) {
builder.prepartorOrTranslator(data['preparerUsed'] as bool?);
if (data.containsKey('signature'))
}
if (data.containsKey('signature')) {
builder.signature(data['signature'] as String?);
}
// Note: admissionNumber not in builder based on file read
}
@@ -208,19 +220,23 @@ class TaxFormsRepositoryImpl
try {
final String status = data['filingStatus'] as String;
// Simple mapping assumptions:
if (status.contains('single')) builder.marital(dc.MaritalStatus.SINGLE);
else if (status.contains('married'))
if (status.contains('single')) {
builder.marital(dc.MaritalStatus.SINGLE);
} else if (status.contains('married'))
builder.marital(dc.MaritalStatus.MARRIED);
else if (status.contains('head'))
builder.marital(dc.MaritalStatus.HEAD);
} catch (_) {}
}
if (data.containsKey('multipleJobs'))
if (data.containsKey('multipleJobs')) {
builder.multipleJob(data['multipleJobs'] as bool?);
if (data.containsKey('qualifyingChildren'))
}
if (data.containsKey('qualifyingChildren')) {
builder.childrens(data['qualifyingChildren'] as int?);
if (data.containsKey('otherDependents'))
}
if (data.containsKey('otherDependents')) {
builder.otherDeps(data['otherDependents'] as int?);
}
if (data.containsKey('otherIncome')) {
builder.otherInconme(double.tryParse(data['otherIncome'].toString()));
}
@@ -231,8 +247,9 @@ class TaxFormsRepositoryImpl
builder.extraWithholding(
double.tryParse(data['extraWithholding'].toString()));
}
if (data.containsKey('signature'))
if (data.containsKey('signature')) {
builder.signature(data['signature'] as String?);
}
}
}

View File

@@ -2,9 +2,9 @@ import 'package:krow_domain/krow_domain.dart';
import '../repositories/tax_forms_repository.dart';
class GetTaxFormsUseCase {
final TaxFormsRepository _repository;
GetTaxFormsUseCase(this._repository);
final TaxFormsRepository _repository;
Future<List<TaxForm>> call() async {
return _repository.getTaxForms();

View File

@@ -2,9 +2,9 @@ import 'package:krow_domain/krow_domain.dart';
import '../repositories/tax_forms_repository.dart';
class SaveI9FormUseCase {
final TaxFormsRepository _repository;
SaveI9FormUseCase(this._repository);
final TaxFormsRepository _repository;
Future<void> call(I9TaxForm form) async {
return _repository.updateI9Form(form);

View File

@@ -2,9 +2,9 @@ import 'package:krow_domain/krow_domain.dart';
import '../repositories/tax_forms_repository.dart';
class SaveW4FormUseCase {
final TaxFormsRepository _repository;
SaveW4FormUseCase(this._repository);
final TaxFormsRepository _repository;
Future<void> call(W4TaxForm form) async {
return _repository.updateW4Form(form);

View File

@@ -2,9 +2,9 @@ import 'package:krow_domain/krow_domain.dart';
import '../repositories/tax_forms_repository.dart';
class SubmitI9FormUseCase {
final TaxFormsRepository _repository;
SubmitI9FormUseCase(this._repository);
final TaxFormsRepository _repository;
Future<void> call(I9TaxForm form) async {
return _repository.submitI9Form(form);

View File

@@ -2,9 +2,9 @@ import 'package:krow_domain/krow_domain.dart';
import '../repositories/tax_forms_repository.dart';
class SubmitW4FormUseCase {
final TaxFormsRepository _repository;
SubmitW4FormUseCase(this._repository);
final TaxFormsRepository _repository;
Future<void> call(W4TaxForm form) async {
return _repository.submitW4Form(form);

View File

@@ -7,10 +7,10 @@ import '../../../domain/usecases/submit_i9_form_usecase.dart';
import 'form_i9_state.dart';
class FormI9Cubit extends Cubit<FormI9State> with BlocErrorHandler<FormI9State> {
final SubmitI9FormUseCase _submitI9FormUseCase;
String _formId = '';
FormI9Cubit(this._submitI9FormUseCase) : super(const FormI9State());
final SubmitI9FormUseCase _submitI9FormUseCase;
String _formId = '';
void initialize(TaxForm? form) {
if (form == null || form.formData.isEmpty) {
@@ -99,7 +99,7 @@ class FormI9Cubit extends Cubit<FormI9State> with BlocErrorHandler<FormI9State>
await handleError(
emit: emit,
action: () async {
final Map<String, dynamic> formData = {
final Map<String, dynamic> formData = <String, dynamic>{
'firstName': state.firstName,
'lastName': state.lastName,
'middleInitial': state.middleInitial,

View File

@@ -3,6 +3,32 @@ import 'package:equatable/equatable.dart';
enum FormI9Status { initial, submitting, success, failure }
class FormI9State extends Equatable {
const FormI9State({
this.currentStep = 0,
this.firstName = '',
this.lastName = '',
this.middleInitial = '',
this.otherLastNames = '',
this.dob = '',
this.ssn = '',
this.email = '',
this.phone = '',
this.address = '',
this.aptNumber = '',
this.city = '',
this.state = '',
this.zipCode = '',
this.citizenshipStatus = '',
this.uscisNumber = '',
this.admissionNumber = '',
this.passportNumber = '',
this.countryIssuance = '',
this.preparerUsed = false,
this.signature = '',
this.status = FormI9Status.initial,
this.errorMessage,
});
final int currentStep;
// Personal Info
final String firstName;
@@ -35,32 +61,6 @@ class FormI9State extends Equatable {
final FormI9Status status;
final String? errorMessage;
const FormI9State({
this.currentStep = 0,
this.firstName = '',
this.lastName = '',
this.middleInitial = '',
this.otherLastNames = '',
this.dob = '',
this.ssn = '',
this.email = '',
this.phone = '',
this.address = '',
this.aptNumber = '',
this.city = '',
this.state = '',
this.zipCode = '',
this.citizenshipStatus = '',
this.uscisNumber = '',
this.admissionNumber = '',
this.passportNumber = '',
this.countryIssuance = '',
this.preparerUsed = false,
this.signature = '',
this.status = FormI9Status.initial,
this.errorMessage,
});
FormI9State copyWith({
int? currentStep,
String? firstName,
@@ -114,7 +114,7 @@ class FormI9State extends Equatable {
}
@override
List<Object?> get props => [
List<Object?> get props => <Object?>[
currentStep,
firstName,
lastName,

View File

@@ -6,9 +6,9 @@ import 'tax_forms_state.dart';
class TaxFormsCubit extends Cubit<TaxFormsState>
with BlocErrorHandler<TaxFormsState> {
final GetTaxFormsUseCase _getTaxFormsUseCase;
TaxFormsCubit(this._getTaxFormsUseCase) : super(const TaxFormsState());
final GetTaxFormsUseCase _getTaxFormsUseCase;
Future<void> loadTaxForms() async {
emit(state.copyWith(status: TaxFormsStatus.loading));

View File

@@ -4,15 +4,15 @@ import 'package:krow_domain/krow_domain.dart';
enum TaxFormsStatus { initial, loading, success, failure }
class TaxFormsState extends Equatable {
final TaxFormsStatus status;
final List<TaxForm> forms;
final String? errorMessage;
const TaxFormsState({
this.status = TaxFormsStatus.initial,
this.forms = const <TaxForm>[],
this.errorMessage,
});
final TaxFormsStatus status;
final List<TaxForm> forms;
final String? errorMessage;
TaxFormsState copyWith({
TaxFormsStatus? status,

View File

@@ -7,10 +7,10 @@ import '../../../domain/usecases/submit_w4_form_usecase.dart';
import 'form_w4_state.dart';
class FormW4Cubit extends Cubit<FormW4State> with BlocErrorHandler<FormW4State> {
final SubmitW4FormUseCase _submitW4FormUseCase;
String _formId = '';
FormW4Cubit(this._submitW4FormUseCase) : super(const FormW4State());
final SubmitW4FormUseCase _submitW4FormUseCase;
String _formId = '';
void initialize(TaxForm? form) {
if (form == null || form.formData.isEmpty) {
@@ -92,7 +92,7 @@ class FormW4Cubit extends Cubit<FormW4State> with BlocErrorHandler<FormW4State>
await handleError(
emit: emit,
action: () async {
final Map<String, dynamic> formData = {
final Map<String, dynamic> formData = <String, dynamic>{
'firstName': state.firstName,
'lastName': state.lastName,
'ssn': state.ssn,

View File

@@ -3,6 +3,25 @@ import 'package:equatable/equatable.dart';
enum FormW4Status { initial, submitting, success, failure }
class FormW4State extends Equatable {
const FormW4State({
this.currentStep = 0,
this.firstName = '',
this.lastName = '',
this.ssn = '',
this.address = '',
this.cityStateZip = '',
this.filingStatus = '',
this.multipleJobs = false,
this.qualifyingChildren = 0,
this.otherDependents = 0,
this.otherIncome = '',
this.deductions = '',
this.extraWithholding = '',
this.signature = '',
this.status = FormW4Status.initial,
this.errorMessage,
});
final int currentStep;
// Personal Info
@@ -29,25 +48,6 @@ class FormW4State extends Equatable {
final FormW4Status status;
final String? errorMessage;
const FormW4State({
this.currentStep = 0,
this.firstName = '',
this.lastName = '',
this.ssn = '',
this.address = '',
this.cityStateZip = '',
this.filingStatus = '',
this.multipleJobs = false,
this.qualifyingChildren = 0,
this.otherDependents = 0,
this.otherIncome = '',
this.deductions = '',
this.extraWithholding = '',
this.signature = '',
this.status = FormW4Status.initial,
this.errorMessage,
});
FormW4State copyWith({
int? currentStep,
String? firstName,
@@ -89,7 +89,7 @@ class FormW4State extends Equatable {
int get totalCredits => (qualifyingChildren * 2000) + (otherDependents * 500);
@override
List<Object?> get props => [
List<Object?> get props => <Object?>[
currentStep,
firstName,
lastName,

View File

@@ -9,8 +9,8 @@ import '../blocs/i9/form_i9_cubit.dart';
import '../blocs/i9/form_i9_state.dart';
class FormI9Page extends StatefulWidget {
final TaxForm? form;
const FormI9Page({super.key, this.form});
final TaxForm? form;
@override
State<FormI9Page> createState() => _FormI9PageState();
@@ -77,7 +77,7 @@ class _FormI9PageState extends State<FormI9Page> {
@override
Widget build(BuildContext context) {
final i18n = Translations.of(context).staff_compliance.tax_forms.i9;
final TranslationsStaffComplianceTaxFormsI9En i18n = Translations.of(context).staff_compliance.tax_forms.i9;
final List<Map<String, String>> steps = <Map<String, String>>[
<String, String>{'title': i18n.steps.personal, 'subtitle': i18n.steps.personal_sub},
@@ -150,7 +150,7 @@ class _FormI9PageState extends State<FormI9Page> {
Container(
width: 64,
height: 64,
decoration: BoxDecoration(
decoration: const BoxDecoration(
color: UiColors.tagSuccess,
shape: BoxShape.circle,
),
@@ -507,7 +507,7 @@ class _FormI9PageState extends State<FormI9Page> {
),
const SizedBox(height: UiConstants.space1 + 2),
DropdownButtonFormField<String>(
value: state.state.isEmpty ? null : state.state,
initialValue: state.state.isEmpty ? null : state.state,
onChanged: (String? val) =>
context.read<FormI9Cubit>().stateChanged(val ?? ''),
items: _usStates.map((String stateAbbr) {
@@ -828,7 +828,7 @@ class _FormI9PageState extends State<FormI9Page> {
}
String _getReadableCitizenship(String status) {
final i18n = Translations.of(context).staff_compliance.tax_forms.i9.fields;
final TranslationsStaffComplianceTaxFormsI9FieldsEn i18n = Translations.of(context).staff_compliance.tax_forms.i9.fields;
switch (status) {
case 'CITIZEN':
return i18n.status_us_citizen;
@@ -848,7 +848,7 @@ class _FormI9PageState extends State<FormI9Page> {
FormI9State state,
List<Map<String, String>> steps,
) {
final i18n = Translations.of(context).staff_compliance.tax_forms.i9;
final TranslationsStaffComplianceTaxFormsI9En i18n = Translations.of(context).staff_compliance.tax_forms.i9;
return Container(
padding: const EdgeInsets.all(UiConstants.space4),

View File

@@ -9,8 +9,8 @@ import '../blocs/w4/form_w4_cubit.dart';
import '../blocs/w4/form_w4_state.dart';
class FormW4Page extends StatefulWidget {
final TaxForm? form;
const FormW4Page({super.key, this.form});
final TaxForm? form;
@override
State<FormW4Page> createState() => _FormW4PageState();
@@ -123,7 +123,7 @@ class _FormW4PageState extends State<FormW4Page> {
@override
Widget build(BuildContext context) {
final i18n = Translations.of(context).staff_compliance.tax_forms.w4;
final TranslationsStaffComplianceTaxFormsW4En i18n = Translations.of(context).staff_compliance.tax_forms.w4;
final List<Map<String, String>> steps = <Map<String, String>>[
<String, String>{'title': i18n.steps.personal, 'subtitle': i18n.step_label(current: '1', total: '5')},
@@ -198,7 +198,7 @@ class _FormW4PageState extends State<FormW4Page> {
Container(
width: 64,
height: 64,
decoration: BoxDecoration(
decoration: const BoxDecoration(
color: UiColors.tagSuccess,
shape: BoxShape.circle,
),
@@ -1065,7 +1065,7 @@ class _FormW4PageState extends State<FormW4Page> {
}
String _getFilingStatusLabel(String status) {
final i18n = Translations.of(context).staff_compliance.tax_forms.w4.fields;
final TranslationsStaffComplianceTaxFormsW4FieldsEn i18n = Translations.of(context).staff_compliance.tax_forms.w4.fields;
switch (status) {
case 'SINGLE':
return i18n.status_single;
@@ -1083,7 +1083,7 @@ class _FormW4PageState extends State<FormW4Page> {
FormW4State state,
List<Map<String, String>> steps,
) {
final i18n = Translations.of(context).staff_compliance.tax_forms.w4;
final TranslationsStaffComplianceTaxFormsW4En i18n = Translations.of(context).staff_compliance.tax_forms.w4;
return Container(
padding: const EdgeInsets.all(UiConstants.space4),

View File

@@ -150,12 +150,12 @@ class TaxFormsPage extends StatelessWidget {
return GestureDetector(
onTap: () async {
if (form is I9TaxForm) {
final result = await Modular.to.pushNamed('i9', arguments: form);
final Object? result = await Modular.to.pushNamed('i9', arguments: form);
if (result == true && context.mounted) {
await BlocProvider.of<TaxFormsCubit>(context).loadTaxForms();
}
} else if (form is W4TaxForm) {
final result = await Modular.to.pushNamed('w4', arguments: form);
final Object? result = await Modular.to.pushNamed('w4', arguments: form);
if (result == true && context.mounted) {
await BlocProvider.of<TaxFormsCubit>(context).loadTaxForms();
}

View File

@@ -1,3 +1,3 @@
library staff_tax_forms;
library;
export 'src/staff_tax_forms_module.dart';

View File

@@ -4,12 +4,12 @@ import 'package:krow_domain/krow_domain.dart';
/// Arguments for adding a bank account.
class AddBankAccountParams extends UseCaseArgument with EquatableMixin {
final StaffBankAccount account;
const AddBankAccountParams({required this.account});
final StaffBankAccount account;
@override
List<Object?> get props => [account];
List<Object?> get props => <Object?>[account];
@override
bool? get stringify => true;

View File

@@ -4,9 +4,9 @@ import '../arguments/add_bank_account_params.dart';
/// Use case to add a bank account.
class AddBankAccountUseCase implements UseCase<AddBankAccountParams, void> {
final BankAccountRepository _repository;
AddBankAccountUseCase(this._repository);
final BankAccountRepository _repository;
@override
Future<void> call(AddBankAccountParams params) {

View File

@@ -4,9 +4,9 @@ import '../repositories/bank_account_repository.dart';
/// Use case to fetch bank accounts.
class GetBankAccountsUseCase implements NoInputUseCase<List<StaffBankAccount>> {
final BankAccountRepository _repository;
GetBankAccountsUseCase(this._repository);
final BankAccountRepository _repository;
@override
Future<List<StaffBankAccount>> call() {

View File

@@ -8,8 +8,6 @@ import 'bank_account_state.dart';
class BankAccountCubit extends Cubit<BankAccountState>
with BlocErrorHandler<BankAccountState> {
final GetBankAccountsUseCase _getBankAccountsUseCase;
final AddBankAccountUseCase _addBankAccountUseCase;
BankAccountCubit({
required GetBankAccountsUseCase getBankAccountsUseCase,
@@ -17,6 +15,8 @@ class BankAccountCubit extends Cubit<BankAccountState>
}) : _getBankAccountsUseCase = getBankAccountsUseCase,
_addBankAccountUseCase = addBankAccountUseCase,
super(const BankAccountState());
final GetBankAccountsUseCase _getBankAccountsUseCase;
final AddBankAccountUseCase _addBankAccountUseCase;
Future<void> loadAccounts() async {
emit(state.copyWith(status: BankAccountStatus.loading));

View File

@@ -4,18 +4,18 @@ import 'package:krow_domain/krow_domain.dart';
enum BankAccountStatus { initial, loading, loaded, error, accountAdded }
class BankAccountState extends Equatable {
const BankAccountState({
this.status = BankAccountStatus.initial,
this.accounts = const <StaffBankAccount>[],
this.errorMessage,
this.showForm = false,
});
final BankAccountStatus status;
final List<StaffBankAccount> accounts;
final String? errorMessage;
final bool showForm;
const BankAccountState({
this.status = BankAccountStatus.initial,
this.accounts = const [],
this.errorMessage,
this.showForm = false,
});
BankAccountState copyWith({
BankAccountStatus? status,
List<StaffBankAccount>? accounts,
@@ -31,5 +31,5 @@ class BankAccountState extends Equatable {
}
@override
List<Object?> get props => [status, accounts, errorMessage, showForm];
List<Object?> get props => <Object?>[status, accounts, errorMessage, showForm];
}

View File

@@ -8,7 +8,6 @@ import 'package:krow_domain/krow_domain.dart';
import '../blocs/bank_account_cubit.dart';
import '../blocs/bank_account_state.dart';
import 'package:krow_core/core.dart';
import '../widgets/add_account_form.dart';
class BankAccountPage extends StatelessWidget {

View File

@@ -1,15 +1,13 @@
import 'package:flutter/material.dart';
import 'package:flutter_modular/flutter_modular.dart';
import 'package:design_system/design_system.dart';
import '../blocs/bank_account_cubit.dart';
class AddAccountForm extends StatefulWidget {
const AddAccountForm({super.key, required this.strings, required this.onSubmit, required this.onCancel});
final dynamic strings;
final Function(String bankName, String routing, String account, String type) onSubmit;
final VoidCallback onCancel;
const AddAccountForm({super.key, required this.strings, required this.onSubmit, required this.onCancel});
@override
State<AddAccountForm> createState() => _AddAccountFormState();
}

View File

@@ -1,3 +1,3 @@
library staff_bank_account;
library;
export 'src/staff_bank_account_module.dart';

View File

@@ -8,11 +8,11 @@ import '../../domain/repositories/time_card_repository.dart';
/// Implementation of [TimeCardRepository] using Firebase Data Connect.
class TimeCardRepositoryImpl implements TimeCardRepository {
final dc.DataConnectService _service;
/// Creates a [TimeCardRepositoryImpl].
TimeCardRepositoryImpl({dc.DataConnectService? service})
: _service = service ?? dc.DataConnectService.instance;
final dc.DataConnectService _service;
@override
Future<List<TimeCard>> getTimeCards(DateTime month) async {

View File

@@ -2,11 +2,11 @@ import 'package:krow_core/core.dart';
/// Arguments for the GetTimeCardsUseCase.
class GetTimeCardsArguments extends UseCaseArgument {
const GetTimeCardsArguments(this.month);
/// The month to fetch time cards for.
final DateTime month;
const GetTimeCardsArguments(this.month);
@override
List<Object?> get props => [month];
List<Object?> get props => <Object?>[month];
}

View File

@@ -5,9 +5,9 @@ import '../repositories/time_card_repository.dart';
/// UseCase to retrieve time cards for a given month.
class GetTimeCardsUseCase extends UseCase<GetTimeCardsArguments, List<TimeCard>> {
final TimeCardRepository repository;
GetTimeCardsUseCase(this.repository);
final TimeCardRepository repository;
/// Executes the use case.
///

View File

@@ -11,12 +11,12 @@ part 'time_card_state.dart';
/// BLoC to manage Time Card state.
class TimeCardBloc extends Bloc<TimeCardEvent, TimeCardState>
with BlocErrorHandler<TimeCardState> {
final GetTimeCardsUseCase getTimeCards;
TimeCardBloc({required this.getTimeCards}) : super(TimeCardInitial()) {
on<LoadTimeCards>(_onLoadTimeCards);
on<ChangeMonth>(_onChangeMonth);
}
final GetTimeCardsUseCase getTimeCards;
/// Handles fetching time cards for the requested month.
Future<void> _onLoadTimeCards(
@@ -25,7 +25,7 @@ class TimeCardBloc extends Bloc<TimeCardEvent, TimeCardState>
) async {
emit(TimeCardLoading());
await handleError(
emit: emit,
emit: emit.call,
action: () async {
final List<TimeCard> cards = await getTimeCards(
GetTimeCardsArguments(event.month),

View File

@@ -3,21 +3,21 @@ part of 'time_card_bloc.dart';
abstract class TimeCardEvent extends Equatable {
const TimeCardEvent();
@override
List<Object?> get props => [];
List<Object?> get props => <Object?>[];
}
class LoadTimeCards extends TimeCardEvent {
final DateTime month;
const LoadTimeCards(this.month);
final DateTime month;
@override
List<Object?> get props => [month];
List<Object?> get props => <Object?>[month];
}
class ChangeMonth extends TimeCardEvent {
final DateTime month;
const ChangeMonth(this.month);
final DateTime month;
@override
List<Object?> get props => [month];
List<Object?> get props => <Object?>[month];
}

View File

@@ -3,16 +3,12 @@ part of 'time_card_bloc.dart';
abstract class TimeCardState extends Equatable {
const TimeCardState();
@override
List<Object?> get props => [];
List<Object?> get props => <Object?>[];
}
class TimeCardInitial extends TimeCardState {}
class TimeCardLoading extends TimeCardState {}
class TimeCardLoaded extends TimeCardState {
final List<TimeCard> timeCards;
final DateTime selectedMonth;
final double totalHours;
final double totalEarnings;
const TimeCardLoaded({
required this.timeCards,
@@ -20,13 +16,17 @@ class TimeCardLoaded extends TimeCardState {
required this.totalHours,
required this.totalEarnings,
});
final List<TimeCard> timeCards;
final DateTime selectedMonth;
final double totalHours;
final double totalEarnings;
@override
List<Object?> get props => [timeCards, selectedMonth, totalHours, totalEarnings];
List<Object?> get props => <Object?>[timeCards, selectedMonth, totalHours, totalEarnings];
}
class TimeCardError extends TimeCardState {
final String message;
const TimeCardError(this.message);
final String message;
@override
List<Object?> get props => [message];
List<Object?> get props => <Object?>[message];
}

View File

@@ -27,7 +27,7 @@ class _TimeCardPageState extends State<TimeCardPage> {
@override
Widget build(BuildContext context) {
final t = Translations.of(context);
final Translations t = Translations.of(context);
return BlocProvider.value(
value: _bloc,
child: Scaffold(
@@ -49,7 +49,7 @@ class _TimeCardPageState extends State<TimeCardPage> {
),
),
body: BlocConsumer<TimeCardBloc, TimeCardState>(
listener: (context, state) {
listener: (BuildContext context, TimeCardState state) {
if (state is TimeCardError) {
UiSnackbar.show(
context,
@@ -58,7 +58,7 @@ class _TimeCardPageState extends State<TimeCardPage> {
);
}
},
builder: (context, state) {
builder: (BuildContext context, TimeCardState state) {
if (state is TimeCardLoading) {
return const Center(child: CircularProgressIndicator());
} else if (state is TimeCardError) {
@@ -79,7 +79,7 @@ class _TimeCardPageState extends State<TimeCardPage> {
vertical: UiConstants.space6,
),
child: Column(
children: [
children: <Widget>[
MonthSelector(
selectedDate: state.selectedMonth,
onPreviousMonth: () => _bloc.add(ChangeMonth(

Some files were not shown because too many files have changed in this diff Show More